diff options
Diffstat (limited to 'drivers/misc/ntsync.c')
-rw-r--r-- | drivers/misc/ntsync.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c new file mode 100644 index 0000000000..3c2f743c58 --- /dev/null +++ b/drivers/misc/ntsync.c @@ -0,0 +1,249 @@ +// 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, + .llseek = no_llseek, +}; + +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, + .llseek = no_llseek, +}; + +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"); |