249 lines · 5.0 KB · C
RawDownload
// 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");