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:
Nicolas Lacasse
2020-08-26 10:05:34 -07:00
committed by gVisor bot
parent ebf5293374
commit 83a8b309e9
7 changed files with 147 additions and 43 deletions
+3
View File
@@ -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)
)
+1 -1
View File
@@ -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
+42 -26
View File
@@ -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
+1 -16
View File
@@ -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")));
+74
View File
@@ -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
+20
View File
@@ -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
+6
View File
@@ -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);