FILESYSTEM IN USERSPACE (FUSE) development
 help / color / mirror / Atom feed
* [PATCH 00/10] libfuse: Add mount service safety checks and tests
@ 2026-09-24 22:23 Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline() Bernd Schubert via B4 Relay
                   ` (9 more replies)
  0 siblings, 10 replies; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

That was noticed by AI on comparison to systemd storage provider
and fuse service mount tests were missing as well.

To: fuse-devel@lists.linux.dev
Cc: Darrick J. Wong <djwong@kernel.org>

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
Bernd Schubert (10):
      mount_service: move the command line check into arg_in_cmdline()
      mount_service: open only paths named on the command line
      mount_service: refuse OPEN after the MNTPT request
      util: give fuservicemount3 an absolute build-tree runpath
      mount.fuse: free the options on the service mount return path
      example/single_file: take no sector size from a regular backing file
      test: check which files fuservicemount3 opens for the server
      test: check what fuservicemount3 refuses
      test: mount the service examples through fuservicemount3
      test: run mkfs.ext4 through the service examples

 .gitignore                              |   1 +
 doc/fuservicemount3.8                   |   5 +
 example/service_ll.c                    |   5 +
 example/single_file.c                   |   3 +-
 include/fuse_service.h                  |   6 +-
 test/cases/lib/service-example.sh       |  98 ++++++++++++++
 test/cases/lib/service.sh               |  93 +++++++++++++
 test/cases/lib/socket_activate.py       |  42 ++++++
 test/cases/mount/service-caps.sh        |  20 +++
 test/cases/mount/service-check.sh       |  38 ++++++
 test/cases/mount/service-hl-mkfs.sh     |   8 ++
 test/cases/mount/service-hl.sh          |   7 +
 test/cases/mount/service-ll-mkfs.sh     |   8 ++
 test/cases/mount/service-ll.sh          |   7 +
 test/cases/mount/service-mount-fuse.sh  |  31 +++++
 test/cases/mount/service-mountpoint.sh  |  36 +++++
 test/cases/mount/service-nonroot.sh     |  54 ++++++++
 test/cases/mount/service-null.sh        |  33 +++++
 test/cases/mount/service-open-bound.sh  |  46 +++++++
 test/cases/mount/service-server-exit.sh |  22 +++
 test/meson.build                        |   6 +
 test/test_service.c                     | 232 ++++++++++++++++++++++++++++++++
 util/meson.build                        |   2 +
 util/mount.fuse.c                       |  42 +++---
 util/mount_service.c                    |  58 +++++---
 25 files changed, 869 insertions(+), 34 deletions(-)
---
base-commit: e001ea32a977933236bcd928692c2c022594a41d
change-id: 20260924-mount-service-bound-open-739b16236bfa

Best regards,
--  
Bernd Schubert <bernd@bsbernd.com>



^ permalink raw reply	[flat|nested] 23+ messages in thread

* [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline()
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 21:40   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 02/10] mount_service: open only paths named on the command line Bernd Schubert via B4 Relay
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

mount_service_handle_mountpoint_cmd() compared the mount point with
each argument of fuservicemount3 in its own loop. The next patch makes
the same check for OPEN requests, so the loop moves into
arg_in_cmdline(). There is no change in behaviour.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 util/mount_service.c | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/util/mount_service.c b/util/mount_service.c
index 0b3266309a8a..7549e0b5024f 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -758,6 +758,18 @@ static int prepare_bdev(const struct mount_service *mo,
 	return 0;
 }
 
+static bool arg_in_cmdline(int argc, const char * const argv[],
+			   const char *value)
+{
+	int i;
+
+	for (i = 0; i < argc; i++)
+		if (!strcmp(argv[i], value))
+			return true;
+
+	return false;
+}
+
 static int mount_service_open_path(const struct mount_service *mo,
 				   mode_t expected_fmt,
 				   struct fuse_service_packet *p, size_t psz)
@@ -1248,8 +1260,6 @@ static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
 			container_of(p, struct fuse_service_mountpoint_command, p);
 	char *mntpt;
 	mode_t expected_fmt;
-	bool foundit = false;
-	int i;
 
 	if (psz < sizeof_fuse_service_mountpoint_command(1)) {
 		fprintf(stderr, "%s: mount point command too small\n",
@@ -1289,13 +1299,7 @@ static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
 	}
 
 	/* Mountpoint must be mentioned in the caller's argument list */
-	for (i = 0; i < argc; i++) {
-		if (!strcmp(argv[i], oc->value)) {
-			foundit = true;
-			break;
-		}
-	}
-	if (!foundit) {
+	if (!arg_in_cmdline(argc, argv, oc->value)) {
 		fprintf(stderr, "%s: mount point must be in command line arguments\n",
 			mo->msgtag);
 		return mount_service_send_reply(mo, EINVAL);

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 02/10] mount_service: open only paths named on the command line
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline() Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:05   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request Bernd Schubert via B4 Relay
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

In a service mount, the fuse server runs as a systemd service in a
sandbox that has no access to the user's files. The user runs mount,
which starts fuservicemount3, a setuid-root helper. The fuse server
sends requests to the helper over a socket. With an OPEN request, the
server asks the helper to open its backing file, for example the disk
image named on the mount command line. The helper opens the file with
the user's credentials and passes the file descriptor to the server.
fusermount3 opens nothing for the fuse server except /dev/fuse; the
server runs as the user and opens its own files.

The helper opened any path the server sent. An attacker who controlled
the server could use this to read every file the user can read, for
example ~/.ssh/id_ed25519, and the sandbox did not prevent it. The
helper now compares the requested path with the arguments it was
started with. For "mount -t fuse.service_ll /srv/disk.img /mnt", these
include "/srv/disk.img" and "/mnt". The helper opens the path only if
the string is equal to one of these arguments, so the server can open
only a file that the user named when mounting. The helper already made
the same check for the mount point.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 doc/fuservicemount3.8  |  4 ++++
 include/fuse_service.h |  5 ++++-
 util/mount_service.c   | 29 ++++++++++++++++++++++-------
 3 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
index aa2167cb4872..06755d7c3c4e 100644
--- a/doc/fuservicemount3.8
+++ b/doc/fuservicemount3.8
@@ -19,6 +19,10 @@ Mount a filesystem using a FUSE server that runs as a socket service.
 These servers can be contained using the platform's service management
 framework.
 
+The FUSE server may ask fuservicemount3 to open files on its behalf.
+fuservicemount3 opens only paths that appear verbatim on its command line
+and refuses any other request with EPERM.
+
 The second form checks if there is a FUSE service available for the given
 filesystem type.
 .SH "AUTHORS"
diff --git a/include/fuse_service.h b/include/fuse_service.h
index d6aedea8f0f8..3954f62aa2b8 100644
--- a/include/fuse_service.h
+++ b/include/fuse_service.h
@@ -139,6 +139,8 @@ int fuse_service_parse_cmdline_opts(struct fuse_args *args,
 
 /**
  * Ask the mount.service helper to open a file on behalf of the fuse server.
+ * The helper refuses a path that is not verbatim on the mount command line;
+ * fuse_service_receive_file() then reports -EPERM.
  *
  * @param sf service context
  * @param path the path to file
@@ -153,7 +155,8 @@ int fuse_service_request_file(const struct fuse_service *sf, const char *path,
 
 /**
  * Ask the mount.service helper to open a block device on behalf of the fuse
- * server.
+ * server.  The path must be verbatim on the mount command line, as for a
+ * file request.
  *
  * @param sf service context
  * @param path the path to file
diff --git a/util/mount_service.c b/util/mount_service.c
index 7549e0b5024f..835d3d94aa4a 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -772,7 +772,8 @@ static bool arg_in_cmdline(int argc, const char * const argv[],
 
 static int mount_service_open_path(const struct mount_service *mo,
 				   mode_t expected_fmt,
-				   struct fuse_service_packet *p, size_t psz)
+				   struct fuse_service_packet *p, size_t psz,
+				   int argc, const char * const argv[])
 {
 	const struct fuse_service_open_command *oc =
 			container_of(p, struct fuse_service_open_command, p);
@@ -800,6 +801,16 @@ static int mount_service_open_path(const struct mount_service *mo,
 		return mount_service_send_file_error(mo, EINVAL, oc->path);
 	}
 
+	/*
+	 * The file is opened outside the service sandbox, so only hand out
+	 * what the user named.
+	 */
+	if (!arg_in_cmdline(argc, argv, oc->path)) {
+		fprintf(stderr, "%s: %s: file must be in command line arguments\n",
+			mo->msgtag, oc->path);
+		return mount_service_send_file_error(mo, EPERM, oc->path);
+	}
+
 	open_flags = ntohl(oc->open_flags) | O_CLOEXEC;
 	drop_privs();
 	fd = open(oc->path, open_flags, ntohl(oc->create_mode));
@@ -834,16 +845,18 @@ static int mount_service_open_path(const struct mount_service *mo,
 
 static int mount_service_handle_open_cmd(const struct mount_service *mo,
 					 struct fuse_service_packet *p,
-					 size_t psz)
+					 size_t psz, int argc,
+					 const char * const argv[])
 {
-	return mount_service_open_path(mo, 0, p, psz);
+	return mount_service_open_path(mo, 0, p, psz, argc, argv);
 }
 
 static int mount_service_handle_open_bdev_cmd(const struct mount_service *mo,
 					      struct fuse_service_packet *p,
-					      size_t psz)
+					      size_t psz, int argc,
+					      const char * const argv[])
 {
-	return mount_service_open_path(mo, S_IFBLK, p, psz);
+	return mount_service_open_path(mo, S_IFBLK, p, psz, argc, argv);
 }
 
 #ifdef HAVE_NEW_MOUNT_API
@@ -1838,10 +1851,12 @@ int mount_service_main(int argc, char *argv[])
 
 		switch (ntohl(p->magic)) {
 		case FUSE_SERVICE_OPEN_CMD:
-			ret = mount_service_handle_open_cmd(&mo, p, sz);
+			ret = mount_service_handle_open_cmd(&mo, p, sz,
+					argc, (const char * const *)argv);
 			break;
 		case FUSE_SERVICE_OPEN_BDEV_CMD:
-			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
+			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz,
+					argc, (const char * const *)argv);
 			break;
 		case FUSE_SERVICE_FSOPEN_CMD:
 			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline() Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 02/10] mount_service: open only paths named on the command line Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:07   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 04/10] util: give fuservicemount3 an absolute build-tree runpath Bernd Schubert via B4 Relay
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

The fuse server sends fuservicemount3 a series of requests: OPEN for
its backing file, MNTPT to name the mount point, then MOUNT. The server
chooses the order. For a directory mount point, attach_to_mountpoint()
changes the working directory of the helper to the mount point. The
helper then mounts on ".", so a rename of the path cannot redirect the
mount.

A relative path in an OPEN request after MNTPT resolved inside the
mount point. For "fuservicemount3 disk.img /mnt -t fuse.service_ll",
an OPEN of "disk.img" matched the command line argument, but the helper
opened /mnt/disk.img, not disk.img in the user's working directory. The
helper now refuses OPEN once the mount point is set. No in-tree server
sends OPEN after MNTPT.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 doc/fuservicemount3.8  | 1 +
 include/fuse_service.h | 3 ++-
 util/mount_service.c   | 7 +++++++
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
index 06755d7c3c4e..9b8c752b3bb9 100644
--- a/doc/fuservicemount3.8
+++ b/doc/fuservicemount3.8
@@ -22,6 +22,7 @@ framework.
 The FUSE server may ask fuservicemount3 to open files on its behalf.
 fuservicemount3 opens only paths that appear verbatim on its command line
 and refuses any other request with EPERM.
+Requests after the server has sent the mount point are refused as well.
 
 The second form checks if there is a FUSE service available for the given
 filesystem type.
diff --git a/include/fuse_service.h b/include/fuse_service.h
index 3954f62aa2b8..56f40a44b670 100644
--- a/include/fuse_service.h
+++ b/include/fuse_service.h
@@ -139,7 +139,8 @@ int fuse_service_parse_cmdline_opts(struct fuse_args *args,
 
 /**
  * Ask the mount.service helper to open a file on behalf of the fuse server.
- * The helper refuses a path that is not verbatim on the mount command line;
+ * The helper refuses a path that is not verbatim on the mount command line,
+ * and any request made after the mount point was sent;
  * fuse_service_receive_file() then reports -EPERM.
  *
  * @param sf service context
diff --git a/util/mount_service.c b/util/mount_service.c
index 835d3d94aa4a..1f25dcde0349 100644
--- a/util/mount_service.c
+++ b/util/mount_service.c
@@ -801,6 +801,13 @@ static int mount_service_open_path(const struct mount_service *mo,
 		return mount_service_send_file_error(mo, EINVAL, oc->path);
 	}
 
+	/* After fchdir to the mountpoint, a relative path resolves there */
+	if (mo->mountpoint) {
+		fprintf(stderr, "%s: %s: files must be requested before the mount point\n",
+			mo->msgtag, oc->path);
+		return mount_service_send_file_error(mo, EPERM, oc->path);
+	}
+
 	/*
 	 * The file is opened outside the service sandbox, so only hand out
 	 * what the user named.

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 04/10] util: give fuservicemount3 an absolute build-tree runpath
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (2 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:09   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 05/10] mount.fuse: free the options on the service mount return path Bernd Schubert via B4 Relay
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

The dynamic loader ignores $ORIGIN runpaths and LD_LIBRARY_PATH for a
setuid program. A build-tree fuservicemount3 made setuid, for example by
"run-tests.py --setuid-helpers", then failed with "libfuse3.so.4: cannot
open shared object file", or loaded the libfuse3.so.4 of the system.
meson removes build_rpath on install, so installed binaries do not change.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 util/meson.build | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/util/meson.build b/util/meson.build
index 28052a65536f..bc266776e153 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -22,6 +22,8 @@ if private_cfg.get('HAVE_SERVICEMOUNT', false)
              link_with: [ libfuse ],
              install: true,
              install_dir: get_option('sbindir'),
+             # A setuid run ignores the $ORIGIN runpath meson sets
+             build_rpath: join_paths(meson.project_build_root(), 'lib'),
              c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags)
 endif
 

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 05/10] mount.fuse: free the options on the service mount return path
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (3 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 04/10] util: give fuservicemount3 an absolute build-tree runpath Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:10   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 06/10] example/single_file: take no sector size from a regular backing file Bernd Schubert via B4 Relay
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

These leaks were detected by the new tests.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 util/mount.fuse.c | 42 ++++++++++++++++++++++++++----------------
 1 file changed, 26 insertions(+), 16 deletions(-)

diff --git a/util/mount.fuse.c b/util/mount.fuse.c
index c67a0c2b7f2f..1414265fd274 100644
--- a/util/mount.fuse.c
+++ b/util/mount.fuse.c
@@ -56,9 +56,7 @@
 #endif
 
 #include "fuse.h"
-#ifdef HAVE_SERVICEMOUNT
-# include "mount_service.h"
-#endif
+#include "mount_service.h"
 
 static char *progname;
 
@@ -396,6 +394,18 @@ out:
 	fuse_opt_free_args(&args);
 	return ret;
 }
+#else
+static int try_service_main(const char *argv0, const char *fstype,
+			    const char *source, const char *mountpoint,
+			    const char *options)
+{
+	(void)argv0;
+	(void)fstype;
+	(void)source;
+	(void)mountpoint;
+	(void)options;
+	return MOUNT_SERVICE_FALLBACK_NEEDED;
+}
 #endif
 
 int main(int argc, char *argv[])
@@ -415,6 +425,7 @@ int main(int argc, char *argv[])
 	int fuse_fd = 0;
 	int drop_privileges = 0;
 	char *dev_fd_mountpoint = NULL;
+	int ret;
 
 	progname = argv[0];
 	basename = strrchr(argv[0], '/');
@@ -608,19 +619,17 @@ int main(int argc, char *argv[])
 	}
 #endif
 
-#ifdef HAVE_SERVICEMOUNT
 	/*
 	 * Now that we know the desired filesystem type, see if we can find
 	 * a socket service implementing that, if we haven't selected any weird
 	 * options that would prevent that.
 	 */
 	if (!pass_fuse_fd && !(setuid_name && setuid_name[0])) {
-		int ret = try_service_main(argv[0], type, source, mountpoint,
-					   options);
+		ret = try_service_main(argv[0], type, source, mountpoint,
+				       options);
 		if (ret != MOUNT_SERVICE_FALLBACK_NEEDED)
-			return ret;
+			goto out;
 	}
-#endif
 
 	add_arg(&command, type);
 	if (source)
@@ -631,17 +640,18 @@ int main(int argc, char *argv[])
 		add_arg(&command, options);
 	}
 
+	execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+	fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+		strerror(errno));
+	ret = 1;
+
+out:
+	if (pass_fuse_fd)
+		close(fuse_fd);
 	free(options);
 	free(dev_fd_mountpoint);
 	free(dup_source);
 	free(setuid_name);
-
-	execl("/bin/sh", "/bin/sh", "-c", command, NULL);
-	fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
-		strerror(errno));
-	
-	if (pass_fuse_fd)
-		close(fuse_fd);
 	free(command);
-	return 1;
+	return ret;
 }

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 06/10] example/single_file: take no sector size from a regular backing file
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (4 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 05/10] mount.fuse: free the options on the service mount return path Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:13   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 07/10] test: check which files fuservicemount3 opens for the server Bernd Schubert via B4 Relay
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

single_file_configure() took the sector size of a regular backing file
from st_blksize, and refused to start when it was larger than the page
size. For a regular file st_blksize is only a preferred I/O size, and
the file is not opened with O_DIRECT, so any offset works. NFS reports
1 MiB there, so service_ll and service_hl failed on an image on NFS
with "lba size 1048576 smaller than blocksize 4096".

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 example/single_file.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/example/single_file.c b/example/single_file.c
index 59dbc6bbac5e..4260ecf3c5f5 100644
--- a/example/single_file.c
+++ b/example/single_file.c
@@ -887,7 +887,8 @@ int single_file_configure(const char *device, const char *filename)
 		perror(device);
 		return -1;
 	}
-	lbasize = stbuf.st_blksize;
+	/* A regular file takes any offset; its st_blksize is only an I/O hint */
+	lbasize = S_ISBLK(stbuf.st_mode) ? stbuf.st_blksize : 1;
 	backing_size = stbuf.st_size;
 
 	if (S_ISBLK(stbuf.st_mode)) {

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 07/10] test: check which files fuservicemount3 opens for the server
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (5 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 06/10] example/single_file: take no sector size from a regular backing file Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 08/10] test: check what fuservicemount3 refuses Bernd Schubert via B4 Relay
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

No test covered the OPEN and OPEN_BDEV commands of fuservicemount3.
test_service is a fuse server that sends one request and prints the
errno it got back. socket_activate.py starts it the way a systemd
socket unit does, so the tests do not depend on systemd.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 .gitignore                             |   1 +
 test/cases/lib/service.sh              |  81 +++++++++++++++
 test/cases/lib/socket_activate.py      |  42 ++++++++
 test/cases/mount/service-open-bound.sh |  46 +++++++++
 test/meson.build                       |   6 ++
 test/test_service.c                    | 176 +++++++++++++++++++++++++++++++++
 6 files changed, 352 insertions(+)

diff --git a/.gitignore b/.gitignore
index 877c2fe2ba87..ebf9b7cdae47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@ TAGS
 /test/test_setattr
 /test/test_api_30
 /test/test_fuser_conf
+/test/test_service
 /build/
 # run-tests.py output, when pointed at the source tree with --run-dir
 /fuse-tests/
diff --git a/test/cases/lib/service.sh b/test/cases/lib/service.sh
new file mode 100644
index 000000000000..1fabd9972393
--- /dev/null
+++ b/test/cases/lib/service.sh
@@ -0,0 +1,81 @@
+# lib/service.sh - start a fuse service server the way a systemd socket unit
+# does, for the fuservicemount3 cases.
+#
+# Sourced after common.sh. The socket has to be in the build-time socket
+# directory, so every caller runs as root.
+
+# service_setup <subtype>
+# Set service_sock. At exit remove the socket and any mount on $TEST_MNT.
+service_setup()
+{
+	service_subtype=$1
+	service_runs=0
+	service_sock=$("$FUSE_TEST_BIN_DIR/test_service" socket-path "$1")
+	# Every service script binds its own socket here; removing the
+	# directory would break another script's bind()
+	mkdir -p "$(dirname "$service_sock")"
+	_at_exit "rm -f '$service_sock'"
+	_at_exit "umount -l '$TEST_MNT' 2>/dev/null"
+}
+
+# service_start <log> <program> [args...]
+# Listen on service_sock and run <program> for the first connection, with its
+# output in <log>. Sets service_pid.
+service_start()
+{
+	local log=$1; shift
+
+	rm -f "$service_sock"
+	python3 "$TEST_LIB/socket_activate.py" "$service_sock" "$@" \
+		>"$log" 2>&1 &
+	service_pid=$!
+	_wait_for 10 "grep -q '^listening' '$log'" ||
+		_fail "$service_sock never listened"
+}
+
+# service_wait_exit
+# Reap the server and set service_rc to its exit status. A helper that never
+# connected leaves the activator in accept(), which is a failure.
+service_wait_exit()
+{
+	_wait_for 10 "! kill -0 $service_pid 2>/dev/null" || {
+		kill "$service_pid" 2>/dev/null || true
+		_fail "server (pid $service_pid) did not exit"
+	}
+	service_rc=0
+	wait "$service_pid" || service_rc=$?
+}
+
+# service_mount <source> <mountpoint> <case> [args...]
+# Run fuservicemount3 once against test_service <case> [args...] and reap the
+# server. Sets service_log.
+service_mount()
+{
+	local source=$1 mnt=$2; shift 2
+
+	service_log=$TEST_LOGDIR/fs-$service_runs-$1.out
+	service_runs=$((service_runs + 1))
+	service_start "$service_log" "$FUSE_TEST_BIN_DIR/test_service" "$@"
+
+	# Its exit status depends on the case; the server's line is the verdict.
+	"$FUSE_UTIL_DIR/fuservicemount3" "$source" "$mnt" \
+		-t "fuse.$service_subtype" || true
+
+	service_wait_exit
+}
+
+# service_result <what>
+# The last server prints one "<what> result: <value>" line, for example
+# "request result: EPERM". Echo <value>; fail on no such line or several.
+service_result()
+{
+	local count
+
+	count=$(grep -c "^$1 result: " "$service_log") || true
+	if [ "$count" != 1 ]; then
+		cat "$service_log" >&2
+		_fail "$service_log: $count \"$1 result:\" lines, want 1"
+	fi
+	# -n and p: print only the line the substitution matched
+	sed -n "s/^$1 result: //p" "$service_log"
+}
diff --git a/test/cases/lib/socket_activate.py b/test/cases/lib/socket_activate.py
new file mode 100755
index 000000000000..c33ca3a61ff2
--- /dev/null
+++ b/test/cases/lib/socket_activate.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+"""socket_activate.py <socket-path> <program> [args...]
+
+Listen on a SOCK_SEQPACKET socket, accept one connection and exec <program>
+with it, the way systemd starts a socket unit with Accept=yes: the connection
+is fd 3, LISTEN_FDS=1 and LISTEN_PID is the pid that runs <program>.
+
+Prints "listening" once a connect() can succeed.
+"""
+
+import os
+import socket
+import sys
+
+SD_LISTEN_FDS_START = 3
+
+
+def main():
+    if len(sys.argv) < 3:
+        sys.exit(__doc__)
+    path = sys.argv[1]
+    program = sys.argv[2:]
+
+    listener = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
+    listener.bind(path)
+    listener.listen(1)
+    print('listening', flush=True)
+
+    conn, _ = listener.accept()
+    listener.close()
+
+    # conn itself is close-on-exec and goes away with the exec
+    os.dup2(conn.fileno(), SD_LISTEN_FDS_START)
+    os.set_inheritable(SD_LISTEN_FDS_START, True)
+    os.environ['LISTEN_FDS'] = '1'
+    # exec keeps the pid
+    os.environ['LISTEN_PID'] = str(os.getpid())
+    os.execv(program[0], program)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/test/cases/mount/service-open-bound.sh b/test/cases/mount/service-open-bound.sh
new file mode 100755
index 000000000000..b81d1de3f7ae
--- /dev/null
+++ b/test/cases/mount/service-open-bound.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# fuservicemount3 opens a file for the fuse server only if the path is on its
+# command line, and only before the server has sent the mount point.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary test/test_service
+
+. "$TEST_LIB/service.sh"
+
+# One socket per run, so a parallel run does not connect to this one
+subtype=test-open-$$
+img=$TEST_SRC/img
+
+touch "$img"
+service_setup "$subtype"
+
+# service_open_request <case> <path> <expected errno name, or 0>
+# Mount $img through fuservicemount3 with the server started for <case>.
+service_open_request()
+{
+	local case=$1 path=$2 expected=$3
+
+	service_mount "$img" "$TEST_MNT" "$case" "$path"
+	_assert_eq "$(service_result request)" "$expected" "$case $path"
+}
+
+# $img is on the command line
+service_open_request open "$img" 0
+# Root can read /etc/passwd, so EPERM comes from the command line check
+service_open_request open /etc/passwd EPERM
+# Passes the command line check, fails the block device check
+service_open_request open-bdev "$img" ENOTBLK
+# Not ENOTBLK: OPEN_BDEV gets the command line check first
+service_open_request open-bdev /etc/passwd EPERM
+
+# On the command line, but after MNTPT the helper runs inside $TEST_MNT
+service_open_request open-after-mount "$img" EPERM
+umount "$TEST_MNT"
diff --git a/test/meson.build b/test/meson.build
index 68e083f7885c..ec48dab44a61 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -54,6 +54,12 @@ if build_utils
                    c_args: '-DFUSE_CONF="fuse.conf"',
                    install: false)
 endif
+if private_cfg.get('HAVE_SERVICEMOUNT', false)
+  td += executable('test_service', 'test_service.c',
+                   include_directories: include_dirs,
+                   link_with: [ libfuse ],
+                   install: false)
+endif
 
 if meson.is_subproject()
 	# Skipped rather than run: the tests mount filesystems, which is not
diff --git a/test/test_service.c b/test/test_service.c
new file mode 100644
index 000000000000..0d54df3427a9
--- /dev/null
+++ b/test/test_service.c
@@ -0,0 +1,176 @@
+/*
+ * FUSE: Filesystem in Userspace
+ *
+ * This program can be distributed under the terms of the GNU GPLv2.
+ * See the file GPL2.txt.
+ *
+ * A fuse service server for the service mount tests. Each mode takes one
+ * step against fuservicemount3 and prints the name of the errno that came
+ * back, 0 for success.
+ *
+ *   test_service socket-path <subtype>
+ *   test_service open <path>
+ *   test_service open-bdev <path>
+ *   test_service open-after-mount <path>
+ */
+
+#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19)
+
+/* strerrorname_np() */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "fuse_config.h"
+#include <fuse_lowlevel.h>
+#include <fuse_service.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+static const struct fuse_lowlevel_ops test_service_oper = { };
+
+/* @return "EPERM" and so on, "0" for no error */
+static const char *errno_name(int error)
+{
+	const char *name;
+
+	if (!error)
+		return "0";
+
+	name = strerrorname_np(error);
+	return name ? name : "unknown errno";
+}
+
+/* The first non-option argument is the mount source, not the mountpoint */
+static int skip_source(void *data, const char *arg, int key,
+		       struct fuse_args *outargs)
+{
+	bool *source_seen = data;
+
+	(void)arg;
+	(void)outargs;
+
+	if (key == FUSE_OPT_KEY_NONOPT && !*source_seen) {
+		*source_seen = true;
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * Mount through the helper so that it has a mount point when the file is
+ * requested.
+ *
+ * @return the mounted session, or NULL on failure
+ */
+static struct fuse_session *session_mounted(struct fuse_service *service,
+					    const char *argv0)
+{
+	struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+	struct fuse_cmdline_opts opts = { };
+	struct fuse_session *se = NULL;
+	bool source_seen = false;
+
+	if (fuse_opt_add_arg(&args, argv0) ||
+	    fuse_service_append_args(service, &args) ||
+	    fuse_opt_parse(&args, &source_seen, NULL, skip_source) ||
+	    fuse_service_parse_cmdline_opts(&args, &opts))
+		goto out;
+
+	se = fuse_session_new(&args, &test_service_oper,
+			      sizeof(test_service_oper), NULL);
+	if (!se)
+		goto out;
+
+	if (fuse_service_session_mount(service, se, S_IFDIR, &opts)) {
+		fuse_session_destroy(se);
+		se = NULL;
+	}
+
+out:
+	free(opts.mountpoint);
+	fuse_opt_free_args(&args);
+	return se;
+}
+
+/* @return 0 when the result was printed, negative errno otherwise */
+static int request_printed(const struct fuse_service *service,
+			   const char *path, bool blockdev)
+{
+	int fd;
+	int ret;
+
+	if (blockdev)
+		ret = fuse_service_request_blockdev(service, path, O_RDONLY,
+						    0, 0, 0);
+	else
+		ret = fuse_service_request_file(service, path, O_RDONLY, 0, 0);
+	if (ret)
+		return ret;
+
+	/* A refusal by the helper is a success return, with -errno in fd */
+	ret = fuse_service_receive_file(service, path, &fd);
+	if (ret)
+		return ret;
+
+	if (fd >= 0) {
+		close(fd);
+		printf("request result: 0\n");
+	} else {
+		printf("request result: %s\n", errno_name(-fd));
+	}
+	fflush(stdout);
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct fuse_service *service = NULL;
+	struct fuse_session *se = NULL;
+	bool blockdev = false;
+	int ret = 1;
+
+	if (argc != 3) {
+		fprintf(stderr, "usage: %s socket-path <subtype>\n", argv[0]);
+		fprintf(stderr, "       %s open|open-bdev|open-after-mount <path>\n",
+			argv[0]);
+		return 1;
+	}
+
+	if (!strcmp(argv[1], "socket-path")) {
+		printf("%s/%s\n", FUSE_SERVICE_SOCKET_DIR, argv[2]);
+		return 0;
+	}
+
+	if (fuse_service_accept(&service) || !fuse_service_accepted(service)) {
+		fprintf(stderr, "%s: not started as a fuse service\n", argv[0]);
+		return 1;
+	}
+
+	if (!strcmp(argv[1], "open-after-mount")) {
+		se = session_mounted(service, argv[0]);
+		if (!se)
+			goto out;
+	} else if (!strcmp(argv[1], "open-bdev")) {
+		blockdev = true;
+	} else if (strcmp(argv[1], "open")) {
+		fprintf(stderr, "%s: unknown case %s\n", argv[0], argv[1]);
+		goto out;
+	}
+
+	if (request_printed(service, argv[2], blockdev))
+		goto out;
+
+	ret = 0;
+out:
+	fuse_service_send_goodbye(service, ret);
+	fuse_service_destroy(&service);
+	/* Closes /dev/fuse; the test script unmounts */
+	if (se)
+		fuse_session_destroy(se);
+	return ret;
+}

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 08/10] test: check what fuservicemount3 refuses
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (6 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 07/10] test: check which files fuservicemount3 opens for the server Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 09/10] test: mount the service examples through fuservicemount3 Bernd Schubert via B4 Relay
  2026-09-24 22:23 ` [PATCH 10/10] test: run mkfs.ext4 through the service examples Bernd Schubert via B4 Relay
  9 siblings, 0 replies; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

