[RFC PATCH] fs: Add vfs_masks_device_ioctl*() helpers

Mickaël Salaün mic at digikod.net
Fri Mar 1 13:42:00 UTC 2024


Arnd, Christian, are you OK with this approach to identify VFS IOCTLs?

If yes, Günther should include it in his next patch series.

On Mon, Feb 19, 2024 at 07:35:39PM +0100, Mickaël Salaün wrote:
> vfs_masks_device_ioctl() and vfs_masks_device_ioctl_compat() are useful
> to differenciate between device driver IOCTL implementations and
> filesystem ones.  The goal is to be able to filter well-defined IOCTLs
> from per-device (i.e. namespaced) IOCTLs and control such access.
> 
> Add a new ioctl_compat() helper, similar to vfs_ioctl(), to wrap
> compat_ioctl() calls and handle error conversions.
> 
> Cc: Arnd Bergmann <arnd at arndb.de>
> Cc: Christian Brauner <brauner at kernel.org>
> Cc: Günther Noack <gnoack at google.com>
> ---
>  fs/ioctl.c         | 101 +++++++++++++++++++++++++++++++++++++++++----
>  include/linux/fs.h |  12 ++++++
>  2 files changed, 105 insertions(+), 8 deletions(-)
> 
> diff --git a/fs/ioctl.c b/fs/ioctl.c
> index 76cf22ac97d7..f72c8da47d21 100644
> --- a/fs/ioctl.c
> +++ b/fs/ioctl.c
> @@ -763,6 +763,38 @@ static int ioctl_fssetxattr(struct file *file, void __user *argp)
>  	return err;
>  }
>  
> +/*
> + * Safeguard to maintain a list of valid IOCTLs handled by do_vfs_ioctl()
> + * instead of def_blk_fops or def_chr_fops (see init_special_inode).
> + */
> +__attribute_const__ bool vfs_masked_device_ioctl(const unsigned int cmd)
> +{
> +	switch (cmd) {
> +	case FIOCLEX:
> +	case FIONCLEX:
> +	case FIONBIO:
> +	case FIOASYNC:
> +	case FIOQSIZE:
> +	case FIFREEZE:
> +	case FITHAW:
> +	case FS_IOC_FIEMAP:
> +	case FIGETBSZ:
> +	case FICLONE:
> +	case FICLONERANGE:
> +	case FIDEDUPERANGE:
> +	/* FIONREAD is forwarded to device implementations. */
> +	case FS_IOC_GETFLAGS:
> +	case FS_IOC_SETFLAGS:
> +	case FS_IOC_FSGETXATTR:
> +	case FS_IOC_FSSETXATTR:
> +	/* file_ioctl()'s IOCTLs are forwarded to device implementations. */
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +EXPORT_SYMBOL(vfs_masked_device_ioctl);
> +
>  /*
>   * do_vfs_ioctl() is not for drivers and not intended to be EXPORT_SYMBOL()'d.
>   * It's just a simple helper for sys_ioctl and compat_sys_ioctl.
> @@ -858,6 +890,8 @@ SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
>  {
>  	struct fd f = fdget(fd);
>  	int error;
> +	const struct inode *inode;
> +	bool is_device;
>  
>  	if (!f.file)
>  		return -EBADF;
> @@ -866,9 +900,18 @@ SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
>  	if (error)
>  		goto out;
>  
> +	inode = file_inode(f.file);
> +	is_device = S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
> +	if (is_device && !vfs_masked_device_ioctl(cmd)) {
> +		error = vfs_ioctl(f.file, cmd, arg);
> +		goto out;
> +	}
> +
>  	error = do_vfs_ioctl(f.file, fd, cmd, arg);
> -	if (error == -ENOIOCTLCMD)
> +	if (error == -ENOIOCTLCMD) {
> +		WARN_ON_ONCE(is_device);
>  		error = vfs_ioctl(f.file, cmd, arg);
> +	}
>  
>  out:
>  	fdput(f);
> @@ -911,11 +954,49 @@ long compat_ptr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
>  }
>  EXPORT_SYMBOL(compat_ptr_ioctl);
>  
> +static long ioctl_compat(struct file *filp, unsigned int cmd,
> +			 compat_ulong_t arg)
> +{
> +	int error = -ENOTTY;
> +
> +	if (!filp->f_op->compat_ioctl)
> +		goto out;
> +
> +	error = filp->f_op->compat_ioctl(filp, cmd, arg);
> +	if (error == -ENOIOCTLCMD)
> +		error = -ENOTTY;
> +
> +out:
> +	return error;
> +}
> +
> +__attribute_const__ bool vfs_masked_device_ioctl_compat(const unsigned int cmd)
> +{
> +	switch (cmd) {
> +	case FICLONE:
> +#if defined(CONFIG_X86_64)
> +	case FS_IOC_RESVSP_32:
> +	case FS_IOC_RESVSP64_32:
> +	case FS_IOC_UNRESVSP_32:
> +	case FS_IOC_UNRESVSP64_32:
> +	case FS_IOC_ZERO_RANGE_32:
> +#endif
> +	case FS_IOC32_GETFLAGS:
> +	case FS_IOC32_SETFLAGS:
> +		return true;
> +	default:
> +		return vfs_masked_device_ioctl(cmd);
> +	}
> +}
> +EXPORT_SYMBOL(vfs_masked_device_ioctl_compat);
> +
>  COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd,
>  		       compat_ulong_t, arg)
>  {
>  	struct fd f = fdget(fd);
>  	int error;
> +	const struct inode *inode;
> +	bool is_device;
>  
>  	if (!f.file)
>  		return -EBADF;
> @@ -924,6 +1005,13 @@ COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd,
>  	if (error)
>  		goto out;
>  
> +	inode = file_inode(f.file);
> +	is_device = S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
> +	if (is_device && !vfs_masked_device_ioctl_compat(cmd)) {
> +		error = ioctl_compat(f.file, cmd, arg);
> +		goto out;
> +	}
> +
>  	switch (cmd) {
>  	/* FICLONE takes an int argument, so don't use compat_ptr() */
>  	case FICLONE:
> @@ -964,13 +1052,10 @@ COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd,
>  	default:
>  		error = do_vfs_ioctl(f.file, fd, cmd,
>  				     (unsigned long)compat_ptr(arg));
> -		if (error != -ENOIOCTLCMD)
> -			break;
> -
> -		if (f.file->f_op->compat_ioctl)
> -			error = f.file->f_op->compat_ioctl(f.file, cmd, arg);
> -		if (error == -ENOIOCTLCMD)
> -			error = -ENOTTY;
> +		if (error == -ENOIOCTLCMD) {
> +			WARN_ON_ONCE(is_device);
> +			error = ioctl_compat(f.file, cmd, arg);
> +		}
>  		break;
>  	}
>  
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index ed5966a70495..b620d0c00e16 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1902,6 +1902,18 @@ extern long compat_ptr_ioctl(struct file *file, unsigned int cmd,
>  #define compat_ptr_ioctl NULL
>  #endif
>  
> +extern __attribute_const__ bool vfs_masked_device_ioctl(const unsigned int cmd);
> +#ifdef CONFIG_COMPAT
> +extern __attribute_const__ bool
> +vfs_masked_device_ioctl_compat(const unsigned int cmd);
> +#else /* CONFIG_COMPAT */
> +static inline __attribute_const__ bool
> +vfs_masked_device_ioctl_compat(const unsigned int cmd)
> +{
> +	return vfs_masked_device_ioctl(cmd);
> +}
> +#endif /* CONFIG_COMPAT */
> +
>  /*
>   * VFS file helper functions.
>   */
> -- 
> 2.43.0
> 
> 



More information about the Linux-security-module-archive mailing list