// 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.

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/capability.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>

#include <cstddef>
#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/container/flat_hash_set.h"
#include "absl/strings/string_view.h"
#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/linux_capability_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"

namespace gvisor {
namespace testing {

namespace {

using ::gvisor::testing::IsTmpfs;
using ::testing::AnyOf;

constexpr char kUnshareAndSetTrustedXattrInNewUserns[] = "--set_trusted_xattr";
constexpr int kNobodyUID = 65534;

class XattrTest : public FileTest {};

TEST_F(XattrTest, XattrNonexistentFile) {
  const char* path = "/does/not/exist";
  const char* name = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0),
              SyscallFailsWithErrno(ENOENT));
  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENOENT));
  EXPECT_THAT(listxattr(path, nullptr, 0), SyscallFailsWithErrno(ENOENT));
  EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(ENOENT));
}

TEST_F(XattrTest, XattrNullName) {
  const char* path = test_file_name_.c_str();

  EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, /*flags=*/0),
              SyscallFailsWithErrno(EFAULT));
  EXPECT_THAT(getxattr(path, nullptr, nullptr, 0),
              SyscallFailsWithErrno(EFAULT));
  EXPECT_THAT(removexattr(path, nullptr), SyscallFailsWithErrno(EFAULT));
}

TEST_F(XattrTest, XattrEmptyName) {
  const char* path = test_file_name_.c_str();

  EXPECT_THAT(setxattr(path, "", nullptr, 0, /*flags=*/0),
              SyscallFailsWithErrno(ERANGE));
  EXPECT_THAT(getxattr(path, "", nullptr, 0), SyscallFailsWithErrno(ERANGE));
  EXPECT_THAT(removexattr(path, ""), SyscallFailsWithErrno(ERANGE));
}

TEST_F(XattrTest, XattrLargeName) {
  const char* path = test_file_name_.c_str();
  std::string name = "user.";
  name += std::string(XATTR_NAME_MAX - name.length(), 'a');

  if (!IsRunningOnGvisor()) {
    // In gVisor, access to xattrs is controlled with an explicit list of
    // allowed names. This name isn't going to be configured to allow access, so
    // don't test it.
    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),
              SyscallFailsWithErrno(ERANGE));
  EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0),
              SyscallFailsWithErrno(ERANGE));
  EXPECT_THAT(removexattr(path, name.c_str()), SyscallFailsWithErrno(ERANGE));
}

TEST_F(XattrTest, XattrInvalidPrefix) {
  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),
              SyscallFailsWithErrno(EOPNOTSUPP));
  EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0),
              SyscallFailsWithErrno(EOPNOTSUPP));
  EXPECT_THAT(removexattr(path, name.c_str()),
              SyscallFailsWithErrno(EOPNOTSUPP));
}

TEST_F(XattrTest, SecurityCapacityXattr) {
  SKIP_IF(!IsRunningOnGvisor());
  const char* path = test_file_name_.c_str();
  const char name[] = "security.capacity";
  const std::string val = "";
  EXPECT_THAT(lsetxattr(path, name, &val, val.size(), 0),
              SyscallFailsWithErrno(EOPNOTSUPP));
  int buf = 0;
  EXPECT_THAT(lgetxattr(path, name, &buf, /*size=*/128),
              SyscallFailsWithErrno(AnyOf(ENODATA, EOPNOTSUPP)));
  EXPECT_THAT(lremovexattr(path, name), SyscallFailsWithErrno(EOPNOTSUPP));
}

TEST_F(XattrTest, WriteRemovesSecurityCapability) {
  // Gofer does not support security namespace.
  SKIP_IF(IsRunningOnGvisor() &&
          ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(test_file_name_)));
  SKIP_IF(getuid() != 0);
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETFCAP)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));

  const char* path = test_file_name_.c_str();
  const char name[] = "security.capability";

  struct {
    uint32_t magic_etc;
    uint32_t permitted_lo;
    uint32_t inheritable_lo;
    uint32_t permitted_hi;
    uint32_t inheritable_hi;
  } cap_data = {};
  cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE;
  cap_data.permitted_lo = 1 << CAP_SETUID;

  ASSERT_THAT(chmod(path, 0666), SyscallSucceeds());
  ASSERT_THAT(setxattr(path, name, &cap_data, sizeof(cap_data), 0),
              SyscallSucceeds());

  char buf[sizeof(cap_data)] = {};
  ASSERT_THAT(getxattr(path, name, buf, sizeof(buf)),
              SyscallSucceedsWithValue(sizeof(cap_data)));

  pid_t pid = fork();
  ASSERT_THAT(pid, SyscallSucceeds());
  if (pid == 0) {
    if (syscall(SYS_setuid, kNobodyUID) < 0) _exit(1);
    char data[] = "overwritten";
    int fd = open(path, O_WRONLY);
    if (fd < 0) _exit(2);
    if (write(fd, data, sizeof(data)) < 0) _exit(3);
    close(fd);
    _exit(0);
  }
  int status;
  ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
  ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
      << "child exited with status " << status;

  EXPECT_THAT(getxattr(path, name, buf, sizeof(buf)),
              SyscallFailsWithErrno(ENODATA));
}

