mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-10 18:40:25 -04:00
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:
70
fs/fsopen.c
70
fs/fsopen.c
@@ -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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
dnotify_test
|
||||
devpts_pts
|
||||
fclog
|
||||
file_stressor
|
||||
anon_inode_test
|
||||
kernfs_test
|
||||
|
||||
@@ -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
|
||||
|
||||
130
tools/testing/selftests/filesystems/fclog.c
Normal file
130
tools/testing/selftests/filesystems/fclog.c
Normal 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
|
||||
Reference in New Issue
Block a user