Merge patch series "fscontext: do not consume log entries when returning -EMSGSIZE"

Aleksa Sarai <cyphar@cyphar.com> says:

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. While we're at it, refactor some fscontext_read() into a
separate helper to make the ring buffer logic a bit easier to read.

* patches from https://lore.kernel.org/20250807-fscontext-log-cleanups-v3-0-8d91d6242dc3@cyphar.com:
  selftests/filesystems: add basic fscontext log tests
  fscontext: do not consume log entries when returning -EMSGSIZE

Link: https://lore.kernel.org/20250807-fscontext-log-cleanups-v3-0-8d91d6242dc3@cyphar.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner
2025-08-08 15:52:47 +02:00
4 changed files with 170 additions and 33 deletions

View File

@@ -18,50 +18,56 @@
#include "internal.h"
#include "mount.h"
static inline const char *fetch_message_locked(struct fc_log *log, size_t len,
bool *need_free)
{
const char *p;
int index;
if (unlikely(log->head == log->tail))
return ERR_PTR(-ENODATA);
index = log->tail & (ARRAY_SIZE(log->buffer) - 1);
p = log->buffer[index];
if (unlikely(strlen(p) > 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;
}
/*
* Allow the user to read back any error, warning or informational messages.
* Only one message is returned for each read(2) call.
*/
static ssize_t fscontext_read(struct file *file,
char __user *_buf, size_t len, loff_t *pos)
{
struct fs_context *fc = file->private_data;
struct fc_log *log = fc->log.log;
unsigned int logsize = ARRAY_SIZE(log->buffer);
ssize_t ret;
char *p;
ssize_t err;
const char *p __free(kfree) = NULL, *message;
bool need_free;
int index, n;
int n;
ret = mutex_lock_interruptible(&fc->uapi_mutex);
if (ret < 0)
return ret;
if (log->head == log->tail) {
mutex_unlock(&fc->uapi_mutex);
return -ENODATA;
}
index = log->tail & (logsize - 1);
p = log->buffer[index];
need_free = log->need_free & (1 << index);
log->buffer[index] = NULL;
log->need_free &= ~(1 << index);
log->tail++;
err = mutex_lock_interruptible(&fc->uapi_mutex);
if (err < 0)
return err;
message = fetch_message_locked(fc->log.log, len, &need_free);
mutex_unlock(&fc->uapi_mutex);
if (IS_ERR(message))
return PTR_ERR(message);
ret = -EMSGSIZE;
n = strlen(p);
if (n > len)
goto err_free;
ret = -EFAULT;
if (copy_to_user(_buf, p, n) != 0)
goto err_free;
ret = n;
err_free:
if (need_free)
kfree(p);
return ret;
p = message;
n = strlen(message);
if (copy_to_user(_buf, message, n))
return -EFAULT;
return n;
}
static int fscontext_release(struct inode *inode, struct file *file)

View File

@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
dnotify_test
devpts_pts
fclog
file_stressor
anon_inode_test
kernfs_test

View File

@@ -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

View File

@@ -0,0 +1,130 @@
// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.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)));
EXPECT_STREQ("e tmpfs: Unknown parameter 'invalid-arg'\n", buf);
/* 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)));
EXPECT_STREQ("e tmpfs: Unknown parameter 'invalid-arg'\n", buf);
/* 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.
*/
ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 0));
ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 1));
for (int i = 0; i < 16; i++)
ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 16));
ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf)));
EXPECT_STREQ("e tmpfs: Unknown parameter 'invalid-arg'\n", buf);
/* The message has been consumed. */
ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf)));
ASSERT_SUCCESS(close(fsfd));
}
TEST_HARNESS_MAIN