lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <176169812533.1427080.16650603918169685068.stgit@frogsfrogsfrogs>
Date: Tue, 28 Oct 2025 17:59:02 -0700
From: "Darrick J. Wong" <djwong@...nel.org>
To: djwong@...nel.org, miklos@...redi.hu
Cc: joannelkoong@...il.com, bernd@...ernd.com, neal@...pa.dev,
 linux-ext4@...r.kernel.org, linux-fsdevel@...r.kernel.org
Subject: [PATCH 1/2] fuse: allow privileged mount helpers to pre-approve iomap
 usage

From: Darrick J. Wong <djwong@...nel.org>

For the upcoming safemount functionality in libfuse, we will create a
privileged "mount.safe" helper that starts the fuse server in a
completely unprivileged systemd container.  The mount helper will pass
the mount options and fds for /dev/fuse and any other files requested by
the fuse server into the container via a Unix socket.

Currently, the ability to turn on iomap for fuse depends on a module
parameter and the process that calls mount() having the CAP_SYS_RAWIO
capability.  However, the unprivilged fuse server might want to query
the /dev/fuse fd for iomap capabilities before mount or FUSE_INIT so
that it can get ready.

Similar to FUSE_DEV_SYNC_INIT, add a new bit for iomap that can be
squirreled away in file->private_data and an ioctl to set that bit.
That way the privileged mount helper can pass its iomap privilege to the
contained fuse server without the fuse server needing to have
CAP_SYS_RAWIO.

Signed-off-by: "Darrick J. Wong" <djwong@...nel.org>
---
 fs/fuse/fuse_dev_i.h      |   32 +++++++++++++++++++++++++++++---
 fs/fuse/fuse_i.h          |    9 +++++++++
 include/uapi/linux/fuse.h |    1 +
 fs/fuse/dev.c             |   11 +++++------
 fs/fuse/file_iomap.c      |   43 ++++++++++++++++++++++++++++++++++++++++++-
 fs/fuse/inode.c           |   18 ++++++++++++------
 6 files changed, 98 insertions(+), 16 deletions(-)


diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
index 6e8373f970409e..783ab1432c8691 100644
--- a/fs/fuse/fuse_dev_i.h
+++ b/fs/fuse/fuse_dev_i.h
@@ -39,8 +39,10 @@ struct fuse_copy_state {
 	} ring;
 };
 
