mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
tmpfs: Allow xattrs in the trusted namespace if creds has CAP_SYS_ADMIN.
This is needed to support the overlay opaque attribute. PiperOrigin-RevId: 328552985
This commit is contained in:
committed by
gVisor bot
parent
ebf5293374
commit
83a8b309e9
@@ -23,6 +23,9 @@ const (
|
||||
XATTR_CREATE = 1
|
||||
XATTR_REPLACE = 2
|
||||
|
||||
XATTR_TRUSTED_PREFIX = "trusted."
|
||||
XATTR_TRUSTED_PREFIX_LEN = len(XATTR_TRUSTED_PREFIX)
|
||||
|
||||
XATTR_USER_PREFIX = "user."
|
||||
XATTR_USER_PREFIX_LEN = len(XATTR_USER_PREFIX)
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
// _OVL_XATTR_OPAQUE is an extended attribute key whose value is set to "y" for
|
||||
// opaque directories.
|
||||
// Linux: fs/overlayfs/overlayfs.h:OVL_XATTR_OPAQUE
|
||||
const _OVL_XATTR_OPAQUE = "trusted.overlay.opaque"
|
||||
const _OVL_XATTR_OPAQUE = linux.XATTR_TRUSTED_PREFIX + "overlay.opaque"
|
||||
|
||||
func isWhiteout(stat *linux.Statx) bool {
|
||||
return stat.Mode&linux.S_IFMT == linux.S_IFCHR && stat.RdevMajor == 0 && stat.RdevMinor == 0
|
||||
|
||||
@@ -631,49 +631,65 @@ func (i *inode) listxattr(size uint64) ([]string, error) {
|
||||
}
|
||||
|
||||
func (i *inode) getxattr(creds *auth.Credentials, opts *vfs.GetxattrOptions) (string, error) {
|
||||
if err := i.checkPermissions(creds, vfs.MayRead); err != nil {
|
||||
if err := i.checkXattrPermissions(creds, opts.Name, vfs.MayRead); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !strings.HasPrefix(opts.Name, linux.XATTR_USER_PREFIX) {
|
||||
return "", syserror.EOPNOTSUPP
|
||||
}
|
||||
if !i.userXattrSupported() {
|
||||
return "", syserror.ENODATA
|
||||
}
|
||||
return i.xattrs.Getxattr(opts)
|
||||
}
|
||||
|
||||
func (i *inode) setxattr(creds *auth.Credentials, opts *vfs.SetxattrOptions) error {
|
||||
if err := i.checkPermissions(creds, vfs.MayWrite); err != nil {
|
||||
if err := i.checkXattrPermissions(creds, opts.Name, vfs.MayWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(opts.Name, linux.XATTR_USER_PREFIX) {
|
||||
return syserror.EOPNOTSUPP
|
||||
}
|
||||
if !i.userXattrSupported() {
|
||||
return syserror.EPERM
|
||||
}
|
||||
return i.xattrs.Setxattr(opts)
|
||||
}
|
||||
|
||||
func (i *inode) removexattr(creds *auth.Credentials, name string) error {
|
||||
if err := i.checkPermissions(creds, vfs.MayWrite); err != nil {
|
||||
if err := i.checkXattrPermissions(creds, name, vfs.MayWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(name, linux.XATTR_USER_PREFIX) {
|
||||
return syserror.EOPNOTSUPP
|
||||
}
|
||||
if !i.userXattrSupported() {
|
||||
return syserror.EPERM
|
||||
}
|
||||
return i.xattrs.Removexattr(name)
|
||||
}
|
||||
|
||||
// Extended attributes in the user.* namespace are only supported for regular
|
||||
// files and directories.
|
||||
func (i *inode) userXattrSupported() bool {
|
||||
filetype := linux.S_IFMT & atomic.LoadUint32(&i.mode)
|
||||
return filetype == linux.S_IFREG || filetype == linux.S_IFDIR
|
||||
func (i *inode) checkXattrPermissions(creds *auth.Credentials, name string, ats vfs.AccessTypes) error {
|
||||
switch {
|
||||
case ats&vfs.MayRead == vfs.MayRead:
|
||||
if err := i.checkPermissions(creds, vfs.MayRead); err != nil {
|
||||
return err
|
||||
}
|
||||
case ats&vfs.MayWrite == vfs.MayWrite:
|
||||
if err := i.checkPermissions(creds, vfs.MayWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("checkXattrPermissions called with impossible AccessTypes: %v", ats))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(name, linux.XATTR_TRUSTED_PREFIX):
|
||||
// The trusted.* namespace can only be accessed by privileged
|
||||
// users.
|
||||
if creds.HasCapability(linux.CAP_SYS_ADMIN) {
|
||||
return nil
|
||||
}
|
||||
if ats&vfs.MayWrite == vfs.MayWrite {
|
||||
return syserror.EPERM
|
||||
}
|
||||
return syserror.ENODATA
|
||||
case strings.HasPrefix(name, linux.XATTR_USER_PREFIX):
|
||||
// Extended attributes in the user.* namespace are only
|
||||
// supported for regular files and directories.
|
||||
filetype := linux.S_IFMT & atomic.LoadUint32(&i.mode)
|
||||
if filetype == linux.S_IFREG || filetype == linux.S_IFDIR {
|
||||
return nil
|
||||
}
|
||||
if ats&vfs.MayWrite == vfs.MayWrite {
|
||||
return syserror.EPERM
|
||||
}
|
||||
return syserror.ENODATA
|
||||
|
||||
}
|
||||
return syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
// fileDescription is embedded by tmpfs implementations of
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/memfd.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <vector>
|
||||
@@ -53,6 +51,7 @@ namespace {
|
||||
#define F_SEAL_GROW 0x0004
|
||||
#define F_SEAL_WRITE 0x0008
|
||||
|
||||
using ::gvisor::testing::IsTmpfs;
|
||||
using ::testing::StartsWith;
|
||||
|
||||
const std::string kMemfdName = "some-memfd";
|
||||
@@ -444,20 +443,6 @@ TEST(MemfdTest, SealsAreInodeLevelProperties) {
|
||||
EXPECT_THAT(ftruncate(memfd3.get(), kPageSize), SyscallFailsWithErrno(EPERM));
|
||||
}
|
||||
|
||||
PosixErrorOr<bool> IsTmpfs(const std::string& path) {
|
||||
struct statfs stat;
|
||||
if (statfs(path.c_str(), &stat)) {
|
||||
if (errno == ENOENT) {
|
||||
// Nothing at path, don't raise this as an error. Instead, just report no
|
||||
// tmpfs at path.
|
||||
return false;
|
||||
}
|
||||
return PosixError(errno,
|
||||
absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
|
||||
}
|
||||
return stat.f_type == TMPFS_MAGIC;
|
||||
}
|
||||
|
||||
// Tmpfs files also support seals, but are created with F_SEAL_SEAL.
|
||||
TEST(MemfdTest, TmpfsFilesHaveSealSeal) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp")));
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "test/syscalls/linux/file_base.h"
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
@@ -37,6 +38,8 @@ namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::gvisor::testing::IsTmpfs;
|
||||
|
||||
class XattrTest : public FileTest {};
|
||||
|
||||
TEST_F(XattrTest, XattrNonexistentFile) {
|
||||
@@ -604,6 +607,77 @@ TEST_F(XattrTest, XattrWithFD) {
|
||||
EXPECT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) {
|
||||
// Trusted namespace not supported in VFS1.
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
|
||||
// TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace.
|
||||
SKIP_IF(IsRunningOnGvisor() &&
|
||||
!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_)));
|
||||
|
||||
// Setting/Getting in the trusted namespace requires CAP_SYS_ADMIN.
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
const char name[] = "trusted.test";
|
||||
|
||||
// Set.
|
||||
char val = 'a';
|
||||
size_t size = sizeof(val);
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());
|
||||
|
||||
// Get.
|
||||
char got = '\0';
|
||||
EXPECT_THAT(getxattr(path, name, &got, size), SyscallSucceedsWithValue(size));
|
||||
EXPECT_EQ(val, got);
|
||||
|
||||
// List.
|
||||
char list[sizeof(name)];
|
||||
EXPECT_THAT(listxattr(path, list, sizeof(list)),
|
||||
SyscallSucceedsWithValue(sizeof(name)));
|
||||
EXPECT_STREQ(list, name);
|
||||
|
||||
// Remove.
|
||||
EXPECT_THAT(removexattr(path, name), SyscallSucceeds());
|
||||
|
||||
// Get should now return ENODATA.
|
||||
EXPECT_THAT(getxattr(path, name, &got, size), SyscallFailsWithErrno(ENODATA));
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, TrustedNamespaceWithoutCapSysAdmin) {
|
||||
// Trusted namespace not supported in VFS1.
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
|
||||
// TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace.
|
||||
SKIP_IF(IsRunningOnGvisor() &&
|
||||
!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_)));
|
||||
|
||||
// Drop CAP_SYS_ADMIN if we have it.
|
||||
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) {
|
||||
EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
|
||||
}
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
const char name[] = "trusted.test";
|
||||
|
||||
// Set fails.
|
||||
char val = 'a';
|
||||
size_t size = sizeof(val);
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0),
|
||||
SyscallFailsWithErrno(EPERM));
|
||||
|
||||
// Get fails.
|
||||
char got = '\0';
|
||||
EXPECT_THAT(getxattr(path, name, &got, size), SyscallFailsWithErrno(ENODATA));
|
||||
|
||||
// List still works, but returns no items.
|
||||
char list[sizeof(name)];
|
||||
EXPECT_THAT(listxattr(path, list, sizeof(list)), SyscallSucceedsWithValue(0));
|
||||
|
||||
// Remove fails.
|
||||
EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
#include "test/util/fs_util.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#ifndef __fuchsia__
|
||||
#include <linux/magic.h>
|
||||
#endif // __fuchsia__
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -629,5 +633,21 @@ PosixErrorOr<std::string> ProcessExePath(int pid) {
|
||||
return ReadLink(absl::StrCat("/proc/", pid, "/exe"));
|
||||
}
|
||||
|
||||
#ifndef __fuchsia__
|
||||
PosixErrorOr<bool> IsTmpfs(const std::string& path) {
|
||||
struct statfs stat;
|
||||
if (statfs(path.c_str(), &stat)) {
|
||||
if (errno == ENOENT) {
|
||||
// Nothing at path, don't raise this as an error. Instead, just report no
|
||||
// tmpfs at path.
|
||||
return false;
|
||||
}
|
||||
return PosixError(errno,
|
||||
absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
|
||||
}
|
||||
return stat.f_type == TMPFS_MAGIC;
|
||||
}
|
||||
#endif // __fuchsia__
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -178,6 +179,11 @@ std::string CleanPath(absl::string_view path);
|
||||
// Returns the full path to the executable of the given pid or a PosixError.
|
||||
PosixErrorOr<std::string> ProcessExePath(int pid);
|
||||
|
||||
#ifndef __fuchsia__
|
||||
// IsTmpfs returns true if the file at path is backed by tmpfs.
|
||||
PosixErrorOr<bool> IsTmpfs(const std::string& path);
|
||||
#endif // __fucshia__
|
||||
|
||||
namespace internal {
|
||||
// Not part of the public API.
|
||||
std::string JoinPathImpl(std::initializer_list<absl::string_view> paths);
|
||||
|
||||
Reference in New Issue
Block a user