fuservicemount3 runs setuid root and acts on requests from a fuse
server it does not trust. No test covered its checks on the subtype,
the mount point and its file type, fuseblk for a user who is not root,
or a server that exits before its goodbye.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 test/cases/lib/service.sh               |  20 ++++--
 test/cases/mount/service-caps.sh        |  20 ++++++
 test/cases/mount/service-check.sh       |  38 ++++++++++++
 test/cases/mount/service-mountpoint.sh  |  36 +++++++++++
 test/cases/mount/service-nonroot.sh     |  54 +++++++++++++++++
 test/cases/mount/service-server-exit.sh |  22 +++++++
 test/test_service.c                     | 104 ++++++++++++++++++++++++--------
 7 files changed, 266 insertions(+), 28 deletions(-)

diff --git a/test/cases/lib/service.sh b/test/cases/lib/service.sh
index 1fabd9972393..de19f37dd712 100644
--- a/test/cases/lib/service.sh
+++ b/test/cases/lib/service.sh
@@ -10,6 +10,8 @@ service_setup()
 {
 	service_subtype=$1
 	service_runs=0
+	# A case may prefix it, to run the helper as another user
+	service_helper=("$FUSE_UTIL_DIR/fuservicemount3")
 	service_sock=$("$FUSE_TEST_BIN_DIR/test_service" socket-path "$1")
 	# Every service script binds its own socket here; removing the
 	# directory would break another script's bind()
@@ -31,6 +33,16 @@ service_start()
 	service_pid=$!
 	_wait_for 10 "grep -q '^listening' '$log'" ||
 		_fail "$service_sock never listened"
+	# connect() needs write permission, and a case may run as another user
+	chmod 0666 "$service_sock"
+}
+
+# service_stop
+# Kill an activator that no helper connected to.
+service_stop()
+{
+	kill "$service_pid" 2>/dev/null || true
+	wait "$service_pid" 2>/dev/null || true
 }
 
 # service_wait_exit
