From: John Groves famfs denies most namespace, attribute and data operations by default because the userspace log, not the kernel, is authoritative for a famfs instance. Earlier commits already guard each such operation with a famfs_opt_enabled(fsi, FAMFS_OPT_x) check backed by a permissive stub. This commit defines the permission bitmap and makes those checks live. Add: - FAMFS_OPT_* (uapi): a u64 permission bitmap, one bit per gated operation (create, mkdir, mknod, symlink, link, unlink, rmdir, rename, the four setattr components, data write, and MAP_CREATE), plus FAMFS_OPT_ALL. The FAMFS_OPT_XATTR bit is reserved - famfs has no xattr ops yet. - fsi->opts: a per-mount atomic64 bitmap initialized to FAMFS_OPT_DEFAULT, which sets famfs's default policy: create, mkdir, chmod, chown, utimes, write and MAP_CREATE are permitted; unlink of mapped files, link, symlink, mknod, rmdir, rename and truncate are denied. - the real famfs_opt_enabled() (replacing the stub), so every planted gate now consults fsi->opts. - FAMFSIOC_{GET,SET,CLEAR}_OPTS: read the bitmap, or enable/disable the bits set in a caller-supplied mask, returning the resulting bitmap. SET/CLEAR require CAP_SYS_ADMIN and reject unknown bits with -EINVAL; the bitmap is updated with atomic RMW so the checks stay lockless. Signed-off-by: John Groves --- fs/famfs/famfs_file.c | 55 ++++++++++++++++++++++++++++++++ fs/famfs/famfs_inode.c | 1 + fs/famfs/famfs_internal.h | 30 ++++++++++++++--- include/uapi/linux/famfs_ioctl.h | 45 ++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 4 deletions(-) diff --git a/fs/famfs/famfs_file.c b/fs/famfs/famfs_file.c index e11a55ecf8d7..abf049b32a4b 100644 --- a/fs/famfs/famfs_file.c +++ b/fs/famfs/famfs_file.c @@ -357,6 +357,49 @@ famfs_daxdev_open(struct file *file, void __user *arg) return rc; } +/** + * famfs_get_opts() - FAMFSIOC_GET_OPTS: return the permission bitmap + */ +static long famfs_get_opts(struct famfs_fs_info *fsi, void __user *arg) +{ + struct famfs_ioc_opts o = { .opts = atomic64_read(&fsi->opts) }; + + if (copy_to_user(arg, &o, sizeof(o))) + return -EFAULT; + + return 0; +} + +/* + * famfs_modify_opts() - FAMFSIOC_SET_OPTS / FAMFSIOC_CLEAR_OPTS + * @set: true to enable (OR in) the requested bits, false to disable (mask out) + * + * The caller supplies a mask of FAMFS_OPT_* bits; the resulting bitmap is + * returned. Requires CAP_SYS_ADMIN since it changes mount-wide policy. + */ +static long famfs_modify_opts(struct famfs_fs_info *fsi, void __user *arg, + bool set) +{ + struct famfs_ioc_opts o; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&o, arg, sizeof(o))) + return -EFAULT; + if (o.opts & ~FAMFS_OPT_ALL) + return -EINVAL; + + if (set) + o.opts = atomic64_fetch_or(o.opts, &fsi->opts) | o.opts; + else + o.opts = atomic64_fetch_and(~o.opts, &fsi->opts) & ~o.opts; + + if (copy_to_user(arg, &o, sizeof(o))) + return -EFAULT; + + return 0; +} + /** * famfs_file_ioctl() - Top-level famfs file ioctl handler * @file: the file @@ -378,6 +421,18 @@ famfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) rc = 0; break; + case FAMFSIOC_GET_OPTS: + rc = famfs_get_opts(fsi, (void __user *)arg); + break; + + case FAMFSIOC_SET_OPTS: + rc = famfs_modify_opts(fsi, (void __user *)arg, true); + break; + + case FAMFSIOC_CLEAR_OPTS: + rc = famfs_modify_opts(fsi, (void __user *)arg, false); + break; + case FAMFSIOC_DAXDEV_OPEN: rc = famfs_daxdev_open(file, (void __user *)arg); break; diff --git a/fs/famfs/famfs_inode.c b/fs/famfs/famfs_inode.c index a6c3b4574e69..6cbd7d657fd8 100644 --- a/fs/famfs/famfs_inode.c +++ b/fs/famfs/famfs_inode.c @@ -717,6 +717,7 @@ static int famfs_init_fs_context(struct fs_context *fc) return -ENOMEM; init_rwsem(&fsi->devlist_sem); + atomic64_set(&fsi->opts, FAMFS_OPT_DEFAULT); fsi->mount_opts.mode = FAMFS_DEFAULT_MODE; fc->s_fs_info = fsi; fc->ops = &famfs_context_ops; diff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h index b5f9c8d0349f..26873162b4a0 100644 --- a/fs/famfs/famfs_internal.h +++ b/fs/famfs/famfs_internal.h @@ -12,11 +12,24 @@ #define FAMFS_INTERNAL_H #include +#include #include #include #include +/* + * Default operation-permission bitmap (see FAMFS_OPT_* in the uapi header). + * This preserves famfs's historical behavior: file/dir creation, the fmap + * ioctl, data writes, and the non-resize setattr components are permitted; + * unlink of mapped files, link, symlink, mknod, rmdir, rename and truncate + * are denied until enabled via FAMFSIOC_SET_OPTS. + */ +#define FAMFS_OPT_DEFAULT (FAMFS_OPT_CREATE | FAMFS_OPT_MKDIR | \ + FAMFS_OPT_CHMOD | FAMFS_OPT_CHOWN | \ + FAMFS_OPT_UTIMES | FAMFS_OPT_WRITE | \ + FAMFS_OPT_MAP_CREATE) + extern const struct file_operations famfs_file_operations; /* @@ -104,6 +117,8 @@ struct famfs_dax_devlist { * @famfs_fs_info * * @mount_opts: The mount options + * @opts: Operation-permission bitmap (FAMFS_OPT_*), adjusted at runtime + * via the FAMFSIOC_{GET,SET,CLEAR}_OPTS ioctls * @deverror: True if the dax device has called our notify_failure entry * point, or if other "shutdown" conditions exist * @dax_devlist: Table of backing daxdevs (slot 0 is the mount primary) @@ -111,16 +126,23 @@ struct famfs_dax_devlist { */ struct famfs_fs_info { struct famfs_mount_opts mount_opts; + atomic64_t opts; bool deverror; struct famfs_dax_devlist *dax_devlist; struct rw_semaphore devlist_sem; }; -/* This stub will be replaced in a later commit - * Note: the opt parameter is intentionally unused, and will be used by - * the replacement function when that commit lands +/* + * famfs_opt_enabled() - is operation permission @opt enabled for this mount? + * + * @opt is a single FAMFS_OPT_* bit; returns true if that operation is + * permitted. The bitmap is read locklessly (updated via atomic RMW by the + * FAMFSIOC_{SET,CLEAR}_OPTS ioctls). */ -#define famfs_opt_enabled(fsi, opt) (fsi != 0) +static inline bool famfs_opt_enabled(struct famfs_fs_info *fsi, u64 opt) +{ + return !!(atomic64_read(&fsi->opts) & opt); +} int lookup_daxdev(const char *pathname, dev_t *devno); int famfs_devlist_alloc(struct famfs_fs_info *fsi); diff --git a/include/uapi/linux/famfs_ioctl.h b/include/uapi/linux/famfs_ioctl.h index 751d8b033c2e..efe6ef263975 100644 --- a/include/uapi/linux/famfs_ioctl.h +++ b/include/uapi/linux/famfs_ioctl.h @@ -100,6 +100,48 @@ struct famfs_ioc_daxdev { __u32 flags; }; +/* + * Mount-wide operation permissions, queried and modified via the + * FAMFSIOC_{GET,SET,CLEAR}_OPTS ioctls. A set bit means the operation is + * permitted; a clear bit means it is rejected with -EPERM. famfs denies most + * of these by default because the userspace log, not the kernel, is + * authoritative for a famfs instance. + */ +#define FAMFS_OPT_CREATE (1ULL << 0) /* create a regular file */ +#define FAMFS_OPT_MKDIR (1ULL << 1) /* mkdir */ +#define FAMFS_OPT_MKNOD (1ULL << 2) /* mknod a special file */ +#define FAMFS_OPT_SYMLINK (1ULL << 3) /* create a symlink */ +#define FAMFS_OPT_LINK (1ULL << 4) /* hard link */ +#define FAMFS_OPT_UNLINK (1ULL << 5) /* unlink a mapped file */ +#define FAMFS_OPT_RMDIR (1ULL << 6) /* rmdir */ +#define FAMFS_OPT_RENAME (1ULL << 7) /* rename */ +#define FAMFS_OPT_CHMOD (1ULL << 8) /* setattr ATTR_MODE */ +#define FAMFS_OPT_CHOWN (1ULL << 9) /* setattr ATTR_UID / ATTR_GID */ +#define FAMFS_OPT_TRUNCATE (1ULL << 10) /* setattr ATTR_SIZE (resize) */ +#define FAMFS_OPT_UTIMES (1ULL << 11) /* setattr ATTR_ATIME/ATTR_MTIME*/ +#define FAMFS_OPT_WRITE (1ULL << 12) /* write file data */ +#define FAMFS_OPT_XATTR (1ULL << 13) /* set/remove xattrs (reserved) */ +#define FAMFS_OPT_MAP_CREATE (1ULL << 14) /* attach an fmap (MAP_CREATE) */ + +#define FAMFS_OPT_ALL (FAMFS_OPT_CREATE | FAMFS_OPT_MKDIR | \ + FAMFS_OPT_MKNOD | FAMFS_OPT_SYMLINK | \ + FAMFS_OPT_LINK | FAMFS_OPT_UNLINK | \ + FAMFS_OPT_RMDIR | FAMFS_OPT_RENAME | \ + FAMFS_OPT_CHMOD | FAMFS_OPT_CHOWN | \ + FAMFS_OPT_TRUNCATE | FAMFS_OPT_UTIMES | \ + FAMFS_OPT_WRITE | FAMFS_OPT_XATTR | \ + FAMFS_OPT_MAP_CREATE) + +/** + * struct famfs_ioc_opts - operation-permission bitmap + * @opts: for GET, the current bitmap is returned here. For SET/CLEAR, the + * caller-supplied mask of bits to enable/disable on input, and the + * resulting bitmap on return. + */ +struct famfs_ioc_opts { + __u64 opts; +}; + #define FAMFSIOC_MAGIC 'u' /* famfs file ioctl opcodes */ @@ -111,5 +153,8 @@ struct famfs_ioc_daxdev { */ #define FAMFSIOC_MAP_CREATE _IOW(FAMFSIOC_MAGIC, 0x51, struct famfs_ioc_fmap_header) #define FAMFSIOC_DAXDEV_OPEN _IOW(FAMFSIOC_MAGIC, 0x52, struct famfs_ioc_daxdev) +#define FAMFSIOC_GET_OPTS _IOR(FAMFSIOC_MAGIC, 0x53, struct famfs_ioc_opts) +#define FAMFSIOC_SET_OPTS _IOWR(FAMFSIOC_MAGIC, 0x54, struct famfs_ioc_opts) +#define FAMFSIOC_CLEAR_OPTS _IOWR(FAMFSIOC_MAGIC, 0x55, struct famfs_ioc_opts) #endif /* FAMFS_IOCTL_H */ -- 2.53.0