// Do not allow save/restore cycles after making the test file read-only, as
// the restore will fail to open it with r/w permissions.
TEST_F(XattrTest, XattrReadOnly) {
  // Drop capabilities that allow us to override file and directory permissions.
  AutoCapability cap1(CAP_DAC_OVERRIDE, false);
  AutoCapability cap2(CAP_DAC_READ_SEARCH, false);

  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  size_t size = sizeof(val);

  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());

  DisableSave ds;
  ASSERT_NO_ERRNO(testing::Chmod(test_file_name_, S_IRUSR));

  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0),
              SyscallFailsWithErrno(EACCES));
  EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EACCES));

  char buf = '-';
  EXPECT_THAT(getxattr(path, name, &buf, size), SyscallSucceedsWithValue(size));
  EXPECT_EQ(buf, val);

  char list[sizeof(name)];
  EXPECT_THAT(listxattr(path, list, sizeof(list)),
              SyscallSucceedsWithValue(sizeof(name)));
  EXPECT_STREQ(list, name);
}

// Do not allow save/restore cycles after making the test file write-only, as
// the restore will fail to open it with r/w permissions.
TEST_F(XattrTest, XattrWriteOnly) {
  // Drop capabilities that allow us to override file and directory permissions.
  AutoCapability cap1(CAP_DAC_OVERRIDE, false);
  AutoCapability cap2(CAP_DAC_READ_SEARCH, false);

  DisableSave ds;
  ASSERT_NO_ERRNO(testing::Chmod(test_file_name_, S_IWUSR));

  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  size_t size = sizeof(val);

  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());

  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(EACCES));

  // listxattr will succeed even without read permissions.
  char list[sizeof(name)];
  EXPECT_THAT(listxattr(path, list, sizeof(list)),
              SyscallSucceedsWithValue(sizeof(name)));
  EXPECT_STREQ(list, name);

  EXPECT_THAT(removexattr(path, name), SyscallSucceeds());
}

TEST_F(XattrTest, XattrTrustedWithNonadmin) {
  // Gofer does not support trusted namespace.
  SKIP_IF(IsRunningOnGvisor() &&
          ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(test_file_name_)));
  SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const char* path = test_file_name_.c_str();
  const char name[] = "trusted.abc";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0),
              SyscallFailsWithErrno(EPERM));
  EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM));
  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
}

TEST_F(XattrTest, XattrOnDirectory) {
  TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(dir.path().c_str(), name, nullptr, 0, /*flags=*/0),
              SyscallSucceeds());
  EXPECT_THAT(getxattr(dir.path().c_str(), name, nullptr, 0),
              SyscallSucceedsWithValue(0));

  // Overlay may have private attributes. Even though it is not returned to
  // userspace, it is counted against the `size` argument in listxattr(2).
  // See fs/overlayfs/inode.c:ovl_listxattr(). Notice that `size` is not
  // extended to accommodate for private attributes that are filtered later.
  // So use a large enough buffer for xattr list.
  char list[64];
  EXPECT_THAT(listxattr(dir.path().c_str(), list, sizeof(list)),
              SyscallSucceedsWithValue(sizeof(name)));
  EXPECT_STREQ(list, name);

  EXPECT_THAT(removexattr(dir.path().c_str(), name), SyscallSucceeds());
}

TEST_F(XattrTest, XattrOnSymlink) {
  TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateSymlinkTo(dir.path(), test_file_name_));
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(link.path().c_str(), name, nullptr, 0, /*flags=*/0),
              SyscallSucceeds());
  EXPECT_THAT(getxattr(link.path().c_str(), name, nullptr, 0),
              SyscallSucceedsWithValue(0));

  char list[sizeof(name)];
  EXPECT_THAT(listxattr(link.path().c_str(), list, sizeof(list)),
              SyscallSucceedsWithValue(sizeof(name)));
  EXPECT_STREQ(list, name);

  EXPECT_THAT(removexattr(link.path().c_str(), name), SyscallSucceeds());
}