@@ -48,7 +60,7 @@ service_wait_exit()
 
 # service_mount <source> <mountpoint> <case> [args...]
 # Run fuservicemount3 once against test_service <case> [args...] and reap the
-# server. Sets service_log.
+# server. Sets service_log and service_helper_rc.
 service_mount()
 {
 	local source=$1 mnt=$2; shift 2
@@ -57,9 +69,9 @@ service_mount()
 	service_runs=$((service_runs + 1))
 	service_start "$service_log" "$FUSE_TEST_BIN_DIR/test_service" "$@"
 
-	# Its exit status depends on the case; the server's line is the verdict.
-	"$FUSE_UTIL_DIR/fuservicemount3" "$source" "$mnt" \
-		-t "fuse.$service_subtype" || true
+	service_helper_rc=0
+	"${service_helper[@]}" "$source" "$mnt" -t "fuse.$service_subtype" ||
+		service_helper_rc=$?
 
 	service_wait_exit
 }
diff --git a/test/cases/mount/service-caps.sh b/test/cases/mount/service-caps.sh
new file mode 100755
index 000000000000..69bc2bdbef8d
--- /dev/null
+++ b/test/cases/mount/service-caps.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# fuservicemount3 run by root offers the fuse server allow_other and fuseblk.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary test/test_service
+
+. "$TEST_LIB/service.sh"
+
+service_setup "test-caps-$$"
+
+service_mount "$service_subtype" "$TEST_MNT" caps
+_assert_eq "$(service_result caps)" "allow_other=1 fuseblk=1" "caps as root"
diff --git a/test/cases/mount/service-check.sh b/test/cases/mount/service-check.sh
new file mode 100755
index 000000000000..ec30031d039e
--- /dev/null
+++ b/test/cases/mount/service-check.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# fuservicemount3 --check succeeds only for a socket named after the subtype,
+# and never for a subtype that is a path.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+_require_root
+_require_binary util/fuservicemount3
+_require_binary test/test_service
+
+. "$TEST_LIB/service.sh"
+
+subtype=test-check-$$
+service_setup "$subtype"
+
+# check_rc <fstype>
+check_rc()
+{
+	local rc=0
+
+	"$FUSE_UTIL_DIR/fuservicemount3" -t "$1" --check || rc=$?
+	echo "$rc"
+}
+
+_assert_eq "$(check_rc "fuse.$subtype")" 1 "no socket"
+
+touch "$service_sock"
+_assert_eq "$(check_rc "fuse.$subtype")" 1 "regular file"
+
+service_start "$TEST_LOGDIR/fs-check.out" "$FUSE_TEST_BIN_DIR/test_service" caps
+_assert_eq "$(check_rc "fuse.$subtype")" 0 "listening socket"
+# The same socket, named through a path
+_assert_eq "$(check_rc "fuse../$subtype")" 1 "subtype ./$subtype"
+service_stop
diff --git a/test/cases/mount/service-mountpoint.sh b/test/cases/mount/service-mountpoint.sh
new file mode 100755
index 000000000000..0df4733389e0
--- /dev/null
+++ b/test/cases/mount/service-mountpoint.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# The fuse server names the mount point, so fuservicemount3 has to refuse one
+# that is not on its command line, and one of the wrong file type.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary test/test_service
+
+. "$TEST_LIB/service.sh"
+
+file=$TEST_SRC/file
+
+touch "$file"
+service_setup "test-mntpt-$$"
+
+service_mount "$service_subtype" "$TEST_MNT" mount dir
+_assert_eq "$(service_result mount)" 0 "mount dir on a directory"
+_assert_fstype "$TEST_MNT" "fuse.$service_subtype" fuse
+umount "$TEST_MNT"
+
+service_mount "$service_subtype" "$TEST_MNT" mount-elsewhere "$TEST_SRC"
+_assert_eq "$(service_result mount)" EINVAL \
+	"mount point not on the command line"
+
+service_mount "$service_subtype" "$TEST_MNT" mount file
+_assert_eq "$(service_result mount)" EISDIR "mount file on a directory"
+
+service_mount "$service_subtype" "$file" mount dir
+_assert_eq "$(service_result mount)" ENOTDIR "mount dir on a regular file"
diff --git a/test/cases/mount/service-nonroot.sh b/test/cases/mount/service-nonroot.sh
new file mode 100755
index 000000000000..0c211d0a9965
--- /dev/null
+++ b/test/cases/mount/service-nonroot.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# fuservicemount3 installed setuid and run by an unprivileged user mounts only
+# on a directory that user can write, and never offers fuseblk.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+# Root installs the setuid copy and the socket
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary test/test_service
+_require_prog setpriv
+_require_prog findmnt
+
+user=nobody
+uid=$(id -u "$user") || _notrun "no user $user"
+gid=$(id -g "$user")
+run_as=(setpriv --reuid="$uid" --regid="$gid" --clear-groups)
+
+helper=$TEST_WORKDIR/fuservicemount3
+case ",$(findmnt -n -o OPTIONS -T "$TEST_WORKDIR")," in
+*,nosuid,*) _notrun "$TEST_WORKDIR is on a nosuid mount" ;;
+esac
+cp "$FUSE_UTIL_DIR/fuservicemount3" "$helper"
+_at_exit "rm -f '$helper'"
+chmod 4755 "$helper"
+"${run_as[@]}" test -x "$helper" || _notrun "$user cannot reach $helper"
+
+. "$TEST_LIB/service.sh"
+
+service_setup "test-nonroot-$$"
+service_helper=("${run_as[@]}" "$helper")
+root_dir=$TEST_WORKDIR/root-mnt
+mkdir -m 0755 "$root_dir"
+
+chown "$uid" "$TEST_MNT"
+service_mount "$service_subtype" "$TEST_MNT" mount dir
+_assert_eq "$(service_result mount)" 0 "mount on a directory $user owns"
+umount "$TEST_MNT"
+
+service_mount "$service_subtype" "$root_dir" mount dir
+_assert_eq "$(service_result mount)" EPERM \
+	"mount on a directory owned by root"
+
+# allow_other depends on user_allow_other in the system fuse.conf
+service_mount "$service_subtype" "$TEST_MNT" caps
+case $(service_result caps) in
+*" fuseblk=0") ;;
+*) _fail "caps as $user: $(service_result caps)" ;;
+esac
diff --git a/test/cases/mount/service-server-exit.sh b/test/cases/mount/service-server-exit.sh
new file mode 100755
index 000000000000..6304f20ff2cb
--- /dev/null
+++ b/test/cases/mount/service-server-exit.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# A fuse server that exits without a goodbye makes fuservicemount3 fail, and
+# leaves nothing mounted.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary test/test_service
+
+. "$TEST_LIB/service.sh"
+
+service_setup "test-exit-$$"
+
+service_mount "$service_subtype" "$TEST_MNT" exit-early
+_assert_ne "$service_helper_rc" 0 "fuservicemount3 exit status"
+_assert_eq "$(mountinfo_field "$TEST_MNT" fstype)" "" "$TEST_MNT mounted"
diff --git a/test/test_service.c b/test/test_service.c
index 0d54df3427a9..7bbd14e2650d 100644
--- a/test/test_service.c
+++ b/test/test_service.c
@@ -12,6 +12,10 @@
  *   test_service open <path>
  *   test_service open-bdev <path>
  *   test_service open-after-mount <path>