-#define FUSE_DEV_SYNC_INIT ((struct fuse_dev *) 1)
-#define FUSE_DEV_PTR_MASK (~1UL)
+#define FUSE_DEV_SYNC_INIT	(1UL << 0)
+#define FUSE_DEV_INHERIT_IOMAP	(1UL << 1)
+#define FUSE_DEV_FLAGS_MASK	(FUSE_DEV_SYNC_INIT | FUSE_DEV_INHERIT_IOMAP)
+#define FUSE_DEV_PTR_MASK	(~FUSE_DEV_FLAGS_MASK)
 
 static inline struct fuse_dev *__fuse_get_dev(struct file *file)
 {
@@ -50,7 +52,31 @@ static inline struct fuse_dev *__fuse_get_dev(struct file *file)
 	 */
 	struct fuse_dev *fud = READ_ONCE(file->private_data);
 
-	return (typeof(fud)) ((unsigned long) fud & FUSE_DEV_PTR_MASK);
+	return (typeof(fud)) ((uintptr_t)fud & FUSE_DEV_PTR_MASK);
+}
+
+static inline struct fuse_dev *__fuse_get_dev_and_flags(struct file *file,
+							uintptr_t *flagsp)
+{
+	/*
+	 * Lockless access is OK, because file->private data is set
+	 * once during mount and is valid until the file is released.
+	 */
+	struct fuse_dev *fud = READ_ONCE(file->private_data);
+
+	*flagsp = ((uintptr_t)fud) & FUSE_DEV_FLAGS_MASK;
+	return (typeof(fud)) ((uintptr_t) fud & FUSE_DEV_PTR_MASK);
+}
+
+static inline int __fuse_set_dev_flags(struct file *file, uintptr_t flag)
+{
+	uintptr_t old_flags = 0;
+
+	if (__fuse_get_dev_and_flags(file, &old_flags))
+		return -EINVAL;
+
+	WRITE_ONCE(file->private_data, (struct fuse_dev *)(old_flags | flag));
+	return 0;
 }
 
 struct fuse_dev *fuse_get_dev(struct file *file);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 0f49edaf951a6d..f45e59d16d0ebc 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -990,6 +990,13 @@ struct fuse_conn {
 	/* Enable fs/iomap for file operations */
 	unsigned int iomap:1;
 
+	/*
+	 * Are filesystems using this connection allowed to use iomap?  This is
+	 * determined by the privilege level of the process that initiated the
+	 * mount() call.
+	 */
+	unsigned int may_iomap:1;
+
 	/* Use io_uring for communication */
 	unsigned int io_uring;
 
@@ -1847,6 +1854,7 @@ void fuse_iomap_release_truncate(struct inode *inode);
 void fuse_iomap_copied_file_range(struct inode *inode, loff_t offset,
 				  size_t written);
 
+int fuse_dev_ioctl_add_iomap(struct file *file);
 int fuse_dev_ioctl_iomap_support(struct file *file,
 				 struct fuse_iomap_support __user *argp);
 int fuse_iomap_dev_inval(struct fuse_conn *fc,
@@ -1898,6 +1906,7 @@ int fuse_iomap_inval(struct fuse_conn *fc,
 # define fuse_iomap_open_truncate(...)		((void)0)
 # define fuse_iomap_release_truncate(...)	((void)0)
 # define fuse_iomap_copied_file_range(...)	((void)0)
+# define fuse_dev_ioctl_add_iomap(...)		(-EOPNOTSUPP)
 # define fuse_dev_ioctl_iomap_support(...)	(-EOPNOTSUPP)
 # define fuse_iomap_dev_inval(...)		(-ENOSYS)
 # define fuse_iomap_fadvise			NULL
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 437d740cf23474..daf72e46120c24 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -1204,6 +1204,7 @@ struct fuse_iomap_support {
 					     struct fuse_backing_map)
 #define FUSE_DEV_IOC_BACKING_CLOSE	_IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
 #define FUSE_DEV_IOC_SYNC_INIT		_IO(FUSE_DEV_IOC_MAGIC, 3)
+#define FUSE_DEV_IOC_ADD_IOMAP		_IO(FUSE_DEV_IOC_MAGIC, 99)
 #define FUSE_DEV_IOC_IOMAP_SUPPORT	_IOR(FUSE_DEV_IOC_MAGIC, 99, \
 					     struct fuse_iomap_support)
 
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 60f6d1f9819804..4dfad6c33fac8f 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1558,7 +1558,7 @@ struct fuse_dev *fuse_get_dev(struct file *file)
 		return fud;
 
 	err = wait_event_interruptible(fuse_dev_waitq,
-				       READ_ONCE(file->private_data) != FUSE_DEV_SYNC_INIT);
+				       __fuse_get_dev(file) != NULL);
 	if (err)
 		return ERR_PTR(err);
 
@@ -2761,13 +2761,10 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
 
 static long fuse_dev_ioctl_sync_init(struct file *file)
 {
-	int err = -EINVAL;
+	int err;
 
 	mutex_lock(&fuse_mutex);
-	if (!__fuse_get_dev(file)) {
-		WRITE_ONCE(file->private_data, FUSE_DEV_SYNC_INIT);
-		err = 0;
-	}
+	err = __fuse_set_dev_flags(file, FUSE_DEV_SYNC_INIT);
 	mutex_unlock(&fuse_mutex);
 	return err;
 }
@@ -2792,6 +2789,8 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
 
 	case FUSE_DEV_IOC_IOMAP_SUPPORT:
 		return fuse_dev_ioctl_iomap_support(file, argp);
+	case FUSE_DEV_IOC_ADD_IOMAP:
+		return fuse_dev_ioctl_add_iomap(file);
 
 	default:
 		return -ENOTTY;
diff --git a/fs/fuse/file_iomap.c b/fs/fuse/file_iomap.c
index 9d77f4db32d7fd..08e7e4f924a65a 100644
--- a/fs/fuse/file_iomap.c
+++ b/fs/fuse/file_iomap.c
@@ -10,6 +10,7 @@
 #include <linux/fadvise.h>
 #include <linux/swap.h>
 #include "fuse_i.h"
+#include "fuse_dev_i.h"
 #include "fuse_trace.h"
 #include "iomap_i.h"
 
@@ -115,6 +116,12 @@ bool fuse_iomap_enabled(void)
 	return enable_iomap && has_capability_noaudit(current, CAP_SYS_RAWIO);
 }
 
