diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h --- a/sys/fs/fuse/fuse_kernel.h +++ b/sys/fs/fuse/fuse_kernel.h @@ -176,6 +176,12 @@ * * 7.32 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS + * + * 7.33 + * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID + * - add FUSE_OPEN_KILL_SUIDGID + * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT + * - add FUSE_SETXATTR_ACL_KILL_SGID */ #ifndef _FUSE_FUSE_KERNEL_H @@ -211,7 +217,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 32 +#define FUSE_KERNEL_MINOR_VERSION 33 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -272,6 +278,7 @@ #define FATTR_MTIME_NOW (1 << 8) #define FATTR_LOCKOWNER (1 << 9) #define FATTR_CTIME (1 << 10) +#define FATTR_KILL_SUIDGID (1 << 11) /** * Flags returned by the OPEN request @@ -321,6 +328,12 @@ * foffset and moffset fields in struct * fuse_setupmapping_out and fuse_removemapping_one. * FUSE_SUBMOUNTS: kernel supports auto-mounting directory submounts + * FUSE_HANDLE_KILLPRIV_V2: fs kills suid/sgid/cap on write/chown/trunc. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * FUSE_SETXATTR_EXT: Server supports extended struct fuse_setxattr_in */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -350,6 +363,8 @@ #define FUSE_EXPLICIT_INVAL_DATA (1 << 25) #define FUSE_MAP_ALIGNMENT (1 << 26) #define FUSE_SUBMOUNTS (1 << 27) +#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28) +#define FUSE_SETXATTR_EXT (1 << 29) #ifdef linux /** @@ -381,11 +396,14 @@ * * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed * FUSE_WRITE_LOCKOWNER: lock_owner field is valid - * FUSE_WRITE_KILL_PRIV: kill suid and sgid bits + * FUSE_WRITE_KILL_SUIDGID: kill suid and sgid bits */ #define FUSE_WRITE_CACHE (1 << 0) #define FUSE_WRITE_LOCKOWNER (1 << 1) -#define FUSE_WRITE_KILL_PRIV (1 << 2) +#define FUSE_WRITE_KILL_SUIDGID (1 << 2) + +/* Obsolete alias; this flag implies killing suid/sgid only. */ +#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID /** * Read flags @@ -435,11 +453,22 @@ /** * fuse_attr flags - * * FUSE_ATTR_SUBMOUNT: Object is a submount root */ #define FUSE_ATTR_SUBMOUNT (1 << 0) +/** + * Open flags + * FUSE_OPEN_KILL_SUIDGID: Kill suid and sgid if executable + */ +#define FUSE_OPEN_KILL_SUIDGID (1 << 0) + +/** + * setxattr flags + * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is set + */ +#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0) + enum fuse_opcode { FUSE_LOOKUP = 1, FUSE_FORGET = 2, /* no reply */ @@ -494,7 +523,7 @@ CUSE_INIT = 4096, /* Reserved opcodes: helpful to detect structure endian-ness */ CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */ - FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */ + FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */ #endif /* linux */ }; @@ -602,14 +631,14 @@ struct fuse_open_in { uint32_t flags; - uint32_t unused; + uint32_t open_flags; /* FUSE_OPEN_... */ }; struct fuse_create_in { uint32_t flags; uint32_t mode; uint32_t umask; - uint32_t padding; + uint32_t open_flags; /* FUSE_OPEN_... */ }; struct fuse_open_out { @@ -671,9 +700,13 @@ uint32_t padding; }; +#define FUSE_COMPAT_SETXATTR_IN_SIZE 8 + struct fuse_setxattr_in { uint32_t size; uint32_t flags; + uint32_t setxattr_flags; + uint32_t padding; }; struct fuse_listxattr_in { @@ -928,7 +961,6 @@ uint64_t flags; }; - #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) struct fuse_setupmapping_in { @@ -957,6 +989,6 @@ }; #define FUSE_REMOVEMAPPING_MAX_ENTRY \ - (PAGE_SIZE / sizeof(struct fuse_removemapping_one)) + (PAGE_SIZE / sizeof(struct fuse_removemapping_one)) #endif /* _FUSE_FUSE_KERNEL_H */ diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -2704,6 +2704,7 @@ struct mount *mp = vnode_mount(vp); struct thread *td = ap->a_td; struct ucred *cred = ap->a_cred; + size_t struct_size = FUSE_COMPAT_SETXATTR_IN_SIZE; char *prefix; size_t len; char *attr_str; @@ -2744,17 +2745,26 @@ len = strlen(prefix) + sizeof(extattr_namespace_separator) + strlen(ap->a_name) + 1; - fdisp_init(&fdi, len + sizeof(*set_xattr_in) + uio->uio_resid); + /* we want to be binary compatible */ + if (fuse_libabi_geq(fuse_get_mpdata(mp), 7, 33)) + struct_size = sizeof(*set_xattr_in); + + fdisp_init(&fdi, len + struct_size + uio->uio_resid); fdisp_make_vp(&fdi, FUSE_SETXATTR, vp, td, cred); set_xattr_in = fdi.indata; set_xattr_in->size = uio->uio_resid; - attr_str = (char *)fdi.indata + sizeof(*set_xattr_in); + if (fuse_libabi_geq(fuse_get_mpdata(mp), 7, 33)) { + set_xattr_in->setxattr_flags = 0; + set_xattr_in->padding = 0; + } + + attr_str = (char *)fdi.indata + struct_size; snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator, ap->a_name); - err = uiomove((char *)fdi.indata + sizeof(*set_xattr_in) + len, + err = uiomove((char *)fdi.indata + struct_size + len, uio->uio_resid, uio); if (err != 0) { goto out; diff --git a/tests/sys/fs/fusefs/xattr.cc b/tests/sys/fs/fusefs/xattr.cc --- a/tests/sys/fs/fusefs/xattr.cc +++ b/tests/sys/fs/fusefs/xattr.cc @@ -110,6 +110,8 @@ const char *v = a + strlen(a) + 1; return (in.header.opcode == FUSE_SETXATTR && in.header.nodeid == ino && + in.body.setxattr.size == (strlen(value) + 1) && + in.body.setxattr.flags == 0 && 0 == strcmp(attr, a) && 0 == strcmp(value, v)); }, Eq(true)), @@ -119,6 +121,33 @@ }; +class Xattr_7_32: public FuseTest { +public: + virtual void SetUp() { + m_kernel_minor_version = 32; + FuseTest::SetUp(); + } + +void expect_setxattr_7_32(uint64_t ino, const char *attr, const char *value, + ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in.body.bytes + + FUSE_COMPAT_SETXATTR_IN_SIZE; + const char *v = a + strlen(a) + 1; + return (in.header.opcode == FUSE_SETXATTR && + in.header.nodeid == ino && + in.body.setxattr.size == (strlen(value) + 1) && + in.body.setxattr.flags == 0 && + 0 == strcmp(attr, a) && + 0 == strcmp(value, v)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} +}; + class Getxattr: public Xattr {}; class Listxattr: public Xattr {}; @@ -153,6 +182,7 @@ class Removexattr: public Xattr {}; class Setxattr: public Xattr {}; +class Setxattr_7_32: public Xattr_7_32 {}; class RofsXattr: public Xattr { public: virtual void SetUp() { @@ -728,6 +758,7 @@ << strerror(errno); } + /* * If the filesystem returns ENOSYS, then it will be treated as a permanent * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP @@ -815,6 +846,23 @@ ASSERT_EQ(value_len, r) << strerror(errno); } + +TEST_F(Setxattr_7_32, ok) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(0)); + + r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, + value_len); + ASSERT_EQ(value_len, r) << strerror(errno); +} + TEST_F(RofsXattr, deleteextattr_erofs) { uint64_t ino = 42;