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 20 enum 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 35 struct 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 52 struct ntsync_device { 53 struct file *file; 54 }; 55 56 /* 57 * Actually change the semaphore state, returning -EOVERFLOW if it is made 58 * invalid. 59 */ 60 static 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 74 static 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 100 static 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 110 static 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 124 static 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 132 static 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 148 static 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 167 static 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 194 static 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 207 static 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 216 static 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 230 static 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 239 static struct miscdevice ntsync_misc = { 240 .minor = MISC_DYNAMIC_MINOR, 241 .name = NTSYNC_NAME, 242 .fops = &ntsync_fops, 243 }; 244 245 module_misc_device(ntsync_misc); 246 247 MODULE_AUTHOR("Elizabeth Figura <zfigura@codeweavers.com>"); 248 MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); 249 MODULE_LICENSE("GPL"); 250