Userspace generally expects APIs that return EMSGSIZE to allow for them to adjust their buffer size and retry the operation. However, the fscontext log would previously clear the message even in the EMSGSIZE case.
Given that it is very cheap for us to check whether the buffer is too small before we remove the message from the ring buffer, let's just do that instead.
Fixes: 007ec26cdc9f ("vfs: Implement logging through fs_context") Signed-off-by: Aleksa Sarai cyphar@cyphar.com --- Aleksa Sarai (2): fscontext: do not consume log entries for -EMSGSIZE case selftests/filesystems: add basic fscontext log tests
fs/fsopen.c | 22 ++-- tools/testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- tools/testing/selftests/filesystems/fclog.c | 137 +++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 9 deletions(-) --- base-commit: 66639db858112bf6b0f76677f7517643d586e575 change-id: 20250806-fscontext-log-cleanups-50f0143674ae
Best regards,
Userspace generally expects APIs that return EMSGSIZE to allow for them to adjust their buffer size and retry the operation. However, the fscontext log would previously clear the message even in the EMSGSIZE case.
Given that it is very cheap for us to check whether the buffer is too small before we remove the message from the ring buffer, let's just do that instead.
Fixes: 007ec26cdc9f ("vfs: Implement logging through fs_context") Cc: David Howells dhowells@redhat.com Cc: stable@vger.kernel.org # v5.2+ Signed-off-by: Aleksa Sarai cyphar@cyphar.com --- fs/fsopen.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/fs/fsopen.c b/fs/fsopen.c index 1aaf4cb2afb2..f5fdaa97965b 100644 --- a/fs/fsopen.c +++ b/fs/fsopen.c @@ -36,23 +36,25 @@ static ssize_t fscontext_read(struct file *file, if (ret < 0) return ret;
- if (log->head == log->tail) { - mutex_unlock(&fc->uapi_mutex); - return -ENODATA; - } + ret = -ENODATA; + if (log->head == log->tail) + goto err_unlock_nomsg;
index = log->tail & (logsize - 1); p = log->buffer[index]; + n = strlen(p); + + ret = -EMSGSIZE; + if (n > len) + goto err_unlock_nomsg; + + /* Consume the message from the queue. */ need_free = log->need_free & (1 << index); log->buffer[index] = NULL; log->need_free &= ~(1 << index); log->tail++; mutex_unlock(&fc->uapi_mutex);
- ret = -EMSGSIZE; - n = strlen(p); - if (n > len) - goto err_free; ret = -EFAULT; if (copy_to_user(_buf, p, n) != 0) goto err_free; @@ -62,6 +64,10 @@ static ssize_t fscontext_read(struct file *file, if (need_free) kfree(p); return ret; + +err_unlock_nomsg: + mutex_unlock(&fc->uapi_mutex); + return ret; }
static int fscontext_release(struct inode *inode, struct file *file)
On Wed, Aug 06, 2025 at 03:31:10PM +1000, Aleksa Sarai wrote:
Userspace generally expects APIs that return EMSGSIZE to allow for them to adjust their buffer size and retry the operation. However, the fscontext log would previously clear the message even in the EMSGSIZE case.
Given that it is very cheap for us to check whether the buffer is too small before we remove the message from the ring buffer, let's just do that instead.
Fixes: 007ec26cdc9f ("vfs: Implement logging through fs_context") Cc: David Howells dhowells@redhat.com Cc: stable@vger.kernel.org # v5.2+ Signed-off-by: Aleksa Sarai cyphar@cyphar.com
fs/fsopen.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/fs/fsopen.c b/fs/fsopen.c index 1aaf4cb2afb2..f5fdaa97965b 100644 --- a/fs/fsopen.c +++ b/fs/fsopen.c @@ -36,23 +36,25 @@ static ssize_t fscontext_read(struct file *file, if (ret < 0) return ret;
- if (log->head == log->tail) {
mutex_unlock(&fc->uapi_mutex);
return -ENODATA;
- }
- ret = -ENODATA;
- if (log->head == log->tail)
goto err_unlock_nomsg;
index = log->tail & (logsize - 1); p = log->buffer[index];
- n = strlen(p);
- ret = -EMSGSIZE;
- if (n > len)
goto err_unlock_nomsg;
FWIW, I would rather turn that into a helper taking log and len and returning p or ERR_PTR(...); something like
static inline const char *fetch_message(struct fc_log *log, size_t len, bool *need_free) { int index = log->tail & (ARRAY_SIZE(log->buffer) - 1); const char *p = log->buffer[index];
if (unlikely(log->head == log->tail)) return ERR_PTR(-ENODATA);
n = strlen(p); if (unlikely(n > len)) return ERR_PTR(-EMSGSIZE);
log->buffer[index] = NULL; *need_free = log->need_free & (1 << index); log->need_free &= ~(1 << index); log->tail++;
return p; }
with caller being
ret = mutex_lock_interruptible(&fc->uapi_mutex); if (ret < 0) return ret; p = fetch_message(log, len, &need_free); mutex_unlock(&fc->uapi_mutex); if (IS_ERR(p)) return PTR_ERR(p); n = strlen(p); if (copy_to_user(_buf, p, n)) n = -EFAULT; if (need_free) kfree(p); return n;
and that's it.
Signed-off-by: Aleksa Sarai cyphar@cyphar.com --- tools/testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- tools/testing/selftests/filesystems/fclog.c | 137 +++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index fcbdb1297e24..64ac0dfa46b7 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only dnotify_test devpts_pts +fclog file_stressor anon_inode_test kernfs_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 73d4650af1a5..85427d7f19b9 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0
CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test +TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog TEST_GEN_PROGS_EXTENDED := dnotify_test
include ../lib.mk diff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/selftests/filesystems/fclog.c new file mode 100644 index 000000000000..2bdb6d6a1ff1 --- /dev/null +++ b/tools/testing/selftests/filesystems/fclog.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: Aleksa Sarai cyphar@cyphar.com + * Copyright (C) 2025 SUSE LLC. + */ + +#include <assert.h> +#include <errno.h> +#include <sched.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/prctl.h> + +#include "../kselftest_harness.h" + +#define ASSERT_ERRNO(expected, _t, seen) \ + __EXPECT(expected, #expected, \ + ({__typeof__(seen) _tmp_seen = (seen); \ + _tmp_seen >= 0 ? _tmp_seen : -errno; }), #seen, _t, 1) + +#define ASSERT_ERRNO_EQ(expected, seen) \ + ASSERT_ERRNO(expected, ==, seen) + +#define ASSERT_SUCCESS(seen) \ + ASSERT_ERRNO(0, <=, seen) + +FIXTURE(ns) +{ + int host_mntns; +}; + +FIXTURE_SETUP(ns) +{ + /* Stash the old mntns. */ + self->host_mntns = open("/proc/self/ns/mnt", O_RDONLY|O_CLOEXEC); + ASSERT_SUCCESS(self->host_mntns); + + /* Create a new mount namespace and make it private. */ + ASSERT_SUCCESS(unshare(CLONE_NEWNS)); + ASSERT_SUCCESS(mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL)); +} + +FIXTURE_TEARDOWN(ns) +{ + ASSERT_SUCCESS(setns(self->host_mntns, CLONE_NEWNS)); + ASSERT_SUCCESS(close(self->host_mntns)); +} + +TEST_F(ns, fscontext_log_enodata) +{ + int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC); + ASSERT_SUCCESS(fsfd); + + /* A brand new fscontext has no log entries. */ + char buf[128] = {}; + for (int i = 0; i < 16; i++) + ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf))); + + ASSERT_SUCCESS(close(fsfd)); +} + +TEST_F(ns, fscontext_log_errorfc) +{ + int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC); + ASSERT_SUCCESS(fsfd); + + ASSERT_ERRNO_EQ(-EINVAL, fsconfig(fsfd, FSCONFIG_SET_STRING, "invalid-arg", "123", 0)); + + char buf[128] = {}; + ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf))); + + printf("fsconfig(): %s\n", buf); + ASSERT_EQ(strcmp(buf, "e tmpfs: Unknown parameter 'invalid-arg'\n"), 0); + + /* The message has been consumed. */ + ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf))); + ASSERT_SUCCESS(close(fsfd)); +} + +TEST_F(ns, fscontext_log_errorfc_after_fsmount) +{ + int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC); + ASSERT_SUCCESS(fsfd); + + ASSERT_ERRNO_EQ(-EINVAL, fsconfig(fsfd, FSCONFIG_SET_STRING, "invalid-arg", "123", 0)); + + ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)); + int mfd = fsmount(fsfd, FSMOUNT_CLOEXEC, MOUNT_ATTR_NOEXEC | MOUNT_ATTR_NOSUID); + ASSERT_SUCCESS(mfd); + ASSERT_SUCCESS(move_mount(mfd, "", AT_FDCWD, "/tmp", MOVE_MOUNT_F_EMPTY_PATH)); + + /* + * The fscontext log should still contain data even after + * FSCONFIG_CMD_CREATE and fsmount(). + */ + char buf[128] = {}; + ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf))); + + printf("fsconfig(): %s\n", buf); + ASSERT_EQ(strcmp(buf, "e tmpfs: Unknown parameter 'invalid-arg'\n"), 0); + + /* The message has been consumed. */ + ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf))); + ASSERT_SUCCESS(close(fsfd)); +} + +TEST_F(ns, fscontext_log_emsgsize) +{ + int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC); + ASSERT_SUCCESS(fsfd); + + ASSERT_ERRNO_EQ(-EINVAL, fsconfig(fsfd, FSCONFIG_SET_STRING, "invalid-arg", "123", 0)); + + char buf[128] = {}; + /* + * Attempting to read a message with too small a buffer should not + * result in the message getting consumed. + */ + for (int i = 0; i < 16; i++) + ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 1)); + ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf))); + + printf("fsconfig(): %s\n", buf); + ASSERT_EQ(strcmp(buf, "e tmpfs: Unknown parameter 'invalid-arg'\n"), 0); + + /* The message has been consumed. */ + ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf))); + ASSERT_SUCCESS(close(fsfd)); +} + +TEST_HARNESS_MAIN
linux-kselftest-mirror@lists.linaro.org