+static inline bool fuse_iomap_may_enable(void)
+{
+	/* Same as above, but this time we log the denial in audit log */
+	return enable_iomap && capable(CAP_SYS_RAWIO);
+}
+
 /* Convert IOMAP_* mapping types to FUSE_IOMAP_TYPE_* */
 #define XMAP(word) \
 	case IOMAP_##word: \
@@ -2437,12 +2444,46 @@ fuse_iomap_fallocate(
 	return 0;
 }
 
+int fuse_dev_ioctl_add_iomap(struct file *file)
+{
+	uintptr_t flags = 0;
+	struct fuse_dev *fud;
+	int ret = 0;
+
+	mutex_lock(&fuse_mutex);
+	fud = __fuse_get_dev_and_flags(file, &flags);
+	if (fud) {
+		if (!fud->fc->may_iomap && !fuse_iomap_may_enable()) {
+			ret = -EPERM;
+			goto out_unlock;
+		}
+
+		fud->fc->may_iomap = 1;
+		goto out_unlock;
+	}
+
+	if (!(flags & FUSE_DEV_INHERIT_IOMAP) && !fuse_iomap_may_enable()) {
+		ret = -EPERM;
+		goto out_unlock;
+	}
+
+	ret = __fuse_set_dev_flags(file, FUSE_DEV_INHERIT_IOMAP);
+
+out_unlock:
+	mutex_unlock(&fuse_mutex);
+	return ret;
+}
+
 int fuse_dev_ioctl_iomap_support(struct file *file,
 				 struct fuse_iomap_support __user *argp)
 {
 	struct fuse_iomap_support ios = { };
+	uintptr_t flags = 0;
+	struct fuse_dev *fud = __fuse_get_dev_and_flags(file, &flags);
 
-	if (fuse_iomap_enabled())
+	if ((!fud && (flags & FUSE_DEV_INHERIT_IOMAP)) ||
+	    (fud && fud->fc->may_iomap) ||
+	    fuse_iomap_enabled())
 		ios.flags = FUSE_IOMAP_SUPPORT_FILEIO |
 			    FUSE_IOMAP_SUPPORT_ATOMIC;
 
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index c82c6a29904396..2dc5d868140245 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1043,6 +1043,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
 	fc->name_max = FUSE_NAME_LOW_MAX;
 	fc->timeout.req_timeout = 0;
 	fc->root_nodeid = FUSE_ROOT_ID;
+	fc->may_iomap = fuse_iomap_enabled();
 
 	if (IS_ENABLED(CONFIG_FUSE_BACKING))
 		fuse_backing_files_init(fc);
@@ -1575,7 +1576,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
 			if (flags & FUSE_REQUEST_TIMEOUT)
 				timeout = arg->request_timeout;
 
-			if ((flags & FUSE_IOMAP) && fuse_iomap_enabled()) {
+			if ((flags & FUSE_IOMAP) && fc->may_iomap) {
 				fc->iomap = 1;
 				pr_warn(
  "EXPERIMENTAL iomap feature enabled.  Use at your own risk!");
@@ -1662,7 +1663,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 	 */
 	if (fuse_uring_enabled())
 		flags |= FUSE_OVER_IO_URING;
-	if (fuse_iomap_enabled())
+	if (fm->fc->may_iomap)
 		flags |= FUSE_IOMAP;
 
 	ia->in.flags = flags;
@@ -2046,11 +2047,16 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 
 	mutex_lock(&fuse_mutex);
 	err = -EINVAL;
-	if (ctx->fudptr && *ctx->fudptr) {
-		if (*ctx->fudptr == FUSE_DEV_SYNC_INIT)
-			fc->sync_init = 1;
-		else
+	if (ctx->fudptr) {
+		uintptr_t raw = (uintptr_t)(*ctx->fudptr);
+		uintptr_t flags = raw & FUSE_DEV_FLAGS_MASK;
+
+		if (raw & FUSE_DEV_PTR_MASK)
 			goto err_unlock;
+		if (flags & FUSE_DEV_SYNC_INIT)
+			fc->sync_init = 1;
+		if (flags & FUSE_DEV_INHERIT_IOMAP)
+			fc->may_iomap = 1;
 	}
 
 	err = fuse_ctl_add_conn(fc);


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