+ *   test_service mount dir|file
+ *   test_service mount-elsewhere <mountpoint>
+ *   test_service caps
+ *   test_service exit-early
  */
 
 #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19)
@@ -62,18 +66,25 @@ static int skip_source(void *data, const char *arg, int key,
 }
 
 /*
- * Mount through the helper so that it has a mount point when the file is
- * requested.
+ * Mount through the helper. On success *sep is the mounted session, which
+ * keeps /dev/fuse open until it is destroyed.
  *
- * @return the mounted session, or NULL on failure
+ * @param fmt         mount point type the helper has to find
+ * @param mountpoint  sent in place of the one on the command line, or NULL
+ * @return 0 when the result was printed, -1 otherwise
  */
-static struct fuse_session *session_mounted(struct fuse_service *service,
-					    const char *argv0)
+static int mount_printed(struct fuse_service *service, const char *argv0,
+			 mode_t fmt, const char *mountpoint,
+			 struct fuse_session **sep)
 {
 	struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
 	struct fuse_cmdline_opts opts = { };
-	struct fuse_session *se = NULL;
+	struct fuse_session *se;
 	bool source_seen = false;
+	int printed = -1;
+	int ret;
+
+	*sep = NULL;
 
 	if (fuse_opt_add_arg(&args, argv0) ||
 	    fuse_service_append_args(service, &args) ||
@@ -81,20 +92,30 @@ static struct fuse_session *session_mounted(struct fuse_service *service,
 	    fuse_service_parse_cmdline_opts(&args, &opts))
 		goto out;
 
+	if (mountpoint) {
+		free(opts.mountpoint);
+		opts.mountpoint = strdup(mountpoint);
+		if (!opts.mountpoint)
+			goto out;
+	}
+
 	se = fuse_session_new(&args, &test_service_oper,
 			      sizeof(test_service_oper), NULL);
 	if (!se)
 		goto out;
 
-	if (fuse_service_session_mount(service, se, S_IFDIR, &opts)) {
+	ret = fuse_service_session_mount(service, se, fmt, &opts);
+	if (ret)
 		fuse_session_destroy(se);
-		se = NULL;
-	}
+	else
+		*sep = se;
 
+	printf("mount result: %s\n", errno_name(-ret));
+	printed = 0;
 out:
 	free(opts.mountpoint);
 	fuse_opt_free_args(&args);
-	return se;
+	return printed;
 }
 
 /* @return 0 when the result was printed, negative errno otherwise */
@@ -127,22 +148,40 @@ static int request_printed(const struct fuse_service *service,
 	return 0;
 }
 
+/* @return S_IFDIR or S_IFREG, 0 for an unknown name */
+static mode_t mount_format(const char *name)
+{
+	if (!strcmp(name, "dir"))
+		return S_IFDIR;
+	if (!strcmp(name, "file"))
+		return S_IFREG;
+	return 0;
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_service *service = NULL;
 	struct fuse_session *se = NULL;
-	bool blockdev = false;
+	const char *mode;
+	const char *arg;
 	int ret = 1;
 
-	if (argc != 3) {
+	if (argc != 2 && argc != 3) {
 		fprintf(stderr, "usage: %s socket-path <subtype>\n", argv[0]);
 		fprintf(stderr, "       %s open|open-bdev|open-after-mount <path>\n",
 			argv[0]);
+		fprintf(stderr, "       %s mount dir|file\n", argv[0]);
+		fprintf(stderr, "       %s mount-elsewhere <mountpoint>\n",
+			argv[0]);
+		fprintf(stderr, "       %s caps|exit-early\n", argv[0]);
 		return 1;
 	}
+	mode = argv[1];
+	/* argv[argc] is NULL */
+	arg = argv[2];
 
-	if (!strcmp(argv[1], "socket-path")) {
-		printf("%s/%s\n", FUSE_SERVICE_SOCKET_DIR, argv[2]);
+	if (!strcmp(mode, "socket-path") && arg) {
+		printf("%s/%s\n", FUSE_SERVICE_SOCKET_DIR, arg);
 		return 0;
 	}
 
@@ -151,19 +190,36 @@ int main(int argc, char *argv[])
 		return 1;
 	}
 
-	if (!strcmp(argv[1], "open-after-mount")) {
-		se = session_mounted(service, argv[0]);
-		if (!se)
-			goto out;
-	} else if (!strcmp(argv[1], "open-bdev")) {
-		blockdev = true;
-	} else if (strcmp(argv[1], "open")) {
-		fprintf(stderr, "%s: unknown case %s\n", argv[0], argv[1]);
-		goto out;
+	if (!strcmp(mode, "exit-early")) {
+		/* No goodbye, the helper only sees the connection close */
+		fuse_service_destroy(&service);
+		return 0;
 	}
 
-	if (request_printed(service, argv[2], blockdev))
+	if (!strcmp(mode, "caps")) {
+		printf("caps result: allow_other=%d fuseblk=%d\n",
+		       fuse_service_can_allow_other(service),
+		       fuse_service_can_fuseblk(service));
+	} else if (!strcmp(mode, "mount") && arg && mount_format(arg)) {
+		if (mount_printed(service, argv[0], mount_format(arg), NULL,
+				  &se))
+			goto out;
+	} else if (!strcmp(mode, "mount-elsewhere") && arg) {
+		if (mount_printed(service, argv[0], S_IFDIR, arg, &se))
+			goto out;
+	} else if (!strcmp(mode, "open-after-mount") && arg) {
+		if (mount_printed(service, argv[0], S_IFDIR, NULL, &se) || !se)
+			goto out;
+		if (request_printed(service, arg, false))
+			goto out;
+	} else if ((!strcmp(mode, "open") || !strcmp(mode, "open-bdev")) &&
+		   arg) {
+		if (request_printed(service, arg, !strcmp(mode, "open-bdev")))
+			goto out;
+	} else {
+		fprintf(stderr, "%s: unknown case %s\n", argv[0], mode);
 		goto out;
+	}
 
 	ret = 0;
 out:

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 09/10] test: mount the service examples through fuservicemount3
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (7 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 08/10] test: check what fuservicemount3 refuses Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:25   ` Darrick J. Wong
  2026-09-24 22:23 ` [PATCH 10/10] test: run mkfs.ext4 through the service examples Bernd Schubert via B4 Relay
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

No test ran service_ll, service_hl or null in service mode. No test
checked that mount.fuse3 mounts through a listening service socket, and
runs the filesystem program when the socket refuses the connection.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 test/cases/lib/service-example.sh      | 62 ++++++++++++++++++++++++++++++++++
 test/cases/mount/service-hl.sh         |  7 ++++
 test/cases/mount/service-ll.sh         |  7 ++++
 test/cases/mount/service-mount-fuse.sh | 31 +++++++++++++++++
 test/cases/mount/service-null.sh       | 33 ++++++++++++++++++
 5 files changed, 140 insertions(+)

diff --git a/test/cases/lib/service-example.sh b/test/cases/lib/service-example.sh
new file mode 100644
index 000000000000..878b379b8b16
--- /dev/null
+++ b/test/cases/lib/service-example.sh
@@ -0,0 +1,62 @@
+# lib/service-example.sh - body for the service_ll / service_hl cases.
+#
+# Caller sets FS_NAME and LAUNCH before sourcing:
+#   FS_NAME   service_ll | service_hl
+#   LAUNCH    fuservicemount3 | mount_fuse
+#
+# The file the example serves has to read back as the image, and what is
+# written through the mount has to reach the image.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+# Before the gates: a misspelled FS_NAME would otherwise skip as "not built"
+case ${FS_NAME:-} in
+service_ll | service_hl) ;;
+*) _fail "unknown FS_NAME '${FS_NAME:-}'" ;;
+esac
+case ${LAUNCH:-} in
+fuservicemount3 | mount_fuse) ;;
+*) _fail "unknown LAUNCH '${LAUNCH:-}'" ;;
+esac
+
+_require_linux "fuservicemount3"
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary "example/$FS_NAME"
+[ "$LAUNCH" != mount_fuse ] || _require_binary util/mount.fuse3
+
+. "$TEST_LIB/service.sh"
+
+service_setup "test-$FS_NAME-$$"
+img=$TEST_SRC/img
+old=$TEST_TMP/old
+new=$TEST_TMP/new
+
+# The size has to be a multiple of the page size
+head -c 1048576 /dev/urandom >"$old"
+head -c 1048576 /dev/urandom >"$new"
+cp "$old" "$img"
+
+service_start "$TEST_LOGDIR/fs-$FS_NAME.out" "$FUSE_EXAMPLE_DIR/$FS_NAME"
+case $LAUNCH in
+fuservicemount3)
+	"$FUSE_UTIL_DIR/fuservicemount3" "$img" "$TEST_MNT" \
+		-t "fuse.$service_subtype" ||
+		_fail "fuservicemount3 did not mount $FS_NAME"
+	;;
+mount_fuse)
+	"$FUSE_UTIL_DIR/mount.fuse3" "$service_subtype#$img" "$TEST_MNT" ||
+		_fail "mount.fuse3 did not mount $FS_NAME"
+	;;
+esac
+
+_assert_fstype "$TEST_MNT" "fuse.$service_subtype" fuse
+_assert_file_eq "$TEST_MNT/single_file" "$old"
+dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync status=none
+
+umount "$TEST_MNT"
+service_wait_exit
+_assert_eq "$service_rc" 0 "$FS_NAME exit status"
+_assert_file_eq "$img" "$new"
diff --git a/test/cases/mount/service-hl.sh b/test/cases/mount/service-hl.sh
new file mode 100755
index 000000000000..6db3add3a5d1
--- /dev/null
+++ b/test/cases/mount/service-hl.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+# GROUP: mount
+
+FS_NAME=service_hl
+LAUNCH=fuservicemount3
+
+. "$TEST_LIB/service-example.sh"
diff --git a/test/cases/mount/service-ll.sh b/test/cases/mount/service-ll.sh
new file mode 100755
index 000000000000..036553889f7a
--- /dev/null
+++ b/test/cases/mount/service-ll.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+# GROUP: mount
+
+FS_NAME=service_ll
+LAUNCH=fuservicemount3
+
+. "$TEST_LIB/service-example.sh"
diff --git a/test/cases/mount/service-mount-fuse.sh b/test/cases/mount/service-mount-fuse.sh
new file mode 100755
index 000000000000..6bc95240c772
--- /dev/null
+++ b/test/cases/mount/service-mount-fuse.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# mount.fuse3 mounts through the service when its socket listens, and execs a
+# program named after the type when the socket refuses the connection.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_binary example/hello
+
+FS_NAME=service_ll
+LAUNCH=mount_fuse
+
+. "$TEST_LIB/service-example.sh"
+
+fallback=test-fallback-$$
+service_setup "$fallback"
+
+# Bound but never listening, so connect() gets ECONNREFUSED
+python3 -c 'import socket, sys
+socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET).bind(sys.argv[1])' \
+	"$service_sock"
+
+mkdir "$TEST_TMP/bin"
+ln -s "$FUSE_EXAMPLE_DIR/hello" "$TEST_TMP/bin/$fallback"
+export PATH="$TEST_TMP/bin:$PATH"
+
+fuse_mount_helper "$fallback" >/dev/null
+_assert_listdir "$TEST_MNT" hello
+fuse_umount
diff --git a/test/cases/mount/service-null.sh b/test/cases/mount/service-null.sh
new file mode 100755
index 000000000000..b89107ae3a40
--- /dev/null
+++ b/test/cases/mount/service-null.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+# GROUP: mount
+#
+# The null example mounts through fuservicemount3 on a regular file.
+# null has no backing file and mounts on a regular file, so it does not use
+# lib/service-example.sh.
+
+_fuse_no_mount_needed=1
+. "$TEST_LIB/common.sh"
+
+_require_linux "fuservicemount3"
+_require_root
+_require_fuse_device
+_require_binary util/fuservicemount3
+_require_binary example/null
+
+. "$TEST_LIB/service.sh"
+
+service_setup "test-null-$$"
+mnt_file=$TEST_TMP/file
+_at_exit "umount -l '$mnt_file' 2>/dev/null"
+printf 'dummy' >"$mnt_file"
+
+service_start "$TEST_LOGDIR/fs-null.out" "$FUSE_EXAMPLE_DIR/null"
+# null takes no source, only the mount point
+"$FUSE_UTIL_DIR/fuservicemount3" "$mnt_file" -t "fuse.$service_subtype" ||
+	_fail "fuservicemount3 did not mount null"
+
+_check fuse_test_null_roundtrip "$mnt_file"
+
+umount "$mnt_file"
+service_wait_exit
+_assert_eq "$service_rc" 0 "null exit status"

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 10/10] test: run mkfs.ext4 through the service examples
  2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
                   ` (8 preceding siblings ...)
  2026-09-24 22:23 ` [PATCH 09/10] test: mount the service examples through fuservicemount3 Bernd Schubert via B4 Relay