TEST_F(XattrTest, XattrOnInvalidFileTypes) {
  const char name[] = "user.test";

  char char_device[] = "/dev/zero";
  EXPECT_THAT(
      setxattr(char_device, name, nullptr, 0, /*flags=*/0),
      AnyOf(SyscallFailsWithErrno(EPERM), SyscallFailsWithErrno(EROFS)));
  EXPECT_THAT(getxattr(char_device, name, nullptr, 0),
              SyscallFailsWithErrno(ENODATA));
  EXPECT_THAT(listxattr(char_device, nullptr, 0), SyscallSucceedsWithValue(0));

  // Use tmpfs, where creation of named pipes is supported.
  const std::string fifo = NewTempAbsPathInDir("/dev/shm");
  const char* path = fifo.c_str();
  EXPECT_THAT(mknod(path, S_IFIFO | S_IRUSR | S_IWUSR, 0), SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0),
              SyscallFailsWithErrno(EPERM));
  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
  EXPECT_THAT(listxattr(path, nullptr, 0), SyscallSucceedsWithValue(0));
  EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM));
}

TEST_F(XattrTest, SetXattrSizeSmallerThanValue) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  std::vector<char> val = {'a', 'a'};
  size_t size = 1;
  EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0),
              SyscallSucceeds());

  std::vector<char> buf = {'-', '-'};
  std::vector<char> expected_buf = {'a', '-'};
  EXPECT_THAT(getxattr(path, name, buf.data(), buf.size()),
              SyscallSucceedsWithValue(size));
  EXPECT_EQ(buf, expected_buf);
}

TEST_F(XattrTest, SetXattrZeroSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  EXPECT_THAT(setxattr(path, name, &val, 0, /*flags=*/0), SyscallSucceeds());

  char buf = '-';
  EXPECT_THAT(getxattr(path, name, &buf, 1), SyscallSucceedsWithValue(0));
  EXPECT_EQ(buf, '-');
}

TEST_F(XattrTest, SetXattrSizeTooLarge) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";

  // Note that each particular fs implementation may stipulate a lower size
  // limit, in which case we actually may fail (e.g. error with ENOSPC) for
  // some sizes under XATTR_SIZE_MAX.
  size_t size = XATTR_SIZE_MAX + 1;
  std::vector<char> val(size);
  EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0),
              SyscallFailsWithErrno(E2BIG));

  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
}

TEST_F(XattrTest, SetXattrNullValueAndNonzeroSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
  EXPECT_THAT(setxattr(path, name, nullptr, 1, /*flags=*/0),
              SyscallFailsWithErrno(EFAULT));
#pragma GCC diagnostic pop

  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
}

TEST_F(XattrTest, SetXattrNullValueAndZeroSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds());

  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0));
}

TEST_F(XattrTest, SetXattrValueTooLargeButOKSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  std::vector<char> val(XATTR_SIZE_MAX + 1);
  std::fill(val.begin(), val.end(), 'a');
  size_t size = 1;
  EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0),
              SyscallSucceeds());

  std::vector<char> buf = {'-', '-'};
  std::vector<char> expected_buf = {'a', '-'};
  EXPECT_THAT(getxattr(path, name, buf.data(), size),
              SyscallSucceedsWithValue(size));
  EXPECT_EQ(buf, expected_buf);
}

TEST_F(XattrTest, SetXattrReplaceWithSmaller) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  std::vector<char> val = {'a', 'a'};
  EXPECT_THAT(setxattr(path, name, val.data(), 2, /*flags=*/0),
              SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name, val.data(), 1, /*flags=*/0),
              SyscallSucceeds());

  std::vector<char> buf = {'-', '-'};
  std::vector<char> expected_buf = {'a', '-'};
  EXPECT_THAT(getxattr(path, name, buf.data(), 2), SyscallSucceedsWithValue(1));
  EXPECT_EQ(buf, expected_buf);
}

TEST_F(XattrTest, SetXattrReplaceWithLarger) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  std::vector<char> val = {'a', 'a'};
  EXPECT_THAT(setxattr(path, name, val.data(), 1, /*flags=*/0),
              SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name, val.data(), 2, /*flags=*/0),
              SyscallSucceeds());

  std::vector<char> buf = {'-', '-'};
  EXPECT_THAT(getxattr(path, name, buf.data(), 2), SyscallSucceedsWithValue(2));
  EXPECT_EQ(buf, val);
}

