mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement checks for get/setxattr at the syscall layer.
Add checks for input arguments, file type, permissions, etc. that match the Linux implementation. A call to get/setxattr that passes all the checks will still currently return EOPNOTSUPP. Actual support will be added in following commits. Only allow user.* extended attributes for the time being. PiperOrigin-RevId: 285835159
This commit is contained in:
@@ -57,6 +57,7 @@ go_library(
|
||||
"uio.go",
|
||||
"utsname.go",
|
||||
"wait.go",
|
||||
"xattr.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/abi/linux",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package linux
|
||||
|
||||
// Constants for extended attributes.
|
||||
const (
|
||||
XATTR_NAME_MAX = 255
|
||||
XATTR_SIZE_MAX = 65536
|
||||
|
||||
XATTR_CREATE = 1
|
||||
XATTR_REPLACE = 2
|
||||
|
||||
XATTR_USER_PREFIX = "user."
|
||||
XATTR_USER_PREFIX_LEN = len(XATTR_USER_PREFIX)
|
||||
)
|
||||
@@ -270,6 +270,14 @@ func (i *Inode) Getxattr(name string) (string, error) {
|
||||
return i.InodeOperations.Getxattr(i, name)
|
||||
}
|
||||
|
||||
// Setxattr calls i.InodeOperations.Setxattr with i as the Inode.
|
||||
func (i *Inode) Setxattr(name, value string) error {
|
||||
if i.overlay != nil {
|
||||
return overlaySetxattr(i.overlay, name, value)
|
||||
}
|
||||
return i.InodeOperations.Setxattr(i, name, value)
|
||||
}
|
||||
|
||||
// Listxattr calls i.InodeOperations.Listxattr with i as the Inode.
|
||||
func (i *Inode) Listxattr() (map[string]struct{}, error) {
|
||||
if i.overlay != nil {
|
||||
|
||||
@@ -552,6 +552,11 @@ func overlayGetxattr(o *overlayEntry, name string) (string, error) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// TODO(b/146028302): Support setxattr for overlayfs.
|
||||
func overlaySetxattr(o *overlayEntry, name, value string) error {
|
||||
return syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
func overlayListxattr(o *overlayEntry) (map[string]struct{}, error) {
|
||||
o.copyMu.RLock()
|
||||
defer o.copyMu.RUnlock()
|
||||
|
||||
@@ -49,6 +49,7 @@ go_library(
|
||||
"sys_tls.go",
|
||||
"sys_utsname.go",
|
||||
"sys_write.go",
|
||||
"sys_xattr.go",
|
||||
"timespec.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/syscalls/linux",
|
||||
|
||||
@@ -228,10 +228,10 @@ var AMD64 = &kernel.SyscallTable{
|
||||
185: syscalls.Error("security", syserror.ENOSYS, "Not implemented in Linux.", nil),
|
||||
186: syscalls.Supported("gettid", Gettid),
|
||||
187: syscalls.Supported("readahead", Readahead),
|
||||
188: syscalls.Error("setxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
188: syscalls.PartiallySupported("setxattr", Setxattr, "Only supported for tmpfs.", nil),
|
||||
189: syscalls.Error("lsetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
190: syscalls.Error("fsetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
191: syscalls.ErrorWithEvent("getxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
191: syscalls.PartiallySupported("getxattr", Getxattr, "Only supported for tmpfs.", nil),
|
||||
192: syscalls.ErrorWithEvent("lgetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
193: syscalls.ErrorWithEvent("fgetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
194: syscalls.ErrorWithEvent("listxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
|
||||
@@ -41,10 +41,10 @@ var ARM64 = &kernel.SyscallTable{
|
||||
2: syscalls.PartiallySupported("io_submit", IoSubmit, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}),
|
||||
3: syscalls.PartiallySupported("io_cancel", IoCancel, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}),
|
||||
4: syscalls.PartiallySupported("io_getevents", IoGetevents, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}),
|
||||
5: syscalls.Error("setxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
5: syscalls.PartiallySupported("setxattr", Setxattr, "Only supported for tmpfs.", nil),
|
||||
6: syscalls.Error("lsetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
7: syscalls.Error("fsetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
8: syscalls.ErrorWithEvent("getxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
8: syscalls.PartiallySupported("getxattr", Getxattr, "Only supported for tmpfs.", nil),
|
||||
9: syscalls.ErrorWithEvent("lgetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
10: syscalls.ErrorWithEvent("fgetxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
11: syscalls.ErrorWithEvent("listxattr", syserror.ENOTSUP, "Requires filesystem support.", nil),
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// Getxattr implements linux syscall getxattr(2).
|
||||
func Getxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
pathAddr := args[0].Pointer()
|
||||
nameAddr := args[1].Pointer()
|
||||
valueAddr := args[2].Pointer()
|
||||
size := args[3].SizeT()
|
||||
|
||||
path, dirPath, err := copyInPath(t, pathAddr, false /* allowEmpty */)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
valueLen := 0
|
||||
err = fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error {
|
||||
value, err := getxattr(t, d, dirPath, nameAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valueLen = len(value)
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
if size > linux.XATTR_SIZE_MAX {
|
||||
size = linux.XATTR_SIZE_MAX
|
||||
}
|
||||
if valueLen > int(size) {
|
||||
return syserror.ERANGE
|
||||
}
|
||||
|
||||
_, err = t.CopyOutBytes(valueAddr, []byte(value))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return uintptr(valueLen), nil, nil
|
||||
}
|
||||
|
||||
// getxattr implements getxattr from the given *fs.Dirent.
|
||||
func getxattr(t *kernel.Task, d *fs.Dirent, dirPath bool, nameAddr usermem.Addr) (string, error) {
|
||||
if dirPath && !fs.IsDir(d.Inode.StableAttr) {
|
||||
return "", syserror.ENOTDIR
|
||||
}
|
||||
|
||||
if err := checkXattrPermissions(t, d.Inode, fs.PermMask{Read: true}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
name, err := copyInXattrName(t, nameAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(name, linux.XATTR_USER_PREFIX) {
|
||||
return "", syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
return d.Inode.Getxattr(name)
|
||||
}
|
||||
|
||||
// Setxattr implements linux syscall setxattr(2).
|
||||
func Setxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
pathAddr := args[0].Pointer()
|
||||
nameAddr := args[1].Pointer()
|
||||
valueAddr := args[2].Pointer()
|
||||
size := args[3].SizeT()
|
||||
flags := args[4].Uint()
|
||||
|
||||
path, dirPath, err := copyInPath(t, pathAddr, false /* allowEmpty */)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if flags&^(linux.XATTR_CREATE|linux.XATTR_REPLACE) != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error {
|
||||
return setxattr(t, d, dirPath, nameAddr, valueAddr, size, flags)
|
||||
})
|
||||
}
|
||||
|
||||
// setxattr implements setxattr from the given *fs.Dirent.
|
||||
func setxattr(t *kernel.Task, d *fs.Dirent, dirPath bool, nameAddr, valueAddr usermem.Addr, size uint, flags uint32) error {
|
||||
if dirPath && !fs.IsDir(d.Inode.StableAttr) {
|
||||
return syserror.ENOTDIR
|
||||
}
|
||||
|
||||
if err := checkXattrPermissions(t, d.Inode, fs.PermMask{Write: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, err := copyInXattrName(t, nameAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size > linux.XATTR_SIZE_MAX {
|
||||
return syserror.E2BIG
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
if _, err = t.CopyInBytes(valueAddr, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
value := string(buf)
|
||||
|
||||
if !strings.HasPrefix(name, linux.XATTR_USER_PREFIX) {
|
||||
return syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
return d.Inode.Setxattr(name, value)
|
||||
}
|
||||
|
||||
func copyInXattrName(t *kernel.Task, nameAddr usermem.Addr) (string, error) {
|
||||
name, err := t.CopyInString(nameAddr, linux.XATTR_NAME_MAX+1)
|
||||
if err != nil {
|
||||
if err == syserror.ENAMETOOLONG {
|
||||
return "", syserror.ERANGE
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return "", syserror.ERANGE
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func checkXattrPermissions(t *kernel.Task, i *fs.Inode, perms fs.PermMask) error {
|
||||
// Restrict xattrs to regular files and directories.
|
||||
//
|
||||
// In Linux, this restriction technically only applies to xattrs in the
|
||||
// "user.*" namespace, but we don't allow any other xattr prefixes anyway.
|
||||
if !fs.IsRegular(i.StableAttr) && !fs.IsDir(i.StableAttr) {
|
||||
if perms.Write {
|
||||
return syserror.EPERM
|
||||
}
|
||||
return syserror.ENODATA
|
||||
}
|
||||
|
||||
return i.CheckPermission(t, perms)
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
@@ -37,9 +38,6 @@ namespace {
|
||||
class XattrTest : public FileTest {};
|
||||
|
||||
TEST_F(XattrTest, XattrNullName) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
|
||||
EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, /*flags=*/0),
|
||||
@@ -49,9 +47,6 @@ TEST_F(XattrTest, XattrNullName) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, XattrEmptyName) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
|
||||
EXPECT_THAT(setxattr(path, "", nullptr, 0, /*flags=*/0),
|
||||
@@ -60,16 +55,17 @@ TEST_F(XattrTest, XattrEmptyName) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, XattrLargeName) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
std::string name = "user.";
|
||||
name += std::string(XATTR_NAME_MAX - name.length(), 'a');
|
||||
EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0),
|
||||
SyscallSucceeds());
|
||||
EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
if (!IsRunningOnGvisor()) {
|
||||
EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0),
|
||||
SyscallSucceeds());
|
||||
EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0),
|
||||
SyscallSucceedsWithValue(0));
|
||||
}
|
||||
|
||||
name += "a";
|
||||
EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0),
|
||||
@@ -79,9 +75,6 @@ TEST_F(XattrTest, XattrLargeName) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, XattrInvalidPrefix) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
std::string name(XATTR_NAME_MAX, 'a');
|
||||
EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0),
|
||||
@@ -91,9 +84,6 @@ TEST_F(XattrTest, XattrInvalidPrefix) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, XattrReadOnly) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
// Drop capabilities that allow us to override file and directory permissions.
|
||||
ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
|
||||
ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
|
||||
@@ -102,22 +92,28 @@ TEST_F(XattrTest, XattrReadOnly) {
|
||||
char name[] = "user.abc";
|
||||
char val = 'a';
|
||||
size_t size = sizeof(val);
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());
|
||||
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
if (!IsRunningOnGvisor()) {
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
ASSERT_NO_ERRNO(testing::Chmod(test_file_name_, S_IRUSR));
|
||||
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0),
|
||||
SyscallFailsWithErrno(EACCES));
|
||||
|
||||
char buf = '-';
|
||||
EXPECT_THAT(getxattr(path, name, &buf, size), SyscallSucceedsWithValue(size));
|
||||
EXPECT_EQ(buf, val);
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
if (!IsRunningOnGvisor()) {
|
||||
char buf = '-';
|
||||
EXPECT_THAT(getxattr(path, name, &buf, size),
|
||||
SyscallSucceedsWithValue(size));
|
||||
EXPECT_EQ(buf, val);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, XattrWriteOnly) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
// Drop capabilities that allow us to override file and directory permissions.
|
||||
ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
|
||||
ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
|
||||
@@ -128,7 +124,12 @@ TEST_F(XattrTest, XattrWriteOnly) {
|
||||
char name[] = "user.abc";
|
||||
char val = 'a';
|
||||
size_t size = sizeof(val);
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());
|
||||
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
if (!IsRunningOnGvisor()) {
|
||||
EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(EACCES));
|
||||
}
|
||||
@@ -172,9 +173,6 @@ TEST_F(XattrTest, XattrOnSymlink) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, XattrOnInvalidFileTypes) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
char name[] = "user.abc";
|
||||
|
||||
char char_device[] = "/dev/zero";
|
||||
@@ -226,9 +224,6 @@ TEST_F(XattrTest, SetxattrZeroSize) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, SetxattrSizeTooLarge) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
char name[] = "user.abc";
|
||||
|
||||
@@ -240,19 +235,24 @@ TEST_F(XattrTest, SetxattrSizeTooLarge) {
|
||||
EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0),
|
||||
SyscallFailsWithErrno(E2BIG));
|
||||
|
||||
EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
if (!IsRunningOnGvisor()) {
|
||||
EXPECT_THAT(getxattr(path, name, nullptr, 0),
|
||||
SyscallFailsWithErrno(ENODATA));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, SetxattrNullValueAndNonzeroSize) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
char name[] = "user.abc";
|
||||
EXPECT_THAT(setxattr(path, name, nullptr, 1, /*flags=*/0),
|
||||
SyscallFailsWithErrno(EFAULT));
|
||||
|
||||
EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
if (!IsRunningOnGvisor()) {
|
||||
EXPECT_THAT(getxattr(path, name, nullptr, 0),
|
||||
SyscallFailsWithErrno(ENODATA));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, SetxattrNullValueAndZeroSize) {
|
||||
@@ -350,9 +350,6 @@ TEST_F(XattrTest, SetxattrReplaceFlag) {
|
||||
}
|
||||
|
||||
TEST_F(XattrTest, SetxattrInvalidFlags) {
|
||||
// TODO(b/127675828): Support setxattr and getxattr.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
|
||||
const char* path = test_file_name_.c_str();
|
||||
int invalid_flags = 0xff;
|
||||
EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, invalid_flags),
|
||||
|
||||
Reference in New Issue
Block a user