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 }; 130 131 static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, 132 enum ntsync_type type) 133 { 134 struct ntsync_obj *obj; 135 136 obj = kzalloc(sizeof(*obj), GFP_KERNEL); 137 if (!obj) 138 return NULL; 139 obj->type = type; 140 obj->dev = dev; 141 get_file(dev->file); 142 spin_lock_init(&obj->lock); 143 144 return obj; 145 } 146 147 static int ntsync_obj_get_fd(struct ntsync_obj *obj) 148 { 149 struct file *file; 150 int fd; 151 152 fd = get_unused_fd_flags(O_CLOEXEC); 153 if (fd < 0) 154 return fd; 155 file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); 156 if (IS_ERR(file)) { 157 put_unused_fd(fd); 158 return PTR_ERR(file); 159 } 160 obj->file = file; 161 fd_install(fd, file); 162 163 return fd; 164 } 165 166 static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) 167 { 168 struct ntsync_sem_args __user *user_args = argp; 169 struct ntsync_sem_args args; 170 struct ntsync_obj *sem; 171 int fd; 172 173 if (copy_from_user(&args, argp, sizeof(args))) 174 return -EFAULT; 175 176 if (args.count > args.max) 177 return -EINVAL; 178 179 sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); 180 if (!sem) 181 return -ENOMEM; 182 sem->u.sem.count = args.count; 183 sem->u.sem.max = args.max; 184 fd = ntsync_obj_get_fd(sem); 185 if (fd < 0) { 186 kfree(sem); 187 return fd; 188 } 189 190 return put_user(fd, &user_args->sem); 191 } 192 193 static int ntsync_char_open(struct inode *inode, struct file *file) 194 { 195 struct ntsync_device *dev; 196 197 dev = kzalloc(sizeof(*dev), GFP_KERNEL); 198 if (!dev) 199 return -ENOMEM; 200 201 file->private_data = dev; 202 dev->file = file; 203 return nonseekable_open(inode, file); 204 } 205 206 static int ntsync_char_release(struct inode *inode, struct file *file) 207 { 208 struct ntsync_device *dev = file->private_data; 209 210 kfree(dev); 211 212 return 0; 213 } 214 215 static long ntsync_char_ioctl(struct file *file, unsigned int cmd, 216 unsigned long parm) 217 { 218 struct ntsync_device *dev = file->private_data; 219 void __user *argp = (void __user *)parm; 220 221 switch (cmd) { 222 case NTSYNC_IOC_CREATE_SEM: 223 return ntsync_create_sem(dev, argp); 224 default: 225 return -ENOIOCTLCMD; 226 } 227 } 228 229 static const struct file_operations ntsync_fops = { 230 .owner = THIS_MODULE, 231 .open = ntsync_char_open, 232 .release = ntsync_char_release, 233 .unlocked_ioctl = ntsync_char_ioctl, 234 .compat_ioctl = compat_ptr_ioctl, 235 }; 236 237 static struct miscdevice ntsync_misc = { 238 .minor = MISC_DYNAMIC_MINOR, 239 .name = NTSYNC_NAME, 240 .fops = &ntsync_fops, 241 }; 242 243 module_misc_device(ntsync_misc); 244 245 MODULE_AUTHOR("Elizabeth Figura <zfigura@codeweavers.com>"); 246 MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); 247 MODULE_LICENSE("GPL"); 248