@ 2026-09-24 22:23 ` Bernd Schubert via B4 Relay
  2026-09-25 22:27   ` Darrick J. Wong
  9 siblings, 1 reply; 23+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-09-24 22:23 UTC (permalink / raw)
  To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert

From: Bernd Schubert <bernd@bsbernd.com>

The byte comparison in the service example tests does not show that a
real filesystem tool works on the file service_ll and service_hl serve.
mkfs.ext4 writes a filesystem through the mount, and e2fsck checks the
image after umount.

Assisted-by: LLM
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
 example/service_ll.c                |  5 ++++
 test/cases/lib/service-example.sh   | 54 ++++++++++++++++++++++++++++++-------
 test/cases/mount/service-hl-mkfs.sh |  8 ++++++
 test/cases/mount/service-ll-mkfs.sh |  8 ++++++
 4 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/example/service_ll.c b/example/service_ll.c
index fd43c40fc15b..7baf47905a33 100644
--- a/example/service_ll.c
+++ b/example/service_ll.c
@@ -43,6 +43,11 @@
  *
  *     mount -t fuse.service_ll /dev/sda /mnt
  *
+ * /mnt/single_file then holds the bytes of /dev/sda, so a filesystem can be
+ * created on it:
+ *
+ *     mkfs.ext4 /mnt/single_file
+ *
  * ## Source code ##
  * \include service_ll.c
  * \include service_ll.socket
diff --git a/test/cases/lib/service-example.sh b/test/cases/lib/service-example.sh
index 878b379b8b16..4d7e0b27f3b9 100644
--- a/test/cases/lib/service-example.sh
+++ b/test/cases/lib/service-example.sh
@@ -1,15 +1,19 @@
 # lib/service-example.sh - body for the service_ll / service_hl cases.
 #
-# Caller sets FS_NAME and LAUNCH before sourcing:
+# Caller sets FS_NAME and LAUNCH, and optionally CHECK, before sourcing:
 #   FS_NAME   service_ll | service_hl
 #   LAUNCH    fuservicemount3 | mount_fuse
+#   CHECK     bytes (default) | mkfs
 #
-# The file the example serves has to read back as the image, and what is
-# written through the mount has to reach the image.
+# bytes: the file the example serves has to read back as the image, and what
+# is written through the mount has to reach the image.
+# mkfs: mkfs.ext4 on that file has to leave an image e2fsck accepts.
 
 _fuse_no_mount_needed=1
 . "$TEST_LIB/common.sh"
 
+CHECK=${CHECK:-bytes}
+
 # Before the gates: a misspelled FS_NAME would otherwise skip as "not built"
 case ${FS_NAME:-} in
 service_ll | service_hl) ;;
@@ -19,6 +23,10 @@ case ${LAUNCH:-} in
 fuservicemount3 | mount_fuse) ;;
 *) _fail "unknown LAUNCH '${LAUNCH:-}'" ;;
 esac
+case $CHECK in
+bytes | mkfs) ;;
+*) _fail "unknown CHECK '$CHECK'" ;;
+esac
 
 _require_linux "fuservicemount3"
 _require_root
@@ -26,6 +34,10 @@ _require_fuse_device
 _require_binary util/fuservicemount3
 _require_binary "example/$FS_NAME"
 [ "$LAUNCH" != mount_fuse ] || _require_binary util/mount.fuse3
+if [ "$CHECK" = mkfs ]; then
+	_require_prog mkfs.ext4
+	_require_prog e2fsck
+fi
 
 . "$TEST_LIB/service.sh"
 
@@ -35,9 +47,16 @@ old=$TEST_TMP/old
 new=$TEST_TMP/new
 
 # The size has to be a multiple of the page size
-head -c 1048576 /dev/urandom >"$old"
-head -c 1048576 /dev/urandom >"$new"
-cp "$old" "$img"
+case $CHECK in
+bytes)
+	head -c 1048576 /dev/urandom >"$old"
+	head -c 1048576 /dev/urandom >"$new"
+	cp "$old" "$img"
+	;;
+mkfs)
+	truncate -s 64M "$img"
+	;;
+esac
 
 service_start "$TEST_LOGDIR/fs-$FS_NAME.out" "$FUSE_EXAMPLE_DIR/$FS_NAME"
 case $LAUNCH in
@@ -53,10 +72,27 @@ mount_fuse)
 esac
 
 _assert_fstype "$TEST_MNT" "fuse.$service_subtype" fuse
-_assert_file_eq "$TEST_MNT/single_file" "$old"
-dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync status=none
+case $CHECK in
+bytes)
+	_assert_file_eq "$TEST_MNT/single_file" "$old"
+	dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync \
+		status=none
+	;;
+mkfs)
+	# -F: single_file is a regular file, not a block device
+	mkfs.ext4 -F -q "$TEST_MNT/single_file" ||
+		_fail "mkfs.ext4 through $FS_NAME failed"
+	;;
+esac
 
 umount "$TEST_MNT"
 service_wait_exit
 _assert_eq "$service_rc" 0 "$FS_NAME exit status"
-_assert_file_eq "$img" "$new"
+case $CHECK in
+bytes)
+	_assert_file_eq "$img" "$new"
+	;;
+mkfs)
+	e2fsck -fn "$img" || _fail "e2fsck found errors in $img"
+	;;
+esac
diff --git a/test/cases/mount/service-hl-mkfs.sh b/test/cases/mount/service-hl-mkfs.sh
new file mode 100755
index 000000000000..af552818cd90
--- /dev/null
+++ b/test/cases/mount/service-hl-mkfs.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# GROUP: mount
+
+FS_NAME=service_hl
+LAUNCH=fuservicemount3
+CHECK=mkfs
+
+. "$TEST_LIB/service-example.sh"
diff --git a/test/cases/mount/service-ll-mkfs.sh b/test/cases/mount/service-ll-mkfs.sh
new file mode 100755
index 000000000000..9908937010de
--- /dev/null
+++ b/test/cases/mount/service-ll-mkfs.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# GROUP: mount
+
+FS_NAME=service_ll
+LAUNCH=fuservicemount3
+CHECK=mkfs
+
+. "$TEST_LIB/service-example.sh"

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline()
  2026-09-24 22:23 ` [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline() Bernd Schubert via B4 Relay
@ 2026-09-25 21:40   ` Darrick J. Wong
  0 siblings, 0 replies; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 21:40 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:29AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> mount_service_handle_mountpoint_cmd() compared the mount point with
> each argument of fuservicemount3 in its own loop. The next patch makes
> the same check for OPEN requests, so the loop moves into
> arg_in_cmdline(). There is no change in behaviour.
> 
> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

