Skip to content

Commit 573015d

Browse files
committed
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>
2 parents bfa8ee1 + df579e4 commit 573015d

4 files changed

Lines changed: 170 additions & 33 deletions

File tree

fs/fsopen.c

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,56 @@
1818
#include "internal.h"
1919
#include "mount.h"
2020

21+
static inline const char *fetch_message_locked(struct fc_log *log, size_t len,
22+
bool *need_free)
23+
{
24+
const char *p;
25+
int index;
26+
27+
if (unlikely(log->head == log->tail))
28+
return ERR_PTR(-ENODATA);
29+
30+
index = log->tail & (ARRAY_SIZE(log->buffer) - 1);
31+
p = log->buffer[index];
32+
if (unlikely(strlen(p) > len))
33+
return ERR_PTR(-EMSGSIZE);
34+
35+
log->buffer[index] = NULL;
36+
*need_free = log->need_free & (1 << index);
37+
log->need_free &= ~(1 << index);
38+
log->tail++;
39+
40+
return p;
41+
}
42+
2143
/*
2244
* Allow the user to read back any error, warning or informational messages.
45+
* Only one message is returned for each read(2) call.
2346
*/
2447
static ssize_t fscontext_read(struct file *file,
2548
char __user *_buf, size_t len, loff_t *pos)
2649
{
2750
struct fs_context *fc = file->private_data;
28-
struct fc_log *log = fc->log.log;
29-
unsigned int logsize = ARRAY_SIZE(log->buffer);
30-
ssize_t ret;
31-
char *p;
51+
ssize_t err;
52+
const char *p __free(kfree) = NULL, *message;
3253
bool need_free;
33-
int index, n;
54+
int n;
3455

35-
ret = mutex_lock_interruptible(&fc->uapi_mutex);
36-
if (ret < 0)
37-
return ret;
38-
39-
if (log->head == log->tail) {
40-
mutex_unlock(&fc->uapi_mutex);
41-
return -ENODATA;
42-
}
43-
44-
index = log->tail & (logsize - 1);
45-
p = log->buffer[index];
46-
need_free = log->need_free & (1 << index);
47-
log->buffer[index] = NULL;
48-
log->need_free &= ~(1 << index);
49-
log->tail++;
56+
err = mutex_lock_interruptible(&fc->uapi_mutex);
57+
if (err < 0)
58+
return err;
59+
message = fetch_message_locked(fc->log.log, len, &need_free);
5060
mutex_unlock(&fc->uapi_mutex);
61+
if (IS_ERR(message))
62+
return PTR_ERR(message);
5163

52-
ret = -EMSGSIZE;
53-
n = strlen(p);
54-
if (n > len)
55-
goto err_free;
56-
ret = -EFAULT;
57-
if (copy_to_user(_buf, p, n) != 0)
58-
goto err_free;
59-
ret = n;
60-
61-
err_free:
6264
if (need_free)
63-
kfree(p);
64-
return ret;
65+
p = message;
66+
67+
n = strlen(message);
68+
if (copy_to_user(_buf, message, n))
69+
return -EFAULT;
70+
return n;
6571
}
6672

6773
static int fscontext_release(struct inode *inode, struct file *file)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: GPL-2.0-only
22
dnotify_test
33
devpts_pts
4+
fclog
45
file_stressor
56
anon_inode_test
67
kernfs_test
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-License-Identifier: GPL-2.0
22

33
CFLAGS += $(KHDR_INCLUDES)
4-
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test
4+
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog
55
TEST_GEN_PROGS_EXTENDED := dnotify_test
66

