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@xxxxxxxx> > Cc: Christian Brauner <brauner@xxxxxxxxxx> > Cc: Günther Noack <gnoack@xxxxxxxxxx> > --- > 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 > >