TEST_F(XattrTest, SetXattrCreateFlag) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_CREATE),
              SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_CREATE),
              SyscallFailsWithErrno(EEXIST));

  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0));
}

TEST_F(XattrTest, SetXattrReplaceFlag) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_REPLACE),
              SyscallFailsWithErrno(ENODATA));
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_REPLACE),
              SyscallSucceeds());

  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0));
}

TEST_F(XattrTest, SetXattrInvalidFlags) {
  const char* path = test_file_name_.c_str();
  int invalid_flags = 0xff;
  EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, invalid_flags),
              SyscallFailsWithErrno(EINVAL));
}

TEST_F(XattrTest, GetXattr) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  int val = 1234;
  size_t size = sizeof(val);
  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());

  int buf = 0;
  EXPECT_THAT(getxattr(path, name, &buf, size), SyscallSucceedsWithValue(size));
  EXPECT_EQ(buf, val);
}

TEST_F(XattrTest, GetXattrSizeSmallerThanValue) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  std::vector<char> val = {'a', 'a'};
  size_t size = val.size();
  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());

  char buf = '-';
  EXPECT_THAT(getxattr(path, name, &buf, 1), SyscallFailsWithErrno(ERANGE));
  EXPECT_EQ(buf, '-');
}

TEST_F(XattrTest, GetXattrSizeLargerThanValue) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  EXPECT_THAT(setxattr(path, name, &val, 1, /*flags=*/0), SyscallSucceeds());

  std::vector<char> buf(XATTR_SIZE_MAX);
  std::fill(buf.begin(), buf.end(), '-');
  std::vector<char> expected_buf = buf;
  expected_buf[0] = 'a';
  EXPECT_THAT(getxattr(path, name, buf.data(), buf.size()),
              SyscallSucceedsWithValue(1));
  EXPECT_EQ(buf, expected_buf);
}

TEST_F(XattrTest, GetXattrZeroSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  EXPECT_THAT(setxattr(path, name, &val, sizeof(val), /*flags=*/0),
              SyscallSucceeds());

  char buf = '-';
  EXPECT_THAT(getxattr(path, name, &buf, 0),
              SyscallSucceedsWithValue(sizeof(val)));
  EXPECT_EQ(buf, '-');
}

TEST_F(XattrTest, GetXattrSizeTooLarge) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  EXPECT_THAT(setxattr(path, name, &val, sizeof(val), /*flags=*/0),
              SyscallSucceeds());

  std::vector<char> buf(XATTR_SIZE_MAX + 1);
  std::fill(buf.begin(), buf.end(), '-');
  std::vector<char> expected_buf = buf;
  expected_buf[0] = 'a';
  EXPECT_THAT(getxattr(path, name, buf.data(), buf.size()),
              SyscallSucceedsWithValue(sizeof(val)));
  EXPECT_EQ(buf, expected_buf);
}

TEST_F(XattrTest, GetXattrNullValue) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  size_t size = sizeof(val);
  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
  EXPECT_THAT(getxattr(path, name, nullptr, size),
              SyscallFailsWithErrno(EFAULT));
#pragma GCC diagnostic pop
}

TEST_F(XattrTest, GetXattrNullValueAndZeroSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  char val = 'a';
  size_t size = sizeof(val);
  // Set value with zero size.
  EXPECT_THAT(setxattr(path, name, &val, 0, /*flags=*/0), SyscallSucceeds());
  // Get value with nonzero size.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
  EXPECT_THAT(getxattr(path, name, nullptr, size), SyscallSucceedsWithValue(0));
#pragma GCC diagnostic pop

  // Set value with nonzero size.
  EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());
  // Get value with zero size.
  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(size));
}

TEST_F(XattrTest, GetXattrNonexistentName) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
}

TEST_F(XattrTest, ListXattr) {
  const char* path = test_file_name_.c_str();
  const std::string name = "user.test";
  const std::string name2 = "user.test2";
  const std::string name3 = "user.test3";
  EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0),
              SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name2.c_str(), nullptr, 0, /*flags=*/0),
              SyscallSucceeds());
  EXPECT_THAT(setxattr(path, name3.c_str(), nullptr, 0, /*flags=*/0),
              SyscallSucceeds());

  std::vector<char> list(name.size() + 1 + name2.size() + 1 + name3.size() + 1);
  char* buf = list.data();
  EXPECT_THAT(listxattr(path, buf, XATTR_SIZE_MAX),
              SyscallSucceedsWithValue(list.size()));

  absl::flat_hash_set<std::string> got = {};
  for (char* p = buf; p < buf + list.size(); p += strlen(p) + 1) {
    got.insert(std::string{p});
  }

  absl::flat_hash_set<std::string> expected = {name, name2, name3};
  EXPECT_EQ(got, expected);
}

