| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * ntsync.c - Kernel driver for NT synchronization primitives | |
| * | |
| * Copyright (C) 2024 Elizabeth Figura <zfigura@codeweavers.com> | |
| */ | |
| #include <linux/anon_inodes.h> | |
| #include <linux/file.h> | |
| #include <linux/fs.h> | |
| #include <linux/miscdevice.h> | |
| #include <linux/module.h> | |
| #include <linux/overflow.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include <uapi/linux/ntsync.h> | |
| #define NTSYNC_NAME "ntsync" | |
| enum ntsync_type { | |
| NTSYNC_TYPE_SEM, | |
| }; | |
| /* | |
| * Individual synchronization primitives are represented by | |
| * struct ntsync_obj, and each primitive is backed by a file. | |
| * | |
| * The whole namespace is represented by a struct ntsync_device also | |
| * backed by a file. | |
| * | |
| * Both rely on struct file for reference counting. Individual | |
| * ntsync_obj objects take a reference to the device when created. | |
| */ | |
| struct ntsync_obj { | |
| spinlock_t lock; | |
| enum ntsync_type type; | |
| struct file *file; | |
| struct ntsync_device *dev; | |
| /* The following fields are protected by the object lock. */ | |
| union { | |
| struct { | |
| __u32 count; | |
| __u32 max; | |
| } sem; | |
| } u; | |
| }; | |
| struct ntsync_device { | |
| struct file *file; | |
| }; | |
| /* | |
| * Actually change the semaphore state, returning -EOVERFLOW if it is made | |
| * invalid. | |
| */ | |
| static int post_sem_state(struct ntsync_obj *sem, __u32 count) | |
| { | |
| __u32 sum; | |
| lockdep_assert_held(&sem->lock); | |
| if (check_add_overflow(sem->u.sem.count, count, &sum) || | |
| sum > sem->u.sem.max) | |
| return -EOVERFLOW; | |
| sem->u.sem.count = sum; | |
| return 0; | |
| } | |
| static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) | |
| { | |
| __u32 __user *user_args = argp; | |
| __u32 prev_count; | |
| __u32 args; | |
| int ret; | |
| if (copy_from_user(&args, argp, sizeof(args))) | |
| return -EFAULT; | |
| if (sem->type != NTSYNC_TYPE_SEM) | |
| return -EINVAL; | |
| spin_lock(&sem->lock); | |
| prev_count = sem->u.sem.count; | |
| ret = post_sem_state(sem, args); | |
| spin_unlock(&sem->lock); | |
| if (!ret && put_user(prev_count, user_args)) | |
| ret = -EFAULT; | |
| return ret; | |
| } | |
| static int ntsync_obj_release(struct inode *inode, struct file *file) | |
| { | |
| struct ntsync_obj *obj = file->private_data; | |
| fput(obj->dev->file); | |
| kfree(obj); | |
| return 0; | |
| } | |
| static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, | |
| unsigned long parm) | |
| { | |
| struct ntsync_obj *obj = file->private_data; | |
| void __user *argp = (void __user *)parm; | |
| switch (cmd) { | |
| case NTSYNC_IOC_SEM_POST: | |
| return ntsync_sem_post(obj, argp); | |
| default: | |
| return -ENOIOCTLCMD; | |
| } | |
| } | |
| static const struct file_operations ntsync_obj_fops = { | |
| .owner = THIS_MODULE, | |
| .release = ntsync_obj_release, | |
| .unlocked_ioctl = ntsync_obj_ioctl, | |
| .compat_ioctl = compat_ptr_ioctl, | |
| }; | |
| static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, | |
| enum ntsync_type type) | |
| { | |
| struct ntsync_obj *obj; | |
| obj = kzalloc(sizeof(*obj), GFP_KERNEL); | |
| if (!obj) | |
| return NULL; | |
| obj->type = type; | |
| obj->dev = dev; | |
| get_file(dev->file); | |
| spin_lock_init(&obj->lock); | |
| return obj; | |
| } | |
| static int ntsync_obj_get_fd(struct ntsync_obj *obj) | |
| { | |
| struct file *file; | |
| int fd; | |
| fd = get_unused_fd_flags(O_CLOEXEC); | |
| if (fd < 0) | |
| return fd; | |
| file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); | |
| if (IS_ERR(file)) { | |
| put_unused_fd(fd); | |
| return PTR_ERR(file); | |
| } | |
| obj->file = file; | |
| fd_install(fd, file); | |
| return fd; | |
| } | |
| static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) | |
| { | |
| struct ntsync_sem_args __user *user_args = argp; | |
| struct ntsync_sem_args args; | |
| struct ntsync_obj *sem; | |
| int fd; | |
| if (copy_from_user(&args, argp, sizeof(args))) | |
| return -EFAULT; | |
| if (args.count > args.max) | |
| return -EINVAL; | |
| sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); | |
| if (!sem) | |
| return -ENOMEM; | |
| sem->u.sem.count = args.count; | |
| sem->u.sem.max = args.max; | |
| fd = ntsync_obj_get_fd(sem); | |
| if (fd < 0) { | |
| kfree(sem); | |
| return fd; | |
| } | |
| return put_user(fd, &user_args->sem); | |
| } | |
| static int ntsync_char_open(struct inode *inode, struct file *file) | |
| { | |
| struct ntsync_device *dev; | |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
| if (!dev) | |
| return -ENOMEM; | |
| file->private_data = dev; | |
| dev->file = file; | |
| return nonseekable_open(inode, file); | |
| } | |
| static int ntsync_char_release(struct inode *inode, struct file *file) | |
| { | |
| struct ntsync_device *dev = file->private_data; | |
| kfree(dev); | |
| return 0; | |
| } | |
| static long ntsync_char_ioctl(struct file *file, unsigned int cmd, | |
| unsigned long parm) | |
| { | |
| struct ntsync_device *dev = file->private_data; | |
| void __user *argp = (void __user *)parm; | |
| switch (cmd) { | |
| case NTSYNC_IOC_CREATE_SEM: | |
| return ntsync_create_sem(dev, argp); | |
| default: | |
| return -ENOIOCTLCMD; | |
| } | |
| } | |
| static const struct file_operations ntsync_fops = { | |
| .owner = THIS_MODULE, | |
| .open = ntsync_char_open, | |
| .release = ntsync_char_release, | |
| .unlocked_ioctl = ntsync_char_ioctl, | |
| .compat_ioctl = compat_ptr_ioctl, | |
| }; | |
| static struct miscdevice ntsync_misc = { | |
| .minor = MISC_DYNAMIC_MINOR, | |
| .name = NTSYNC_NAME, | |
| .fops = &ntsync_fops, | |
| }; | |
| module_misc_device(ntsync_misc); | |
| MODULE_AUTHOR("Elizabeth Figura <zfigura@codeweavers.com>"); | |
| MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); | |
| MODULE_LICENSE("GPL"); |