This is a reasonable hoist so
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  util/mount_service.c | 22 +++++++++++++---------
>  1 file changed, 13 insertions(+), 9 deletions(-)
> 
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 0b3266309a8a..7549e0b5024f 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -758,6 +758,18 @@ static int prepare_bdev(const struct mount_service *mo,
>  	return 0;
>  }
>  
> +static bool arg_in_cmdline(int argc, const char * const argv[],
> +			   const char *value)
> +{
> +	int i;
> +
> +	for (i = 0; i < argc; i++)
> +		if (!strcmp(argv[i], value))
> +			return true;
> +
> +	return false;
> +}
> +
>  static int mount_service_open_path(const struct mount_service *mo,
>  				   mode_t expected_fmt,
>  				   struct fuse_service_packet *p, size_t psz)
> @@ -1248,8 +1260,6 @@ static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
>  			container_of(p, struct fuse_service_mountpoint_command, p);
>  	char *mntpt;
>  	mode_t expected_fmt;
> -	bool foundit = false;
> -	int i;
>  
>  	if (psz < sizeof_fuse_service_mountpoint_command(1)) {
>  		fprintf(stderr, "%s: mount point command too small\n",
> @@ -1289,13 +1299,7 @@ static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
>  	}
>  
>  	/* Mountpoint must be mentioned in the caller's argument list */
> -	for (i = 0; i < argc; i++) {
> -		if (!strcmp(argv[i], oc->value)) {
> -			foundit = true;
> -			break;
> -		}
> -	}
> -	if (!foundit) {
> +	if (!arg_in_cmdline(argc, argv, oc->value)) {
>  		fprintf(stderr, "%s: mount point must be in command line arguments\n",
>  			mo->msgtag);
>  		return mount_service_send_reply(mo, EINVAL);
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 02/10] mount_service: open only paths named on the command line
  2026-09-24 22:23 ` [PATCH 02/10] mount_service: open only paths named on the command line Bernd Schubert via B4 Relay
@ 2026-09-25 22:05   ` Darrick J. Wong
  2026-09-28  9:19     ` Bernd Schubert
  0 siblings, 1 reply; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:05 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:30AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> In a service mount, the fuse server runs as a systemd service in a
> sandbox that has no access to the user's files. The user runs mount,
> which starts fuservicemount3, a setuid-root helper. The fuse server
> sends requests to the helper over a socket. With an OPEN request, the
> server asks the helper to open its backing file, for example the disk
> image named on the mount command line. The helper opens the file with
> the user's credentials and passes the file descriptor to the server.
> fusermount3 opens nothing for the fuse server except /dev/fuse; the
> server runs as the user and opens its own files.
> 
> The helper opened any path the server sent. An attacker who controlled
> the server could use this to read every file the user can read, for
> example ~/.ssh/id_ed25519, and the sandbox did not prevent it. The
> helper now compares the requested path with the arguments it was
> started with. For "mount -t fuse.service_ll /srv/disk.img /mnt", these
> include "/srv/disk.img" and "/mnt". The helper opens the path only if
> the string is equal to one of these arguments, so the server can open
> only a file that the user named when mounting. The helper already made
> the same check for the mount point.

Hmm.  In general I think it's a good idea not to let the fuse server
open anything in the mount helper's filesystem namespace that wasn't
explicitly mentioned in the CLI arguments.  However, the simple strcmp
check will exclude too much for fuse servers that receive paths via
mount options.  For instance,

$ mount -t fuse.ext4 /dev/sda1 /mnt -o journal_dev=/dev/sdb1,ro

Here the user mentions /dev/sdb1, but arg_in_cmdline() will never find
it because it's a substring of the last argv[].

--D

> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  doc/fuservicemount3.8  |  4 ++++
>  include/fuse_service.h |  5 ++++-
>  util/mount_service.c   | 29 ++++++++++++++++++++++-------
>  3 files changed, 30 insertions(+), 8 deletions(-)
> 
> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
> index aa2167cb4872..06755d7c3c4e 100644
> --- a/doc/fuservicemount3.8
> +++ b/doc/fuservicemount3.8
> @@ -19,6 +19,10 @@ Mount a filesystem using a FUSE server that runs as a socket service.
>  These servers can be contained using the platform's service management
>  framework.
>  
> +The FUSE server may ask fuservicemount3 to open files on its behalf.
> +fuservicemount3 opens only paths that appear verbatim on its command line
> +and refuses any other request with EPERM.
> +
>  The second form checks if there is a FUSE service available for the given
>  filesystem type.
>  .SH "AUTHORS"
> diff --git a/include/fuse_service.h b/include/fuse_service.h
> index d6aedea8f0f8..3954f62aa2b8 100644
> --- a/include/fuse_service.h
> +++ b/include/fuse_service.h
> @@ -139,6 +139,8 @@ int fuse_service_parse_cmdline_opts(struct fuse_args *args,
>  
>  /**
>   * Ask the mount.service helper to open a file on behalf of the fuse server.
> + * The helper refuses a path that is not verbatim on the mount command line;
> + * fuse_service_receive_file() then reports -EPERM.
>   *
>   * @param sf service context
>   * @param path the path to file
> @@ -153,7 +155,8 @@ int fuse_service_request_file(const struct fuse_service *sf, const char *path,
>  
>  /**
>   * Ask the mount.service helper to open a block device on behalf of the fuse
> - * server.
> + * server.  The path must be verbatim on the mount command line, as for a
> + * file request.
>   *
>   * @param sf service context
>   * @param path the path to file
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 7549e0b5024f..835d3d94aa4a 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -772,7 +772,8 @@ static bool arg_in_cmdline(int argc, const char * const argv[],
>  
>  static int mount_service_open_path(const struct mount_service *mo,
>  				   mode_t expected_fmt,
> -				   struct fuse_service_packet *p, size_t psz)
> +				   struct fuse_service_packet *p, size_t psz,
> +				   int argc, const char * const argv[])
>  {
>  	const struct fuse_service_open_command *oc =
>  			container_of(p, struct fuse_service_open_command, p);
> @@ -800,6 +801,16 @@ static int mount_service_open_path(const struct mount_service *mo,
>  		return mount_service_send_file_error(mo, EINVAL, oc->path);
>  	}
>  
> +	/*
> +	 * The file is opened outside the service sandbox, so only hand out
> +	 * what the user named.
> +	 */
> +	if (!arg_in_cmdline(argc, argv, oc->path)) {
> +		fprintf(stderr, "%s: %s: file must be in command line arguments\n",
> +			mo->msgtag, oc->path);
> +		return mount_service_send_file_error(mo, EPERM, oc->path);
> +	}
> +
>  	open_flags = ntohl(oc->open_flags) | O_CLOEXEC;
>  	drop_privs();
>  	fd = open(oc->path, open_flags, ntohl(oc->create_mode));
> @@ -834,16 +845,18 @@ static int mount_service_open_path(const struct mount_service *mo,
>  
>  static int mount_service_handle_open_cmd(const struct mount_service *mo,
>  					 struct fuse_service_packet *p,
> -					 size_t psz)
> +					 size_t psz, int argc,
> +					 const char * const argv[])
>  {
> -	return mount_service_open_path(mo, 0, p, psz);
> +	return mount_service_open_path(mo, 0, p, psz, argc, argv);
>  }
>  
>  static int mount_service_handle_open_bdev_cmd(const struct mount_service *mo,
>  					      struct fuse_service_packet *p,
> -					      size_t psz)
> +					      size_t psz, int argc,
> +					      const char * const argv[])
>  {
> -	return mount_service_open_path(mo, S_IFBLK, p, psz);
> +	return mount_service_open_path(mo, S_IFBLK, p, psz, argc, argv);
>  }
>  
>  #ifdef HAVE_NEW_MOUNT_API
> @@ -1838,10 +1851,12 @@ int mount_service_main(int argc, char *argv[])
>  
>  		switch (ntohl(p->magic)) {
>  		case FUSE_SERVICE_OPEN_CMD:
> -			ret = mount_service_handle_open_cmd(&mo, p, sz);
> +			ret = mount_service_handle_open_cmd(&mo, p, sz,
> +					argc, (const char * const *)argv);
>  			break;
>  		case FUSE_SERVICE_OPEN_BDEV_CMD:
> -			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
> +			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz,
> +					argc, (const char * const *)argv);
>  			break;
>  		case FUSE_SERVICE_FSOPEN_CMD:
>  			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request
  2026-09-24 22:23 ` [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request Bernd Schubert via B4 Relay
@ 2026-09-25 22:07   ` Darrick J. Wong
  2026-09-28  9:21     ` Bernd Schubert
  0 siblings, 1 reply; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:07 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:31AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> The fuse server sends fuservicemount3 a series of requests: OPEN for
> its backing file, MNTPT to name the mount point, then MOUNT. The server
> chooses the order. For a directory mount point, attach_to_mountpoint()
> changes the working directory of the helper to the mount point. The
> helper then mounts on ".", so a rename of the path cannot redirect the
> mount.
> 
> A relative path in an OPEN request after MNTPT resolved inside the
> mount point. For "fuservicemount3 disk.img /mnt -t fuse.service_ll",
> an OPEN of "disk.img" matched the command line argument, but the helper
> opened /mnt/disk.img, not disk.img in the user's working directory. The
> helper now refuses OPEN once the mount point is set. No in-tree server
> sends OPEN after MNTPT.

How about absolute paths?  The resolution of those remain the same after
the cwd changes.

--D

> 
> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  doc/fuservicemount3.8  | 1 +
>  include/fuse_service.h | 3 ++-
>  util/mount_service.c   | 7 +++++++
>  3 files changed, 10 insertions(+), 1 deletion(-)
> 
> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
> index 06755d7c3c4e..9b8c752b3bb9 100644
> --- a/doc/fuservicemount3.8
> +++ b/doc/fuservicemount3.8
> @@ -22,6 +22,7 @@ framework.
>  The FUSE server may ask fuservicemount3 to open files on its behalf.
>  fuservicemount3 opens only paths that appear verbatim on its command line
>  and refuses any other request with EPERM.
> +Requests after the server has sent the mount point are refused as well.
>  
>  The second form checks if there is a FUSE service available for the given
>  filesystem type.
> diff --git a/include/fuse_service.h b/include/fuse_service.h
> index 3954f62aa2b8..56f40a44b670 100644
> --- a/include/fuse_service.h
> +++ b/include/fuse_service.h
> @@ -139,7 +139,8 @@ int fuse_service_parse_cmdline_opts(struct fuse_args *args,
>  
>  /**
>   * Ask the mount.service helper to open a file on behalf of the fuse server.
> - * The helper refuses a path that is not verbatim on the mount command line;
> + * The helper refuses a path that is not verbatim on the mount command line,
> + * and any request made after the mount point was sent;
>   * fuse_service_receive_file() then reports -EPERM.
>   *
>   * @param sf service context
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 835d3d94aa4a..1f25dcde0349 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -801,6 +801,13 @@ static int mount_service_open_path(const struct mount_service *mo,
>  		return mount_service_send_file_error(mo, EINVAL, oc->path);
>  	}
>  
> +	/* After fchdir to the mountpoint, a relative path resolves there */
> +	if (mo->mountpoint) {
> +		fprintf(stderr, "%s: %s: files must be requested before the mount point\n",
> +			mo->msgtag, oc->path);
> +		return mount_service_send_file_error(mo, EPERM, oc->path);
> +	}
> +
>  	/*
>  	 * The file is opened outside the service sandbox, so only hand out
>  	 * what the user named.
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 04/10] util: give fuservicemount3 an absolute build-tree runpath
  2026-09-24 22:23 ` [PATCH 04/10] util: give fuservicemount3 an absolute build-tree runpath Bernd Schubert via B4 Relay
@ 2026-09-25 22:09   ` Darrick J. Wong
  0 siblings, 0 replies; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:09 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:32AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> The dynamic loader ignores $ORIGIN runpaths and LD_LIBRARY_PATH for a
> setuid program. A build-tree fuservicemount3 made setuid, for example by
> "run-tests.py --setuid-helpers", then failed with "libfuse3.so.4: cannot
> open shared object file", or loaded the libfuse3.so.4 of the system.
> meson removes build_rpath on install, so installed binaries do not change.

That's a clever trick, I will have to use that. :)

> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  util/meson.build | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/util/meson.build b/util/meson.build
> index 28052a65536f..bc266776e153 100644
> --- a/util/meson.build
> +++ b/util/meson.build
> @@ -22,6 +22,8 @@ if private_cfg.get('HAVE_SERVICEMOUNT', false)
>               link_with: [ libfuse ],
>               install: true,
>               install_dir: get_option('sbindir'),
> +             # A setuid run ignores the $ORIGIN runpath meson sets
> +             build_rpath: join_paths(meson.project_build_root(), 'lib'),
>               c_args: ['-DFUSE_USE_VERSION=319'] + mount_service_cflags)
>  endif
>  
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 05/10] mount.fuse: free the options on the service mount return path
  2026-09-24 22:23 ` [PATCH 05/10] mount.fuse: free the options on the service mount return path Bernd Schubert via B4 Relay
@ 2026-09-25 22:10   ` Darrick J. Wong
  0 siblings, 0 replies; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:10 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:33AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> These leaks were detected by the new tests.
> 
> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

Looks fine, though those leaks won't live for long...
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  util/mount.fuse.c | 42 ++++++++++++++++++++++++++----------------
>  1 file changed, 26 insertions(+), 16 deletions(-)
> 
> diff --git a/util/mount.fuse.c b/util/mount.fuse.c
> index c67a0c2b7f2f..1414265fd274 100644
> --- a/util/mount.fuse.c
> +++ b/util/mount.fuse.c
> @@ -56,9 +56,7 @@
>  #endif
>  
>  #include "fuse.h"
> -#ifdef HAVE_SERVICEMOUNT
> -# include "mount_service.h"
> -#endif
> +#include "mount_service.h"
>  
>  static char *progname;
>  
> @@ -396,6 +394,18 @@ out:
>  	fuse_opt_free_args(&args);
>  	return ret;
>  }
> +#else
> +static int try_service_main(const char *argv0, const char *fstype,
> +			    const char *source, const char *mountpoint,
> +			    const char *options)
> +{
> +	(void)argv0;
> +	(void)fstype;
> +	(void)source;
> +	(void)mountpoint;
> +	(void)options;
> +	return MOUNT_SERVICE_FALLBACK_NEEDED;
> +}
>  #endif
>  
>  int main(int argc, char *argv[])
> @@ -415,6 +425,7 @@ int main(int argc, char *argv[])
>  	int fuse_fd = 0;
>  	int drop_privileges = 0;
>  	char *dev_fd_mountpoint = NULL;
> +	int ret;
>  
>  	progname = argv[0];
>  	basename = strrchr(argv[0], '/');
> @@ -608,19 +619,17 @@ int main(int argc, char *argv[])
>  	}
>  #endif
>  
> -#ifdef HAVE_SERVICEMOUNT
>  	/*
>  	 * Now that we know the desired filesystem type, see if we can find
>  	 * a socket service implementing that, if we haven't selected any weird
>  	 * options that would prevent that.
>  	 */
>  	if (!pass_fuse_fd && !(setuid_name && setuid_name[0])) {
> -		int ret = try_service_main(argv[0], type, source, mountpoint,
> -					   options);
> +		ret = try_service_main(argv[0], type, source, mountpoint,
> +				       options);
>  		if (ret != MOUNT_SERVICE_FALLBACK_NEEDED)
> -			return ret;
> +			goto out;
>  	}
> -#endif
>  
>  	add_arg(&command, type);
>  	if (source)
> @@ -631,17 +640,18 @@ int main(int argc, char *argv[])
>  		add_arg(&command, options);
>  	}
>  
> +	execl("/bin/sh", "/bin/sh", "-c", command, NULL);
> +	fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
> +		strerror(errno));
> +	ret = 1;
> +
> +out:
> +	if (pass_fuse_fd)
> +		close(fuse_fd);
>  	free(options);
>  	free(dev_fd_mountpoint);
>  	free(dup_source);
>  	free(setuid_name);
> -
> -	execl("/bin/sh", "/bin/sh", "-c", command, NULL);
> -	fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
> -		strerror(errno));
> -	
> -	if (pass_fuse_fd)
> -		close(fuse_fd);
>  	free(command);
> -	return 1;
> +	return ret;
>  }
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 06/10] example/single_file: take no sector size from a regular backing file
  2026-09-24 22:23 ` [PATCH 06/10] example/single_file: take no sector size from a regular backing file Bernd Schubert via B4 Relay
@ 2026-09-25 22:13   ` Darrick J. Wong
  0 siblings, 0 replies; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:13 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:34AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> single_file_configure() took the sector size of a regular backing file
> from st_blksize, and refused to start when it was larger than the page
> size. For a regular file st_blksize is only a preferred I/O size, and
> the file is not opened with O_DIRECT, so any offset works. NFS reports
> 1 MiB there, so service_ll and service_hl failed on an image on NFS
> with "lba size 1048576 smaller than blocksize 4096".
> 
> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  example/single_file.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/example/single_file.c b/example/single_file.c
> index 59dbc6bbac5e..4260ecf3c5f5 100644
> --- a/example/single_file.c
> +++ b/example/single_file.c
> @@ -887,7 +887,8 @@ int single_file_configure(const char *device, const char *filename)
>  		perror(device);
>  		return -1;
>  	}
> -	lbasize = stbuf.st_blksize;
> +	/* A regular file takes any offset; its st_blksize is only an I/O hint */
> +	lbasize = S_ISBLK(stbuf.st_mode) ? stbuf.st_blksize : 1;

You could just flow that into the S_ISBLK clause below, but I don't feel
strongly either way.  The justification is reasonable so
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D