TEST_F(XattrTest, ListXattrNoXattrs) {
  const char* path = test_file_name_.c_str();

  std::vector<char> list, expected;
  EXPECT_THAT(listxattr(path, list.data(), sizeof(list)),
              SyscallSucceedsWithValue(0));
  EXPECT_EQ(list, expected);

  // ListXattr should succeed if there are no attributes, even if the buffer
  // passed in is a nullptr.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
  EXPECT_THAT(listxattr(path, nullptr, sizeof(list)),
              SyscallSucceedsWithValue(0));
#pragma GCC diagnostic pop
}

TEST_F(XattrTest, ListXattrNullBuffer) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds());

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
  EXPECT_THAT(listxattr(path, nullptr, sizeof(name)),
              SyscallFailsWithErrno(EFAULT));
#pragma GCC diagnostic pop
}

TEST_F(XattrTest, ListXattrSizeTooSmall) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds());

  char list[sizeof(name) - 1];
  EXPECT_THAT(listxattr(path, list, sizeof(list)),
              SyscallFailsWithErrno(ERANGE));
}

TEST_F(XattrTest, ListXattrZeroSize) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds());
  EXPECT_THAT(listxattr(path, nullptr, 0),
              SyscallSucceedsWithValue(sizeof(name)));
}

TEST_F(XattrTest, RemoveXattr) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds());
  EXPECT_THAT(removexattr(path, name), SyscallSucceeds());
  EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA));
}

TEST_F(XattrTest, RemoveXattrNonexistentName) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(ENODATA));
}

TEST_F(XattrTest, LXattrOnSymlink) {
  const char name[] = "user.test";
  TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateSymlinkTo(dir.path(), test_file_name_));

  EXPECT_THAT(lsetxattr(link.path().c_str(), name, nullptr, 0, 0),
              SyscallFailsWithErrno(EPERM));
  EXPECT_THAT(lgetxattr(link.path().c_str(), name, nullptr, 0),
              SyscallFailsWithErrno(ENODATA));
  EXPECT_THAT(llistxattr(link.path().c_str(), nullptr, 0),
              SyscallSucceedsWithValue(0));
  EXPECT_THAT(lremovexattr(link.path().c_str(), name),
              SyscallFailsWithErrno(EPERM));
}

TEST_F(XattrTest, LXattrOnNonsymlink) {
  const char* path = test_file_name_.c_str();
  const char name[] = "user.test";
  int val = 1234;
  size_t size = sizeof(val);
  EXPECT_THAT(lsetxattr(path, name, &val, size, /*flags=*/0),
              SyscallSucceeds());

  int buf = 0;
  EXPECT_THAT(lgetxattr(path, name, &buf, size),
              SyscallSucceedsWithValue(size));
  EXPECT_EQ(buf, val);

  char list[sizeof(name)];
  EXPECT_THAT(llistxattr(path, list, sizeof(list)),
              SyscallSucceedsWithValue(sizeof(name)));
  EXPECT_STREQ(list, name);

  EXPECT_THAT(lremovexattr(path, name), SyscallSucceeds());
}

TEST_F(XattrTest, XattrWithFD) {
  const FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_.c_str(), 0));
  const char name[] = "user.test";
  int val = 1234;
  size_t size = sizeof(val);
  EXPECT_THAT(fsetxattr(fd.get(), name, &val, size, /*flags=*/0),
              SyscallSucceeds());

  int buf = 0;
  EXPECT_THAT(fgetxattr(fd.get(), name, &buf, size),
              SyscallSucceedsWithValue(size));
  EXPECT_EQ(buf, val);

  char list[sizeof(name)];
  EXPECT_THAT(flistxattr(fd.get(), list, sizeof(list)),
              SyscallSucceedsWithValue(sizeof(name)));
  EXPECT_STREQ(list, name);

  EXPECT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds());
}

