[PATCH V12 10/12] famfs: Add runtime operation-permission (opts) framework
John Groves <[email protected]> Mon, 3 Aug 2026 02:29:57 +0000
| Newsgroups | dev.linux.lists.fuse-devel,dev.linux.lists.nvdimm,org.kernel.vger.linux-cxl,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <0100019fc574dabe-572d99fc-3bb0-421a-afec-05432de2a757-000000@email.amazonses.com> |
From: John Groves <[email protected]>=0D=0A=0D=0Afamfs denies most namespac= e, attribute and data operations by default=0D=0Abecause the userspace lo= g, not the kernel, is authoritative for a famfs=0D=0Ainstance. Earlier co= mmits already guard each such operation with a=0D=0Afamfs_opt_enabled(fsi= , FAMFS_OPT_x) check backed by a permissive stub. This=0D=0Acommit define= s the permission bitmap and makes those checks live.=0D=0A=0D=0AAdd:=0D=0A= - FAMFS_OPT_* (uapi): a u64 permission bitmap, one bit per gated operati= on=0D=0A (create, mkdir, mknod, symlink, link, unlink, rmdir, rename, t= he four=0D=0A setattr components, data write, and MAP_CREATE), plus FAM= FS_OPT_ALL. The=0D=0A FAMFS_OPT_XATTR bit is reserved - famfs has no xa= ttr ops yet.=0D=0A - fsi->opts: a per-mount atomic64 bitmap initialized t= o FAMFS_OPT_DEFAULT,=0D=0A which sets famfs's default policy: create, m= kdir, chmod, chown, utimes,=0D=0A write and MAP_CREATE are permitted; u= nlink of mapped files, link,=0D=0A symlink, mknod, rmdir, rename and tr= uncate are denied.=0D=0A - the real famfs_opt_enabled() (replacing the st= ub), so every planted gate=0D=0A now consults fsi->opts.=0D=0A - FAMFSI= OC_{GET,SET,CLEAR}_OPTS: read the bitmap, or enable/disable the=0D=0A b= its set in a caller-supplied mask, returning the resulting bitmap.=0D=0A = SET/CLEAR require CAP_SYS_ADMIN and reject unknown bits with -EINVAL;=0D= =0A the bitmap is updated with atomic RMW so the checks stay lockless.=0D= =0ASigned-off-by: John Groves <[email protected]>=0D=0A---=0D=0A fs/famfs/f= amfs_file.c | 55 ++++++++++++++++++++++++++++++++=0D=0A fs/fam= fs/famfs_inode.c | 1 +=0D=0A fs/famfs/famfs_internal.h = | 30 ++++++++++++++---=0D=0A include/uapi/linux/famfs_ioctl.h | 45 ++++++= ++++++++++++++++++++=0D=0A 4 files changed, 127 insertions(+), 4 deletion= s(-)=0D=0A=0D=0Adiff --git a/fs/famfs/famfs_file.c b/fs/famfs/famfs_file.= c=0D=0Aindex e11a55ecf8d7..abf049b32a4b 100644=0D=0A--- a/fs/famfs/famfs_= file.c=0D=0A+++ b/fs/famfs/famfs_file.c=0D=0A@@ -357,6 +357,49 @@ famfs_d= axdev_open(struct file *file, void __user *arg)=0D=0A =09return rc;=0D=0A= }=0D=0A=20=0D=0A+/**=0D=0A+ * famfs_get_opts() - FAMFSIOC_GET_OPTS: retu= rn the permission bitmap=0D=0A+ */=0D=0A+static long famfs_get_opts(struc= t famfs_fs_info *fsi, void __user *arg)=0D=0A+{=0D=0A+=09struct famfs_ioc= _opts o =3D { .opts =3D atomic64_read(&fsi->opts) };=0D=0A+=0D=0A+=09if (= copy_to_user(arg, &o, sizeof(o)))=0D=0A+=09=09return -EFAULT;=0D=0A+=0D=0A= +=09return 0;=0D=0A+}=0D=0A+=0D=0A+/*=0D=0A+ * famfs_modify_opts() - FAMF= SIOC_SET_OPTS / FAMFSIOC_CLEAR_OPTS=0D=0A+ * @set: true to enable (OR in)= the requested bits, false to disable (mask out)=0D=0A+ *=0D=0A+ * The ca= ller supplies a mask of FAMFS_OPT_* bits; the resulting bitmap is=0D=0A+ = * returned. Requires CAP_SYS_ADMIN since it changes mount-wide policy.=0D= =0A+ */=0D=0A+static long famfs_modify_opts(struct famfs_fs_info *fsi, vo= id __user *arg,=0D=0A+=09=09=09 bool set)=0D=0A+{=0D=0A+=09struct fa= mfs_ioc_opts o;=0D=0A+=0D=0A+=09if (!capable(CAP_SYS_ADMIN))=0D=0A+=09=09= return -EPERM;=0D=0A+=09if (copy_from_user(&o, arg, sizeof(o)))=0D=0A+=09= =09return -EFAULT;=0D=0A+=09if (o.opts & ~FAMFS_OPT_ALL)=0D=0A+=09=09retu= rn -EINVAL;=0D=0A+=0D=0A+=09if (set)=0D=0A+=09=09o.opts =3D atomic64_fetc= h_or(o.opts, &fsi->opts) | o.opts;=0D=0A+=09else=0D=0A+=09=09o.opts =3D a= tomic64_fetch_and(~o.opts, &fsi->opts) & ~o.opts;=0D=0A+=0D=0A+=09if (cop= y_to_user(arg, &o, sizeof(o)))=0D=0A+=09=09return -EFAULT;=0D=0A+=0D=0A+=09= return 0;=0D=0A+}=0D=0A+=0D=0A /**=0D=0A * famfs_file_ioctl() - Top-leve= l famfs file ioctl handler=0D=0A * @file: the file=0D=0A@@ -378,6 +421,1= 8 @@ famfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long = arg)=0D=0A =09=09rc =3D 0;=0D=0A =09=09break;=0D=0A=20=0D=0A+=09case FAMF= SIOC_GET_OPTS:=0D=0A+=09=09rc =3D famfs_get_opts(fsi, (void __user *)arg)= ;=0D=0A+=09=09break;=0D=0A+=0D=0A+=09case FAMFSIOC_SET_OPTS:=0D=0A+=09=09= rc =3D famfs_modify_opts(fsi, (void __user *)arg, true);=0D=0A+=09=09brea= k;=0D=0A+=0D=0A+=09case FAMFSIOC_CLEAR_OPTS:=0D=0A+=09=09rc =3D famfs_mod= ify_opts(fsi, (void __user *)arg, false);=0D=0A+=09=09break;=0D=0A+=0D=0A= =09case FAMFSIOC_DAXDEV_OPEN:=0D=0A =09=09rc =3D famfs_daxdev_open(file,= (void __user *)arg);=0D=0A =09=09break;=0D=0Adiff --git a/fs/famfs/famfs= _inode.c b/fs/famfs/famfs_inode.c=0D=0Aindex a6c3b4574e69..6cbd7d657fd8 1= 00644=0D=0A--- a/fs/famfs/famfs_inode.c=0D=0A+++ b/fs/famfs/famfs_inode.c= =0D=0A@@ -717,6 +717,7 @@ static int famfs_init_fs_context(struct fs_cont= ext *fc)=0D=0A =09=09return -ENOMEM;=0D=0A=20=0D=0A =09init_rwsem(&fsi->d= evlist_sem);=0D=0A+=09atomic64_set(&fsi->opts, FAMFS_OPT_DEFAULT);=0D=0A = =09fsi->mount_opts.mode =3D FAMFS_DEFAULT_MODE;=0D=0A =09fc->s_fs_info = =3D fsi;=0D=0A =09fc->ops =3D &famfs_context_ops;=0D=0A= diff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h=0D=0Ai= ndex b5f9c8d0349f..26873162b4a0 100644=0D=0A--- a/fs/famfs/famfs_internal= =2Eh=0D=0A+++ b/fs/famfs/famfs_internal.h=0D=0A@@ -12,11 +12,24 @@=0D=0A = #define FAMFS_INTERNAL_H=0D=0A=20=0D=0A #include <linux/rwsem.h>=0D=0A+#i= nclude <linux/atomic.h>=0D=0A #include <linux/bits.h>=0D=0A #include <lin= ux/build_bug.h>=0D=0A=20=0D=0A #include <linux/famfs_ioctl.h>=0D=0A=20=0D= =0A+/*=0D=0A+ * Default operation-permission bitmap (see FAMFS_OPT_* in t= he uapi header).=0D=0A+ * This preserves famfs's historical behavior: fil= e/dir creation, the fmap=0D=0A+ * ioctl, data writes, and the non-resize = setattr components are permitted;=0D=0A+ * unlink of mapped files, link, = symlink, mknod, rmdir, rename and truncate=0D=0A+ * are denied until enab= led via FAMFSIOC_SET_OPTS.=0D=0A+ */=0D=0A+#define FAMFS_OPT_DEFAULT=09(F= AMFS_OPT_CREATE | FAMFS_OPT_MKDIR | \=0D=0A+=09=09=09=09 FAMFS_OPT_CHMOD = | FAMFS_OPT_CHOWN | \=0D=0A+=09=09=09=09 FAMFS_OPT_UTIMES | FAMFS_OPT_WRI= TE | \=0D=0A+=09=09=09=09 FAMFS_OPT_MAP_CREATE)=0D=0A+=0D=0A extern const= struct file_operations famfs_file_operations;=0D=0A=20=0D=0A /*=0D=0A@@ = -104,6 +117,8 @@ struct famfs_dax_devlist {=0D=0A * @famfs_fs_info=0D=0A= *=0D=0A * @mount_opts: The mount options=0D=0A+ * @opts: Opera= tion-permission bitmap (FAMFS_OPT_*), adjusted at runtime=0D=0A+ * = via the FAMFSIOC_{GET,SET,CLEAR}_OPTS ioctls=0D=0A * @deverror: = True if the dax device has called our notify_failure entry=0D=0A * = point, or if other "shutdown" conditions exist=0D=0A * @dax_= devlist: Table of backing daxdevs (slot 0 is the mount primary)=0D=0A@@ -= 111,16 +126,23 @@ struct famfs_dax_devlist {=0D=0A */=0D=0A struct famfs= _fs_info {=0D=0A =09struct famfs_mount_opts mount_opts;=0D=0A+=09atomic= 64_t opts;=0D=0A =09bool deverror;=0D= =0A =09struct famfs_dax_devlist *dax_devlist;=0D=0A =09struct rw_semaphor= e devlist_sem;=0D=0A };=0D=0A=20=0D=0A-/* This stub will be replace= d in a later commit=20=0D=0A- * Note: the opt parameter is intentionally = unused, and will be used by=0D=0A- * the replacement function when that c= ommit lands=0D=0A+/*=0D=0A+ * famfs_opt_enabled() - is operation permissi= on @opt enabled for this mount=3F=0D=0A+ *=0D=0A+ * @opt is a single FAMF= S_OPT_* bit; returns true if that operation is=0D=0A+ * permitted. The bi= tmap is read locklessly (updated via atomic RMW by the=0D=0A+ * FAMFSIOC_= {SET,CLEAR}_OPTS ioctls).=0D=0A */=0D=0A-#define famfs_opt_enabled(fsi, = opt) (fsi !=3D 0)=0D=0A+static inline bool famfs_opt_enabled(struct famfs= _fs_info *fsi, u64 opt)=0D=0A+{=0D=0A+=09return !!(atomic64_read(&fsi->op= ts) & opt);=0D=0A+}=0D=0A=20=0D=0A int lookup_daxdev(const char *pathname= , dev_t *devno);=0D=0A int famfs_devlist_alloc(struct famfs_fs_info *fsi)= ;=0D=0Adiff --git a/include/uapi/linux/famfs_ioctl.h b/include/uapi/linux= /famfs_ioctl.h=0D=0Aindex 751d8b033c2e..efe6ef263975 100644=0D=0A--- a/in= clude/uapi/linux/famfs_ioctl.h=0D=0A+++ b/include/uapi/linux/famfs_ioctl.= h=0D=0A@@ -100,6 +100,48 @@ struct famfs_ioc_daxdev {=0D=0A =09__u32 flag= s;=0D=0A };=0D=0A=20=0D=0A+/*=0D=0A+ * Mount-wide operation permissions, = queried and modified via the=0D=0A+ * FAMFSIOC_{GET,SET,CLEAR}_OPTS ioctl= s. A set bit means the operation is=0D=0A+ * permitted; a clear bit means= it is rejected with -EPERM. famfs denies most=0D=0A+ * of these by defau= lt because the userspace log, not the kernel, is=0D=0A+ * authoritative f= or a famfs instance.=0D=0A+ */=0D=0A+#define FAMFS_OPT_CREATE=09(1ULL << = 0) /* create a regular file */=0D=0A+#define FAMFS_OPT_MKDIR=09=09= (1ULL << 1) /* mkdir */=0D=0A+#define FAMFS_OPT_M= KNOD=09=09(1ULL << 2) /* mknod a special file */=0D=0A+#define F= AMFS_OPT_SYMLINK=09(1ULL << 3) /* create a symlink */=0D=0A+= #define FAMFS_OPT_LINK=09=09(1ULL << 4) /* hard link = */=0D=0A+#define FAMFS_OPT_UNLINK=09(1ULL << 5) /* unlink a mapped file = */=0D=0A+#define FAMFS_OPT_RMDIR=09=09(1ULL << 6) /* rmdir = */=0D=0A+#define FAMFS_OPT_RENAME=09(1ULL << 7) /* ren= ame */=0D=0A+#define FAMFS_OPT_CHMOD=09=09(1ULL << = 8) /* setattr ATTR_MODE */=0D=0A+#define FAMFS_OPT_CHOWN=09=09= (1ULL << 9) /* setattr ATTR_UID / ATTR_GID */=0D=0A+#define FAMFS_OPT_T= RUNCATE=09(1ULL << 10) /* setattr ATTR_SIZE (resize) */=0D=0A+#define F= AMFS_OPT_UTIMES=09(1ULL << 11) /* setattr ATTR_ATIME/ATTR_MTIME*/=0D=0A+#= define FAMFS_OPT_WRITE=09=09(1ULL << 12) /* write file data = */=0D=0A+#define FAMFS_OPT_XATTR=09=09(1ULL << 13) /* set/remove xattrs (= reserved) */=0D=0A+#define FAMFS_OPT_MAP_CREATE=09(1ULL << 14) /* attach = an fmap (MAP_CREATE) */=0D=0A+=0D=0A+#define FAMFS_OPT_ALL=09=09(FAMFS_O= PT_CREATE | FAMFS_OPT_MKDIR | \=0D=0A+=09=09=09=09 FAMFS_OPT_MKNOD | FAMF= S_OPT_SYMLINK | \=0D=0A+=09=09=09=09 FAMFS_OPT_LINK | FAMFS_OPT_UNLINK | = \=0D=0A+=09=09=09=09 FAMFS_OPT_RMDIR | FAMFS_OPT_RENAME | \=0D=0A+=09=09=09= =09 FAMFS_OPT_CHMOD | FAMFS_OPT_CHOWN | \=0D=0A+=09=09=09=09 FAMFS_OPT_TR= UNCATE | FAMFS_OPT_UTIMES | \=0D=0A+=09=09=09=09 FAMFS_OPT_WRITE | FAMFS_= OPT_XATTR | \=0D=0A+=09=09=09=09 FAMFS_OPT_MAP_CREATE)=0D=0A+=0D=0A+/**=0D= =0A+ * struct famfs_ioc_opts - operation-permission bitmap=0D=0A+ * @opts= : for GET, the current bitmap is returned here. For SET/CLEAR, the=0D=0A+= * caller-supplied mask of bits to enable/disable on input, and th= e=0D=0A+ * resulting bitmap on return.=0D=0A+ */=0D=0A+struct famf= s_ioc_opts {=0D=0A+=09__u64 opts;=0D=0A+};=0D=0A+=0D=0A #define FAMFSIOC_= MAGIC 'u'=0D=0A=20=0D=0A /* famfs file ioctl opcodes */=0D=0A@@ -111,5 +1= 53,8 @@ struct famfs_ioc_daxdev {=0D=0A */=0D=0A #define FAMFSIOC_MAP_CR= EATE _IOW(FAMFSIOC_MAGIC, 0x51, struct famfs_ioc_fmap_header)=0D=0A #= define FAMFSIOC_DAXDEV_OPEN _IOW(FAMFSIOC_MAGIC, 0x52, struct famfs_io= c_daxdev)=0D=0A+#define FAMFSIOC_GET_OPTS _IOR(FAMFSIOC_MAGIC, 0x53= , struct famfs_ioc_opts)=0D=0A+#define FAMFSIOC_SET_OPTS _IOWR(FAMFS= IOC_MAGIC, 0x54, struct famfs_ioc_opts)=0D=0A+#define FAMFSIOC_CLEAR_OPTS= _IOWR(FAMFSIOC_MAGIC, 0x55, struct famfs_ioc_opts)=0D=0A=20=0D=0A #en= dif /* FAMFS_IOCTL_H */=0D=0A--=20=0D=0A2.53.0=0D=0A=0D=0A