>  	backing_size = stbuf.st_size;
>  
>  	if (S_ISBLK(stbuf.st_mode)) {
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 09/10] test: mount the service examples through fuservicemount3
  2026-09-24 22:23 ` [PATCH 09/10] test: mount the service examples through fuservicemount3 Bernd Schubert via B4 Relay
@ 2026-09-25 22:25   ` Darrick J. Wong
  2026-09-28  9:27     ` Bernd Schubert
  0 siblings, 1 reply; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:25 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:37AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> No test ran service_ll, service_hl or null in service mode. No test
> checked that mount.fuse3 mounts through a listening service socket, and
> runs the filesystem program when the socket refuses the connection.
> 
> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

These are a reasonable set of simple tests to make sure that service
discovery and startup work correctly.  Thanks for writing these tests;
I suppose I had become over-reliant on testing all this via fstests.

Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  test/cases/lib/service-example.sh      | 62 ++++++++++++++++++++++++++++++++++
>  test/cases/mount/service-hl.sh         |  7 ++++
>  test/cases/mount/service-ll.sh         |  7 ++++
>  test/cases/mount/service-mount-fuse.sh | 31 +++++++++++++++++
>  test/cases/mount/service-null.sh       | 33 ++++++++++++++++++
>  5 files changed, 140 insertions(+)
> 
> diff --git a/test/cases/lib/service-example.sh b/test/cases/lib/service-example.sh
> new file mode 100644
> index 000000000000..878b379b8b16
> --- /dev/null
> +++ b/test/cases/lib/service-example.sh
> @@ -0,0 +1,62 @@
> +# lib/service-example.sh - body for the service_ll / service_hl cases.
> +#
> +# Caller sets FS_NAME and LAUNCH before sourcing:
> +#   FS_NAME   service_ll | service_hl
> +#   LAUNCH    fuservicemount3 | mount_fuse
> +#
> +# The file the example serves has to read back as the image, and what is
> +# written through the mount has to reach the image.
> +
> +_fuse_no_mount_needed=1
> +. "$TEST_LIB/common.sh"
> +
> +# Before the gates: a misspelled FS_NAME would otherwise skip as "not built"
> +case ${FS_NAME:-} in
> +service_ll | service_hl) ;;
> +*) _fail "unknown FS_NAME '${FS_NAME:-}'" ;;
> +esac
> +case ${LAUNCH:-} in
> +fuservicemount3 | mount_fuse) ;;
> +*) _fail "unknown LAUNCH '${LAUNCH:-}'" ;;
> +esac
> +
> +_require_linux "fuservicemount3"
> +_require_root
> +_require_fuse_device
> +_require_binary util/fuservicemount3
> +_require_binary "example/$FS_NAME"
> +[ "$LAUNCH" != mount_fuse ] || _require_binary util/mount.fuse3
> +
> +. "$TEST_LIB/service.sh"
> +
> +service_setup "test-$FS_NAME-$$"
> +img=$TEST_SRC/img
> +old=$TEST_TMP/old
> +new=$TEST_TMP/new
> +
> +# The size has to be a multiple of the page size
> +head -c 1048576 /dev/urandom >"$old"
> +head -c 1048576 /dev/urandom >"$new"
> +cp "$old" "$img"
> +
> +service_start "$TEST_LOGDIR/fs-$FS_NAME.out" "$FUSE_EXAMPLE_DIR/$FS_NAME"
> +case $LAUNCH in
> +fuservicemount3)
> +	"$FUSE_UTIL_DIR/fuservicemount3" "$img" "$TEST_MNT" \
> +		-t "fuse.$service_subtype" ||
> +		_fail "fuservicemount3 did not mount $FS_NAME"
> +	;;
> +mount_fuse)
> +	"$FUSE_UTIL_DIR/mount.fuse3" "$service_subtype#$img" "$TEST_MNT" ||
> +		_fail "mount.fuse3 did not mount $FS_NAME"
> +	;;
> +esac
> +
> +_assert_fstype "$TEST_MNT" "fuse.$service_subtype" fuse
> +_assert_file_eq "$TEST_MNT/single_file" "$old"
> +dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync status=none
> +
> +umount "$TEST_MNT"
> +service_wait_exit
> +_assert_eq "$service_rc" 0 "$FS_NAME exit status"
> +_assert_file_eq "$img" "$new"
> diff --git a/test/cases/mount/service-hl.sh b/test/cases/mount/service-hl.sh
> new file mode 100755
> index 000000000000..6db3add3a5d1
> --- /dev/null
> +++ b/test/cases/mount/service-hl.sh
> @@ -0,0 +1,7 @@
> +#!/usr/bin/env bash
> +# GROUP: mount
> +
> +FS_NAME=service_hl
> +LAUNCH=fuservicemount3
> +
> +. "$TEST_LIB/service-example.sh"
> diff --git a/test/cases/mount/service-ll.sh b/test/cases/mount/service-ll.sh
> new file mode 100755
> index 000000000000..036553889f7a
> --- /dev/null
> +++ b/test/cases/mount/service-ll.sh
> @@ -0,0 +1,7 @@
> +#!/usr/bin/env bash
> +# GROUP: mount
> +
> +FS_NAME=service_ll
> +LAUNCH=fuservicemount3
> +
> +. "$TEST_LIB/service-example.sh"
> diff --git a/test/cases/mount/service-mount-fuse.sh b/test/cases/mount/service-mount-fuse.sh
> new file mode 100755
> index 000000000000..6bc95240c772
> --- /dev/null
> +++ b/test/cases/mount/service-mount-fuse.sh
> @@ -0,0 +1,31 @@
> +#!/usr/bin/env bash
> +# GROUP: mount
> +#
> +# mount.fuse3 mounts through the service when its socket listens, and execs a
> +# program named after the type when the socket refuses the connection.
> +
> +_fuse_no_mount_needed=1
> +. "$TEST_LIB/common.sh"
> +
> +_require_binary example/hello
> +
> +FS_NAME=service_ll
> +LAUNCH=mount_fuse
> +
> +. "$TEST_LIB/service-example.sh"
> +
> +fallback=test-fallback-$$
> +service_setup "$fallback"
> +
> +# Bound but never listening, so connect() gets ECONNREFUSED
> +python3 -c 'import socket, sys
> +socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET).bind(sys.argv[1])' \
> +	"$service_sock"
> +
> +mkdir "$TEST_TMP/bin"
> +ln -s "$FUSE_EXAMPLE_DIR/hello" "$TEST_TMP/bin/$fallback"
> +export PATH="$TEST_TMP/bin:$PATH"
> +
> +fuse_mount_helper "$fallback" >/dev/null
> +_assert_listdir "$TEST_MNT" hello
> +fuse_umount
> diff --git a/test/cases/mount/service-null.sh b/test/cases/mount/service-null.sh
> new file mode 100755
> index 000000000000..b89107ae3a40
> --- /dev/null
> +++ b/test/cases/mount/service-null.sh
> @@ -0,0 +1,33 @@
> +#!/usr/bin/env bash
> +# GROUP: mount
> +#
> +# The null example mounts through fuservicemount3 on a regular file.
> +# null has no backing file and mounts on a regular file, so it does not use
> +# lib/service-example.sh.
> +
> +_fuse_no_mount_needed=1
> +. "$TEST_LIB/common.sh"
> +
> +_require_linux "fuservicemount3"
> +_require_root
> +_require_fuse_device
> +_require_binary util/fuservicemount3
> +_require_binary example/null
> +
> +. "$TEST_LIB/service.sh"
> +
> +service_setup "test-null-$$"
> +mnt_file=$TEST_TMP/file
> +_at_exit "umount -l '$mnt_file' 2>/dev/null"
> +printf 'dummy' >"$mnt_file"
> +
> +service_start "$TEST_LOGDIR/fs-null.out" "$FUSE_EXAMPLE_DIR/null"
> +# null takes no source, only the mount point
> +"$FUSE_UTIL_DIR/fuservicemount3" "$mnt_file" -t "fuse.$service_subtype" ||
> +	_fail "fuservicemount3 did not mount null"
> +
> +_check fuse_test_null_roundtrip "$mnt_file"
> +
> +umount "$mnt_file"
> +service_wait_exit
> +_assert_eq "$service_rc" 0 "null exit status"
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 10/10] test: run mkfs.ext4 through the service examples
  2026-09-24 22:23 ` [PATCH 10/10] test: run mkfs.ext4 through the service examples Bernd Schubert via B4 Relay
@ 2026-09-25 22:27   ` Darrick J. Wong
  2026-09-28  9:30     ` Bernd Schubert
  0 siblings, 1 reply; 23+ messages in thread
From: Darrick J. Wong @ 2026-09-25 22:27 UTC (permalink / raw)
  To: bernd; +Cc: fuse-devel

On Fri, Sep 25, 2026 at 12:23:38AM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> The byte comparison in the service example tests does not show that a
> real filesystem tool works on the file service_ll and service_hl serve.
> mkfs.ext4 writes a filesystem through the mount, and e2fsck checks the
> image after umount.

Ooh, a file IO path test too!

> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  example/service_ll.c                |  5 ++++
>  test/cases/lib/service-example.sh   | 54 ++++++++++++++++++++++++++++++-------
>  test/cases/mount/service-hl-mkfs.sh |  8 ++++++
>  test/cases/mount/service-ll-mkfs.sh |  8 ++++++
>  4 files changed, 66 insertions(+), 9 deletions(-)
> 
> diff --git a/example/service_ll.c b/example/service_ll.c
> index fd43c40fc15b..7baf47905a33 100644
> --- a/example/service_ll.c
> +++ b/example/service_ll.c
> @@ -43,6 +43,11 @@
>   *
>   *     mount -t fuse.service_ll /dev/sda /mnt
>   *
> + * /mnt/single_file then holds the bytes of /dev/sda, so a filesystem can be
> + * created on it:
> + *
> + *     mkfs.ext4 /mnt/single_file
> + *
>   * ## Source code ##
>   * \include service_ll.c
>   * \include service_ll.socket
> diff --git a/test/cases/lib/service-example.sh b/test/cases/lib/service-example.sh
> index 878b379b8b16..4d7e0b27f3b9 100644
> --- a/test/cases/lib/service-example.sh
> +++ b/test/cases/lib/service-example.sh
> @@ -1,15 +1,19 @@
>  # lib/service-example.sh - body for the service_ll / service_hl cases.
>  #
> -# Caller sets FS_NAME and LAUNCH before sourcing:
> +# Caller sets FS_NAME and LAUNCH, and optionally CHECK, before sourcing:
>  #   FS_NAME   service_ll | service_hl
>  #   LAUNCH    fuservicemount3 | mount_fuse
> +#   CHECK     bytes (default) | mkfs
>  #
> -# The file the example serves has to read back as the image, and what is
> -# written through the mount has to reach the image.
> +# bytes: the file the example serves has to read back as the image, and what
> +# is written through the mount has to reach the image.
> +# mkfs: mkfs.ext4 on that file has to leave an image e2fsck accepts.
>  
>  _fuse_no_mount_needed=1
>  . "$TEST_LIB/common.sh"
>  
> +CHECK=${CHECK:-bytes}
> +
>  # Before the gates: a misspelled FS_NAME would otherwise skip as "not built"
>  case ${FS_NAME:-} in
>  service_ll | service_hl) ;;
> @@ -19,6 +23,10 @@ case ${LAUNCH:-} in
>  fuservicemount3 | mount_fuse) ;;
>  *) _fail "unknown LAUNCH '${LAUNCH:-}'" ;;
>  esac
> +case $CHECK in
> +bytes | mkfs) ;;
> +*) _fail "unknown CHECK '$CHECK'" ;;
> +esac
>  
>  _require_linux "fuservicemount3"
>  _require_root
> @@ -26,6 +34,10 @@ _require_fuse_device
>  _require_binary util/fuservicemount3
>  _require_binary "example/$FS_NAME"
>  [ "$LAUNCH" != mount_fuse ] || _require_binary util/mount.fuse3
> +if [ "$CHECK" = mkfs ]; then
> +	_require_prog mkfs.ext4
> +	_require_prog e2fsck
> +fi
>  
>  . "$TEST_LIB/service.sh"
>  
> @@ -35,9 +47,16 @@ old=$TEST_TMP/old
>  new=$TEST_TMP/new
>  
>  # The size has to be a multiple of the page size
> -head -c 1048576 /dev/urandom >"$old"
> -head -c 1048576 /dev/urandom >"$new"
> -cp "$old" "$img"
> +case $CHECK in
> +bytes)
> +	head -c 1048576 /dev/urandom >"$old"
> +	head -c 1048576 /dev/urandom >"$new"
> +	cp "$old" "$img"
> +	;;
> +mkfs)
> +	truncate -s 64M "$img"
> +	;;
> +esac
>  
>  service_start "$TEST_LOGDIR/fs-$FS_NAME.out" "$FUSE_EXAMPLE_DIR/$FS_NAME"
>  case $LAUNCH in
> @@ -53,10 +72,27 @@ mount_fuse)
>  esac
>  
>  _assert_fstype "$TEST_MNT" "fuse.$service_subtype" fuse
> -_assert_file_eq "$TEST_MNT/single_file" "$old"
> -dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync status=none
> +case $CHECK in
> +bytes)
> +	_assert_file_eq "$TEST_MNT/single_file" "$old"
> +	dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync \
> +		status=none
> +	;;
> +mkfs)
> +	# -F: single_file is a regular file, not a block device
> +	mkfs.ext4 -F -q "$TEST_MNT/single_file" ||
> +		_fail "mkfs.ext4 through $FS_NAME failed"

You might consider adding a -D <path> argument to mkfs.ext4 so that it
will copy a directory tree into the filesystem image.  That way you're
testing block overwrites.