77
include ../lib.mk
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Author: Aleksa Sarai <cyphar@cyphar.com>
4+
* Copyright (C) 2025 SUSE LLC.
5+
*/
6+
7+
#include <assert.h>
8+
#include <errno.h>
9+
#include <sched.h>
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
#include <unistd.h>
14+
#include <sys/mount.h>
15+
16+
#include "../kselftest_harness.h"
17+
18+
#define ASSERT_ERRNO(expected, _t, seen) \
19+
__EXPECT(expected, #expected, \
20+
({__typeof__(seen) _tmp_seen = (seen); \
21+
_tmp_seen >= 0 ? _tmp_seen : -errno; }), #seen, _t, 1)
22+
23+
#define ASSERT_ERRNO_EQ(expected, seen) \
24+
ASSERT_ERRNO(expected, ==, seen)
25+
26+
#define ASSERT_SUCCESS(seen) \
27+
ASSERT_ERRNO(0, <=, seen)
28+
29+
FIXTURE(ns)
30+
{
31+
int host_mntns;
32+
};
33+
34+
FIXTURE_SETUP(ns)
35+
{
36+
/* Stash the old mntns. */
37+
self->host_mntns = open("/proc/self/ns/mnt", O_RDONLY|O_CLOEXEC);
38+
ASSERT_SUCCESS(self->host_mntns);
39+
40+
/* Create a new mount namespace and make it private. */
41+
ASSERT_SUCCESS(unshare(CLONE_NEWNS));
42+
ASSERT_SUCCESS(mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL));
43+
}
44+
45+
FIXTURE_TEARDOWN(ns)
46+
{
47+
ASSERT_SUCCESS(setns(self->host_mntns, CLONE_NEWNS));
48+
ASSERT_SUCCESS(close(self->host_mntns));
49+
}
50+
51+
TEST_F(ns, fscontext_log_enodata)
52+
{
53+
int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
54+
ASSERT_SUCCESS(fsfd);
55+
56+
/* A brand new fscontext has no log entries. */
57+
char buf[128] = {};
58+
for (int i = 0; i < 16; i++)
59+
ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf)));
60+
61+
ASSERT_SUCCESS(close(fsfd));
62+
}
63+
64+
TEST_F(ns, fscontext_log_errorfc)
65+
{
66+
int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
67+
ASSERT_SUCCESS(fsfd);
68+
69+
ASSERT_ERRNO_EQ(-EINVAL, fsconfig(fsfd, FSCONFIG_SET_STRING, "invalid-arg", "123", 0));
70+
71+
char buf[128] = {};
72+
ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf)));
73+
EXPECT_STREQ("e tmpfs: Unknown parameter 'invalid-arg'\n", buf);
74+
75+
/* The message has been consumed. */
76+
ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf)));
77+
ASSERT_SUCCESS(close(fsfd));
78+
}
79+
80+
TEST_F(ns, fscontext_log_errorfc_after_fsmount)
81+
{
82+
int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
83+
ASSERT_SUCCESS(fsfd);
84+
85+
ASSERT_ERRNO_EQ(-EINVAL, fsconfig(fsfd, FSCONFIG_SET_STRING, "invalid-arg", "123", 0));
86+
87+
ASSERT_SUCCESS(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0));
88+
int mfd = fsmount(fsfd, FSMOUNT_CLOEXEC, MOUNT_ATTR_NOEXEC | MOUNT_ATTR_NOSUID);
89+
ASSERT_SUCCESS(mfd);
90+
ASSERT_SUCCESS(move_mount(mfd, "", AT_FDCWD, "/tmp", MOVE_MOUNT_F_EMPTY_PATH));
91+
92+
/*
93+
* The fscontext log should still contain data even after
94+
* FSCONFIG_CMD_CREATE and fsmount().
95+
*/
96+
char buf[128] = {};
97+
ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf)));
98+
EXPECT_STREQ("e tmpfs: Unknown parameter 'invalid-arg'\n", buf);
99+
100+
/* The message has been consumed. */
101+
ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf)));
102+
ASSERT_SUCCESS(close(fsfd));
103+
}
104+
105+
TEST_F(ns, fscontext_log_emsgsize)
106+
{
107+
int fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
108+
ASSERT_SUCCESS(fsfd);
109+
110+
ASSERT_ERRNO_EQ(-EINVAL, fsconfig(fsfd, FSCONFIG_SET_STRING, "invalid-arg", "123", 0));
111+
112+
char buf[128] = {};
113+
/*
114+
* Attempting to read a message with too small a buffer should not
115+
* result in the message getting consumed.
116+
*/
117+
ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 0));
118+
ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 1));
119+
for (int i = 0; i < 16; i++)
120+
ASSERT_ERRNO_EQ(-EMSGSIZE, read(fsfd, buf, 16));
121+
122+
ASSERT_SUCCESS(read(fsfd, buf, sizeof(buf)));
123+
EXPECT_STREQ("e tmpfs: Unknown parameter 'invalid-arg'\n", buf);
124+
125+
/* The message has been consumed. */
126+
ASSERT_ERRNO_EQ(-ENODATA, read(fsfd, buf, sizeof(buf)));
127+
ASSERT_SUCCESS(close(fsfd));
128+
}
129+
130+
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)