TEST_F(XattrTest, XattrWithOPath) {
  const FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_.c_str(), O_PATH));
  const char name[] = "user.test";
  int val = 1234;
  size_t size = sizeof(val);

  // Bionic's implementations of f*xattr() use *xattr() when O_PATH is set
  // to circumvent the behavior this is testing for.
  // Use syscall() here to avoid running through Bionic on Android.
  EXPECT_THAT(syscall(SYS_fsetxattr, fd.get(), name, &val, size, /*flags=*/0),
              SyscallFailsWithErrno(EBADF));

  int buf;
  EXPECT_THAT(syscall(SYS_fgetxattr, fd.get(), name, &buf, size),
              SyscallFailsWithErrno(EBADF));

  char list[sizeof(name)];
  EXPECT_THAT(syscall(SYS_flistxattr, fd.get(), list, sizeof(list)),
              SyscallFailsWithErrno(EBADF));

  EXPECT_THAT(syscall(SYS_fremovexattr, fd.get(), name),
              SyscallFailsWithErrno(EBADF));
}

TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) {
  // Gofer does not support trusted namespace.
  SKIP_IF(IsRunningOnGvisor() &&
          ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(test_file_name_)));

  const char* path = test_file_name_.c_str();
  const char name[] = "trusted.test";

  // Writing to the trusted.* xattr namespace requires CAP_SYS_ADMIN in the root
  // user namespace. There's no easy way to check that, other than trying the
  // operation and seeing what happens. We'll call removexattr because it's
  // simplest. If we have CAP_SYS_ADMIN, then we get ENODATA.
  if (removexattr(path, name) < 0) {
    SKIP_IF(errno == EPERM);
    if (errno != ENODATA) {
      FAIL() << "unexpected errno from removexattr: " << errno;
    }
  }

  // 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) {
  // Gofer does not support trusted namespace.
  SKIP_IF(IsRunningOnGvisor() &&
          ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(test_file_name_)));

  // Drop CAP_SYS_ADMIN if we have it.
  AutoCapability cap(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));
}

int UnshareAndSetxattrInNewUserns(const char* path) {
  if (syscall(SYS_unshare, CLONE_NEWUSER) == -1) {
    return 1;
  }

  const char name[] = "trusted.test";
  char val = 'a';
  size_t size = sizeof(val);
  if (setxattr(path, name, &val, size, /*flags=*/0) != -1) {
    return 2;
  }
  return (errno == EPERM) ? 0 : 3;
}

TEST_F(XattrTest, TrustedNamespaceWithCapSysAdminInNewUserns) {
  // Gofer does not support trusted namespace.
  SKIP_IF(IsRunningOnGvisor() &&
          ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(test_file_name_)));

  // Drop CAP_SYS_ADMIN if we have it, this test tries to beat the lack of
  // the cap by gaining it in a new userns.
  AutoCapability cap(CAP_SYS_ADMIN, false);

  // We fork and exec into setxattr_in_new_userns() which unshares to gain the
  // missing cap, and then tries to set a trusted xattr. This should fail with
  // EPERM.
  int child_pid;
  auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
      ForkAndExec("/proc/self/exe",
                  {
                      "/proc/self/exe",
                      kUnshareAndSetTrustedXattrInNewUserns,
                      test_file_name_,
                  },
                  {}, /*fn=*/nullptr, /*child=*/&child_pid,
                  /*execve_errno=*/nullptr));
  int status;
  ASSERT_THAT(child_pid, SyscallSucceeds());
  ASSERT_THAT(waitpid(child_pid, &status, 0),
              SyscallSucceedsWithValue(child_pid));

  // setxattr_in_new_userns() exits with 0 only if it sees an EPERM.
  EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
      << "status = " << status;
}

}  // namespace

}  // namespace testing
}  // namespace gvisor

int main(int argc, char** argv) {
  // TrustedNamespaceWithCapSysAdminInNewUserns will exec with the following
  // flag, but we avoid using absl::GetFlag because we intend to unshare(), an
  // act that demands us to be single-threaded, which may not be true after
  // TestInit().
  for (int i = 0; i < argc; i++) {
    absl::string_view arg(argv[i]);
    if (arg == gvisor::testing::kUnshareAndSetTrustedXattrInNewUserns) {
      // The next argument is the path to set the trusted xattr on.
      if (i + 1 < argc) {
        return gvisor::testing::UnshareAndSetxattrInNewUserns(
            argv[i + 1]);
      }
      return 4;
    }
  }

  gvisor::testing::TestInit(&argc, &argv);
  return gvisor::testing::RunAllTests();
}
