at v6.11-rc3 5.1 kB view raw
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * ntsync.c - Kernel driver for NT synchronization primitives 4 * 5 * Copyright (C) 2024 Elizabeth Figura <zfigura@codeweavers.com> 6 */ 7 8#include <linux/anon_inodes.h> 9#include <linux/file.h> 10#include <linux/fs.h> 11#include <linux/miscdevice.h> 12#include <linux/module.h> 13#include <linux/overflow.h> 14#include <linux/slab.h> 15#include <linux/spinlock.h> 16#include <uapi/linux/ntsync.h> 17 18#define NTSYNC_NAME "ntsync" 19 20enum ntsync_type { 21 NTSYNC_TYPE_SEM, 22}; 23 24/* 25 * Individual synchronization primitives are represented by 26 * struct ntsync_obj, and each primitive is backed by a file. 27 * 28 * The whole namespace is represented by a struct ntsync_device also 29 * backed by a file. 30 * 31 * Both rely on struct file for reference counting. Individual 32 * ntsync_obj objects take a reference to the device when created. 33 */ 34 35struct ntsync_obj { 36 spinlock_t lock; 37 38 enum ntsync_type type; 39 40 struct file *file; 41 struct ntsync_device *dev; 42 43 /* The following fields are protected by the object lock. */ 44 union { 45 struct { 46 __u32 count; 47 __u32 max; 48 } sem; 49 } u; 50}; 51 52struct ntsync_device { 53 struct file *file; 54}; 55 56/* 57 * Actually change the semaphore state, returning -EOVERFLOW if it is made 58 * invalid. 59 */ 60static int post_sem_state(struct ntsync_obj *sem, __u32 count) 61{ 62 __u32 sum; 63 64 lockdep_assert_held(&sem->lock); 65 66 if (check_add_overflow(sem->u.sem.count, count, &sum) || 67 sum > sem->u.sem.max) 68 return -EOVERFLOW; 69 70 sem->u.sem.count = sum; 71 return 0; 72} 73 74static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) 75{ 76 __u32 __user *user_args = argp; 77 __u32 prev_count; 78 __u32 args; 79 int ret; 80 81 if (copy_from_user(&args, argp, sizeof(args))) 82 return -EFAULT; 83 84 if (sem->type != NTSYNC_TYPE_SEM) 85 return -EINVAL; 86 87 spin_lock(&sem->lock); 88 89 prev_count = sem->u.sem.count; 90 ret = post_sem_state(sem, args); 91 92 spin_unlock(&sem->lock); 93 94 if (!ret && put_user(prev_count, user_args)) 95 ret = -EFAULT; 96 97 return ret; 98} 99 100static int ntsync_obj_release(struct inode *inode, struct file *file) 101{ 102 struct ntsync_obj *obj = file->private_data; 103 104 fput(obj->dev->file); 105 kfree(obj); 106 107 return 0; 108} 109 110static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, 111 unsigned long parm) 112{ 113 struct ntsync_obj *obj = file->private_data; 114 void __user *argp = (void __user *)parm; 115 116 switch (cmd) { 117 case NTSYNC_IOC_SEM_POST: 118 return ntsync_sem_post(obj, argp); 119 default: 120 return -ENOIOCTLCMD; 121 } 122} 123 124static const struct file_operations ntsync_obj_fops = { 125 .owner = THIS_MODULE, 126 .release = ntsync_obj_release, 127 .unlocked_ioctl = ntsync_obj_ioctl, 128 .compat_ioctl = compat_ptr_ioctl, 129 .llseek = no_llseek, 130}; 131 132static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, 133 enum ntsync_type type) 134{ 135 struct ntsync_obj *obj; 136 137 obj = kzalloc(sizeof(*obj), GFP_KERNEL); 138 if (!obj) 139 return NULL; 140 obj->type = type; 141 obj->dev = dev; 142 get_file(dev->file); 143 spin_lock_init(&obj->lock); 144 145 return obj; 146} 147 148static int ntsync_obj_get_fd(struct ntsync_obj *obj) 149{ 150 struct file *file; 151 int fd; 152 153 fd = get_unused_fd_flags(O_CLOEXEC); 154 if (fd < 0) 155 return fd; 156 file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); 157 if (IS_ERR(file)) { 158 put_unused_fd(fd); 159 return PTR_ERR(file); 160 } 161 obj->file = file; 162 fd_install(fd, file); 163 164 return fd; 165} 166 167static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) 168{ 169 struct ntsync_sem_args __user *user_args = argp; 170 struct ntsync_sem_args args; 171 struct ntsync_obj *sem; 172 int fd; 173 174 if (copy_from_user(&args, argp, sizeof(args))) 175 return -EFAULT; 176 177 if (args.count > args.max) 178 return -EINVAL; 179 180 sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); 181 if (!sem) 182 return -ENOMEM; 183 sem->u.sem.count = args.count; 184 sem->u.sem.max = args.max; 185 fd = ntsync_obj_get_fd(sem); 186 if (fd < 0) { 187 kfree(sem); 188 return fd; 189 } 190 191 return put_user(fd, &user_args->sem); 192} 193 194static int ntsync_char_open(struct inode *inode, struct file *file) 195{ 196 struct ntsync_device *dev; 197 198 dev = kzalloc(sizeof(*dev), GFP_KERNEL); 199 if (!dev) 200 return -ENOMEM; 201 202 file->private_data = dev; 203 dev->file = file; 204 return nonseekable_open(inode, file); 205} 206 207static int ntsync_char_release(struct inode *inode, struct file *file) 208{ 209 struct ntsync_device *dev = file->private_data; 210 211 kfree(dev); 212 213 return 0; 214} 215 216static long ntsync_char_ioctl(struct file *file, unsigned int cmd, 217 unsigned long parm) 218{ 219 struct ntsync_device *dev = file->private_data; 220 void __user *argp = (void __user *)parm; 221 222 switch (cmd) { 223 case NTSYNC_IOC_CREATE_SEM: 224 return ntsync_create_sem(dev, argp); 225 default: 226 return -ENOIOCTLCMD; 227 } 228} 229 230static const struct file_operations ntsync_fops = { 231 .owner = THIS_MODULE, 232 .open = ntsync_char_open, 233 .release = ntsync_char_release, 234 .unlocked_ioctl = ntsync_char_ioctl, 235 .compat_ioctl = compat_ptr_ioctl, 236 .llseek = no_llseek, 237}; 238 239static struct miscdevice ntsync_misc = { 240 .minor = MISC_DYNAMIC_MINOR, 241 .name = NTSYNC_NAME, 242 .fops = &ntsync_fops, 243}; 244 245module_misc_device(ntsync_misc); 246 247MODULE_AUTHOR("Elizabeth Figura <zfigura@codeweavers.com>"); 248MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); 249MODULE_LICENSE("GPL");