diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 72265c8ca533..20d31c31ae7c 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -1609,14 +1609,12 @@ fuse_vnop_setattr(struct vop_setattr_args *ap) } /* Don't set accmode. Permission to trunc is checked upstack */ } - /* - * TODO: for atime and mtime, only require VWRITE if UTIMENS_NULL is - * set. PR 237181 - */ - if (vap->va_atime.tv_sec != VNOVAL) - accmode |= VADMIN; - if (vap->va_mtime.tv_sec != VNOVAL) - accmode |= VADMIN; + if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { + if (vap->va_vaflags & VA_UTIMES_NULL) + accmode |= VWRITE; + else + accmode |= VADMIN; + } if (drop_suid) { if (vap->va_mode != (mode_t)VNOVAL) vap->va_mode &= ~(S_ISUID | S_ISGID); diff --git a/tests/sys/fs/fusefs/default_permissions.cc b/tests/sys/fs/fusefs/default_permissions.cc index 152e43b4131e..5fdc13e58ae3 100644 --- a/tests/sys/fs/fusefs/default_permissions.cc +++ b/tests/sys/fs/fusefs/default_permissions.cc @@ -141,6 +141,7 @@ class Lookup: public DefaultPermissions {}; class Open: public DefaultPermissions {}; class Setattr: public DefaultPermissions {}; class Unlink: public DefaultPermissions {}; +class Utimensat: public DefaultPermissions {}; class Write: public DefaultPermissions {}; /* @@ -544,6 +545,60 @@ TEST_F(Deleteextattr, system) ASSERT_EQ(EPERM, errno); } +/* Anybody with write permission can set both timestamps to UTIME_NOW */ +TEST_F(Utimensat, utime_now) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + /* Write permissions for everybody */ + const mode_t mode = 0666; + uid_t owner = 0; + const timespec times[2] = { + {.tv_sec = 0, .tv_nsec = UTIME_NOW}, + {.tv_sec = 0, .tv_nsec = UTIME_NOW}, + }; + + expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); + expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid & FATTR_ATIME && + in->body.setattr.valid & FATTR_MTIME); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.mode = S_IFREG | mode; + }))); + + ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) + << strerror(errno); +} + +/* Anybody can set both timestamps to UTIME_OMIT */ +TEST_F(Utimensat, utime_omit) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + /* Write permissions for no one */ + const mode_t mode = 0444; + uid_t owner = 0; + const timespec times[2] = { + {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, + {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, + }; + + expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); + expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); + + ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) + << strerror(errno); +} + /* Deleting user attributes merely requires WRITE privilege */ TEST_F(Deleteextattr, user) {