aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Somers <asomers@FreeBSD.org>2023-10-04 18:48:01 +0000
committerAlan Somers <asomers@FreeBSD.org>2023-10-15 02:13:05 +0000
commit0022bd418e6c0c7c767a296a3e0d3782e5137dce (patch)
tree396de7125af059e609e579d1f87d591baf10450b
parent3ac9fb49cc1c3d6034a449471df7ba2d24f26089 (diff)
fusefs: sanitize FUSE_READLINK results for embedded NULs
If VOP_READLINK returns a path that contains a NUL, it will trigger an assertion in vfs_lookup. Sanitize such paths in fusefs, rejecting any and warning the user about the misbehaving server. PR: 274268 Sponsored by: Axcient Approved by: gjb (re) Reviewed by: mjg, markj Differential Revision: https://reviews.freebsd.org/D42081 (cherry picked from commit 662ec2f781521c36b76af748d74bb0a3c2e27a76) (cherry picked from commit 8fca98f6881fdd68a786f4366c345159ab0df408)
-rw-r--r--sys/fs/fuse/fuse_ipc.h1
-rw-r--r--sys/fs/fuse/fuse_vnops.c7
-rw-r--r--tests/sys/fs/fusefs/readlink.cc39
3 files changed, 47 insertions, 0 deletions
diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h
index 27f3662741c5..0ec556138be0 100644
--- a/sys/fs/fuse/fuse_ipc.h
+++ b/sys/fs/fuse/fuse_ipc.h
@@ -239,6 +239,7 @@ struct fuse_data {
#define FSESS_WARN_CACHE_INCOHERENT 0x200000 /* Read cache incoherent */
#define FSESS_WARN_WB_CACHE_INCOHERENT 0x400000 /* WB cache incoherent */
#define FSESS_WARN_ILLEGAL_INODE 0x800000 /* Illegal inode for new file */
+#define FSESS_WARN_READLINK_EMBEDDED_NUL 0x1000000 /* corrupt READLINK output */
#define FSESS_MNTOPTS_MASK ( \
FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \
FSESS_DEFAULT_PERMISSIONS | FSESS_INTR)
diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index 21ee378b24c6..3249e5988801 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -2007,6 +2007,13 @@ fuse_vnop_readlink(struct vop_readlink_args *ap)
if (err) {
goto out;
}
+ if (strnlen(fdi.answ, fdi.iosize) + 1 < fdi.iosize) {
+ struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp));
+ fuse_warn(data, FSESS_WARN_READLINK_EMBEDDED_NUL,
+ "Returned an embedded NUL from FUSE_READLINK.");
+ err = EIO;
+ goto out;
+ }
if (((char *)fdi.answ)[0] == '/' &&
fuse_get_mpdata(vnode_mount(vp))->dataflags & FSESS_PUSH_SYMLINKS_IN) {
char *mpth = vnode_mount(vp)->mnt_stat.f_mntonname;
diff --git a/tests/sys/fs/fusefs/readlink.cc b/tests/sys/fs/fusefs/readlink.cc
index ff9aa08f6fae..30815f2cd4b6 100644
--- a/tests/sys/fs/fusefs/readlink.cc
+++ b/tests/sys/fs/fusefs/readlink.cc
@@ -79,6 +79,45 @@ TEST_F(Readlink, eloop)
EXPECT_EQ(ELOOP, errno);
}
+/*
+ * If a malicious or buggy server returns a NUL in the FUSE_READLINK result, it
+ * should be handled gracefully.
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=274268
+ */
+TEST_F(Readlink, embedded_nul)
+{
+ const char FULLPATH[] = "mountpoint/src";
+ const char RELPATH[] = "src";
+ const char dst[] = "dst\0stuff";
+ char buf[80];
+ const uint64_t ino = 42;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFLNK | 0777;
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_READLINK &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ memcpy(out.body.str, dst, sizeof(dst));
+ out.header.len = sizeof(out.header) + sizeof(dst) + 1;
+ })));
+
+ EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf)));
+ EXPECT_EQ(EIO, errno);
+ EXPECT_EQ(-1, access(FULLPATH, R_OK));
+ EXPECT_EQ(EIO, errno);
+}
+
TEST_F(Readlink, ok)
{
const char FULLPATH[] = "mountpoint/src";