That said, if you're satisified with writing just the superblock and
log, that's fine with me too.

Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> +	;;
> +esac
>  
>  umount "$TEST_MNT"
>  service_wait_exit
>  _assert_eq "$service_rc" 0 "$FS_NAME exit status"
> -_assert_file_eq "$img" "$new"
> +case $CHECK in
> +bytes)
> +	_assert_file_eq "$img" "$new"
> +	;;
> +mkfs)
> +	e2fsck -fn "$img" || _fail "e2fsck found errors in $img"
> +	;;
> +esac
> diff --git a/test/cases/mount/service-hl-mkfs.sh b/test/cases/mount/service-hl-mkfs.sh
> new file mode 100755
> index 000000000000..af552818cd90
> --- /dev/null
> +++ b/test/cases/mount/service-hl-mkfs.sh
> @@ -0,0 +1,8 @@
> +#!/usr/bin/env bash
> +# GROUP: mount
> +
> +FS_NAME=service_hl
> +LAUNCH=fuservicemount3
> +CHECK=mkfs
> +
> +. "$TEST_LIB/service-example.sh"
> diff --git a/test/cases/mount/service-ll-mkfs.sh b/test/cases/mount/service-ll-mkfs.sh
> new file mode 100755
> index 000000000000..9908937010de
> --- /dev/null
> +++ b/test/cases/mount/service-ll-mkfs.sh
> @@ -0,0 +1,8 @@
> +#!/usr/bin/env bash
> +# GROUP: mount
> +
> +FS_NAME=service_ll
> +LAUNCH=fuservicemount3
> +CHECK=mkfs
> +
> +. "$TEST_LIB/service-example.sh"
> 
> -- 
> 2.53.0
> 
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 02/10] mount_service: open only paths named on the command line
  2026-09-25 22:05   ` Darrick J. Wong
@ 2026-09-28  9:19     ` Bernd Schubert
  0 siblings, 0 replies; 23+ messages in thread
From: Bernd Schubert @ 2026-09-28  9:19 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: fuse-devel



On 9/26/26 00:05, Darrick J. Wong wrote:
> On Fri, Sep 25, 2026 at 12:23:30AM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> In a service mount, the fuse server runs as a systemd service in a
>> sandbox that has no access to the user's files. The user runs mount,
>> which starts fuservicemount3, a setuid-root helper. The fuse server
>> sends requests to the helper over a socket. With an OPEN request, the
>> server asks the helper to open its backing file, for example the disk
>> image named on the mount command line. The helper opens the file with
>> the user's credentials and passes the file descriptor to the server.
>> fusermount3 opens nothing for the fuse server except /dev/fuse; the
>> server runs as the user and opens its own files.
>>
>> The helper opened any path the server sent. An attacker who controlled
>> the server could use this to read every file the user can read, for
>> example ~/.ssh/id_ed25519, and the sandbox did not prevent it. The
>> helper now compares the requested path with the arguments it was
>> started with. For "mount -t fuse.service_ll /srv/disk.img /mnt", these
>> include "/srv/disk.img" and "/mnt". The helper opens the path only if
>> the string is equal to one of these arguments, so the server can open
>> only a file that the user named when mounting. The helper already made
>> the same check for the mount point.
> 
> Hmm.  In general I think it's a good idea not to let the fuse server
> open anything in the mount helper's filesystem namespace that wasn't
> explicitly mentioned in the CLI arguments.  However, the simple strcmp
> check will exclude too much for fuse servers that receive paths via
> mount options.  For instance,
> 
> $ mount -t fuse.ext4 /dev/sda1 /mnt -o journal_dev=/dev/sdb1,ro
> 
> Here the user mentions /dev/sdb1, but arg_in_cmdline() will never find
> it because it's a substring of the last argv[].

Thank you! Next version adds more logic and an admin config option in
case the logic failed.


Thanks,
Bernd



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request
  2026-09-25 22:07   ` Darrick J. Wong
@ 2026-09-28  9:21     ` Bernd Schubert
  0 siblings, 0 replies; 23+ messages in thread
From: Bernd Schubert @ 2026-09-28  9:21 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: fuse-devel



On 9/26/26 00:07, Darrick J. Wong wrote:
> On Fri, Sep 25, 2026 at 12:23:31AM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> The fuse server sends fuservicemount3 a series of requests: OPEN for
>> its backing file, MNTPT to name the mount point, then MOUNT. The server
>> chooses the order. For a directory mount point, attach_to_mountpoint()
>> changes the working directory of the helper to the mount point. The
>> helper then mounts on ".", so a rename of the path cannot redirect the
>> mount.
>>
>> A relative path in an OPEN request after MNTPT resolved inside the
>> mount point. For "fuservicemount3 disk.img /mnt -t fuse.service_ll",
>> an OPEN of "disk.img" matched the command line argument, but the helper
>> opened /mnt/disk.img, not disk.img in the user's working directory. The
>> helper now refuses OPEN once the mount point is set. No in-tree server
>> sends OPEN after MNTPT.
> 
> How about absolute paths?  The resolution of those remain the same after
> the cwd changes.

Next version switches to openat and should handle all of that.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 09/10] test: mount the service examples through fuservicemount3
  2026-09-25 22:25   ` Darrick J. Wong
@ 2026-09-28  9:27     ` Bernd Schubert
  0 siblings, 0 replies; 23+ messages in thread
From: Bernd Schubert @ 2026-09-28  9:27 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: fuse-devel



On 9/26/26 00:25, Darrick J. Wong wrote:
> On Fri, Sep 25, 2026 at 12:23:37AM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> No test ran service_ll, service_hl or null in service mode. No test
>> checked that mount.fuse3 mounts through a listening service socket, and
>> runs the filesystem program when the socket refuses the connection.
>>
>> Assisted-by: LLM
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> 
> These are a reasonable set of simple tests to make sure that service
> discovery and startup work correctly.  Thanks for writing these tests;
> I suppose I had become over-reliant on testing all this via fstests.

Back when wrote it tests were still based on python-tests, I think (and
hope) that the new way using shell scripts makes it easier to write tests.

Thanks for review!


^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 10/10] test: run mkfs.ext4 through the service examples
  2026-09-25 22:27   ` Darrick J. Wong
@ 2026-09-28  9:30     ` Bernd Schubert
  0 siblings, 0 replies; 23+ messages in thread
From: Bernd Schubert @ 2026-09-28  9:30 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: fuse-devel



On 9/26/26 00:27, Darrick J. Wong wrote:
> On Fri, Sep 25, 2026 at 12:23:38AM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> The byte comparison in the service example tests does not show that a
>> real filesystem tool works on the file service_ll and service_hl serve.
>> mkfs.ext4 writes a filesystem through the mount, and e2fsck checks the
>> image after umount.
> 
> Ooh, a file IO path test too!
> 
>> Assisted-by: LLM
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
>> ---
>>  example/service_ll.c                |  5 ++++
>>  test/cases/lib/service-example.sh   | 54 ++++++++++++++++++++++++++++++-------
>>  test/cases/mount/service-hl-mkfs.sh |  8 ++++++
>>  test/cases/mount/service-ll-mkfs.sh |  8 ++++++
>>  4 files changed, 66 insertions(+), 9 deletions(-)
>>
>> diff --git a/example/service_ll.c b/example/service_ll.c
>> index fd43c40fc15b..7baf47905a33 100644
>> --- a/example/service_ll.c
>> +++ b/example/service_ll.c
>> @@ -43,6 +43,11 @@
>>   *
>>   *     mount -t fuse.service_ll /dev/sda /mnt
>>   *
>> + * /mnt/single_file then holds the bytes of /dev/sda, so a filesystem can be
>> + * created on it:
>> + *
>> + *     mkfs.ext4 /mnt/single_file
>> + *
>>   * ## Source code ##
>>   * \include service_ll.c
>>   * \include service_ll.socket
>> diff --git a/test/cases/lib/service-example.sh b/test/cases/lib/service-example.sh
>> index 878b379b8b16..4d7e0b27f3b9 100644
>> --- a/test/cases/lib/service-example.sh
>> +++ b/test/cases/lib/service-example.sh
>> @@ -1,15 +1,19 @@
>>  # lib/service-example.sh - body for the service_ll / service_hl cases.
>>  #
>> -# Caller sets FS_NAME and LAUNCH before sourcing:
>> +# Caller sets FS_NAME and LAUNCH, and optionally CHECK, before sourcing:
>>  #   FS_NAME   service_ll | service_hl
>>  #   LAUNCH    fuservicemount3 | mount_fuse
>> +#   CHECK     bytes (default) | mkfs
>>  #
>> -# The file the example serves has to read back as the image, and what is
>> -# written through the mount has to reach the image.
>> +# bytes: the file the example serves has to read back as the image, and what
>> +# is written through the mount has to reach the image.
>> +# mkfs: mkfs.ext4 on that file has to leave an image e2fsck accepts.
>>  
>>  _fuse_no_mount_needed=1
>>  . "$TEST_LIB/common.sh"
>>  
>> +CHECK=${CHECK:-bytes}
>> +
>>  # Before the gates: a misspelled FS_NAME would otherwise skip as "not built"
>>  case ${FS_NAME:-} in
>>  service_ll | service_hl) ;;
>> @@ -19,6 +23,10 @@ case ${LAUNCH:-} in
>>  fuservicemount3 | mount_fuse) ;;
>>  *) _fail "unknown LAUNCH '${LAUNCH:-}'" ;;
>>  esac
>> +case $CHECK in
>> +bytes | mkfs) ;;
>> +*) _fail "unknown CHECK '$CHECK'" ;;
>> +esac
>>  
>>  _require_linux "fuservicemount3"
>>  _require_root
>> @@ -26,6 +34,10 @@ _require_fuse_device
>>  _require_binary util/fuservicemount3
>>  _require_binary "example/$FS_NAME"
>>  [ "$LAUNCH" != mount_fuse ] || _require_binary util/mount.fuse3
>> +if [ "$CHECK" = mkfs ]; then
>> +	_require_prog mkfs.ext4
>> +	_require_prog e2fsck
>> +fi
>>  
>>  . "$TEST_LIB/service.sh"
>>  
>> @@ -35,9 +47,16 @@ old=$TEST_TMP/old
>>  new=$TEST_TMP/new
>>  
>>  # The size has to be a multiple of the page size
>> -head -c 1048576 /dev/urandom >"$old"
>> -head -c 1048576 /dev/urandom >"$new"
>> -cp "$old" "$img"
>> +case $CHECK in
>> +bytes)
>> +	head -c 1048576 /dev/urandom >"$old"
>> +	head -c 1048576 /dev/urandom >"$new"
>> +	cp "$old" "$img"
>> +	;;
>> +mkfs)
>> +	truncate -s 64M "$img"
>> +	;;
>> +esac
>>  
>>  service_start "$TEST_LOGDIR/fs-$FS_NAME.out" "$FUSE_EXAMPLE_DIR/$FS_NAME"
>>  case $LAUNCH in
>> @@ -53,10 +72,27 @@ mount_fuse)
>>  esac
>>  
>>  _assert_fstype "$TEST_MNT" "fuse.$service_subtype" fuse
>> -_assert_file_eq "$TEST_MNT/single_file" "$old"
>> -dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync status=none
>> +case $CHECK in
>> +bytes)
>> +	_assert_file_eq "$TEST_MNT/single_file" "$old"
>> +	dd if="$new" of="$TEST_MNT/single_file" bs=64k conv=notrunc,fsync \
>> +		status=none
>> +	;;
>> +mkfs)
>> +	# -F: single_file is a regular file, not a block device
>> +	mkfs.ext4 -F -q "$TEST_MNT/single_file" ||
>> +		_fail "mkfs.ext4 through $FS_NAME failed"
> 
> You might consider adding a -D <path> argument to mkfs.ext4 so that it
> will copy a directory tree into the filesystem image.  That way you're
> testing block overwrites.
> 
> That said, if you're satisified with writing just the superblock and
> log, that's fine with me too.

Nice suggestion, added in.

Thanks,
Bernd

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2026-09-28  9:30 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-24 22:23 [PATCH 00/10] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
2026-09-24 22:23 ` [PATCH 01/10] mount_service: move the command line check into arg_in_cmdline() Bernd Schubert via B4 Relay
2026-09-25 21:40   ` Darrick J. Wong
2026-09-24 22:23 ` [PATCH 02/10] mount_service: open only paths named on the command line Bernd Schubert via B4 Relay
2026-09-25 22:05   ` Darrick J. Wong
2026-09-28  9:19     ` Bernd Schubert
2026-09-24 22:23 ` [PATCH 03/10] mount_service: refuse OPEN after the MNTPT request Bernd Schubert via B4 Relay
2026-09-25 22:07   ` Darrick J. Wong
2026-09-28  9:21     ` Bernd Schubert
2026-09-24 22:23 ` [PATCH 04/10] util: give fuservicemount3 an absolute build-tree runpath Bernd Schubert via B4 Relay
2026-09-25 22:09   ` Darrick J. Wong
2026-09-24 22:23 ` [PATCH 05/10] mount.fuse: free the options on the service mount return path Bernd Schubert via B4 Relay
2026-09-25 22:10   ` Darrick J. Wong
2026-09-24 22:23 ` [PATCH 06/10] example/single_file: take no sector size from a regular backing file Bernd Schubert via B4 Relay
2026-09-25 22:13   ` Darrick J. Wong
2026-09-24 22:23 ` [PATCH 07/10] test: check which files fuservicemount3 opens for the server Bernd Schubert via B4 Relay
2026-09-24 22:23 ` [PATCH 08/10] test: check what fuservicemount3 refuses Bernd Schubert via B4 Relay
2026-09-24 22:23 ` [PATCH 09/10] test: mount the service examples through fuservicemount3 Bernd Schubert via B4 Relay
2026-09-25 22:25   ` Darrick J. Wong
2026-09-28  9:27     ` Bernd Schubert
2026-09-24 22:23 ` [PATCH 10/10] test: run mkfs.ext4 through the service examples Bernd Schubert via B4 Relay
2026-09-25 22:27   ` Darrick J. Wong
2026-09-28  9:30     ` Bernd Schubert

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox