// Copyright 2018 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 <asm-generic/errno.h>
#include <linux/keyctl.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>

#include <cerrno>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/random.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "test/util/posix_error.h"
#include "test/util/thread_util.h"

#define KEY_POS_VIEW 0x01000000
#define KEY_POS_READ 0x02000000
#define KEY_POS_WRITE 0x04000000
#define KEY_POS_SEARCH 0x08000000
#define KEY_POS_LINK 0x10000000
#define KEY_POS_SETATTR 0x20000000

#define KEY_USR_VIEW 0x00010000
#define KEY_USR_READ 0x00020000
#define KEY_USR_WRITE 0x00040000
#define KEY_USR_SEARCH 0x00080000
#define KEY_USR_LINK 0x00100000
#define KEY_USR_SETATTR 0x00200000

#define KEY_GRP_VIEW 0x00000100
#define KEY_GRP_READ 0x00000200
#define KEY_GRP_WRITE 0x00000400
#define KEY_GRP_SEARCH 0x00000800
#define KEY_GRP_LINK 0x00001000
#define KEY_GRP_SETATTR 0x00002000

#define KEY_OTH_VIEW 0x00000001
#define KEY_OTH_READ 0x00000002
#define KEY_OTH_WRITE 0x00000004
#define KEY_OTH_SEARCH 0x00000008
#define KEY_OTH_LINK 0x00000010
#define KEY_OTH_SETATTR 0x00000020

namespace gvisor {
namespace testing {
namespace {

// keyctl is a cosmetic wrapper for the keyctl(2) system call.
static inline PosixErrorOr<int64_t> keyctl(int operation, uint64_t arg2,
                                           uint64_t arg3, uint64_t arg4,
                                           uint64_t arg5) {
  int64_t ret = syscall(__NR_keyctl, operation, arg2, arg3, arg4, arg5);
  if (ret == -1) {
    return PosixError(
        errno, absl::StrFormat("keyctl(%d, %d, %d, %d, %d) failed", operation,
                               arg2, arg3, arg4, arg5));
  }
  return ret;
}

static inline PosixErrorOr<int64_t> keyctl(int operation) {
  return keyctl(operation, 0, 0, 0, 0);
}

static inline PosixErrorOr<int64_t> keyctl(int operation, uint64_t arg2) {
  return keyctl(operation, arg2, 0, 0, 0);
}

static inline PosixErrorOr<int64_t> keyctl(int operation, uint64_t arg2,
                                           uint64_t arg3) {
  return keyctl(operation, arg2, arg3, 0, 0);
}

// DescribedKey is the description of a key.
struct DescribedKey {
  int64_t key_id;
  std::string full_desc;
  std::string type;
  uint64_t uid;
  uint64_t gid;
  uint64_t perm;
  std::string description;
};

std::string DescribedKeyString(const DescribedKey& described_key) {
  std::string process_perms = "??????";
  uint64_t perms = described_key.perm;
  process_perms[0] = (perms & KEY_POS_VIEW) == 0 ? '-' : 'v';     // view
  process_perms[1] = (perms & KEY_POS_READ) == 0 ? '-' : 'r';     // read
  process_perms[2] = (perms & KEY_POS_WRITE) == 0 ? '-' : 'w';    // write
  process_perms[3] = (perms & KEY_POS_SEARCH) == 0 ? '-' : 's';   // search
  process_perms[4] = (perms & KEY_POS_LINK) == 0 ? '-' : 'l';     // link
  process_perms[5] = (perms & KEY_POS_SETATTR) == 0 ? '-' : 'a';  // setattr
  std::string user_perms = "??????";
  user_perms[0] = (perms & KEY_USR_VIEW) == 0 ? '-' : 'v';     // view
  user_perms[1] = (perms & KEY_USR_READ) == 0 ? '-' : 'r';     // read
  user_perms[2] = (perms & KEY_USR_WRITE) == 0 ? '-' : 'w';    // write
  user_perms[3] = (perms & KEY_USR_SEARCH) == 0 ? '-' : 's';   // search
  user_perms[4] = (perms & KEY_USR_LINK) == 0 ? '-' : 'l';     // link
  user_perms[5] = (perms & KEY_USR_SETATTR) == 0 ? '-' : 'a';  // setattr
  std::string group_perms = "??????";
  group_perms[0] = (perms & KEY_GRP_VIEW) == 0 ? '-' : 'v';     // view
  group_perms[1] = (perms & KEY_GRP_READ) == 0 ? '-' : 'r';     // read
  group_perms[2] = (perms & KEY_GRP_WRITE) == 0 ? '-' : 'w';    // write
  group_perms[3] = (perms & KEY_GRP_SEARCH) == 0 ? '-' : 's';   // search
  group_perms[4] = (perms & KEY_GRP_LINK) == 0 ? '-' : 'l';     // link
  group_perms[5] = (perms & KEY_GRP_SETATTR) == 0 ? '-' : 'a';  // setattr
  std::string other_perms = "??????";
  other_perms[0] = (perms & KEY_OTH_VIEW) == 0 ? '-' : 'v';     // view
  other_perms[1] = (perms & KEY_OTH_READ) == 0 ? '-' : 'r';     // read
  other_perms[2] = (perms & KEY_OTH_WRITE) == 0 ? '-' : 'w';    // write
  other_perms[3] = (perms & KEY_OTH_SEARCH) == 0 ? '-' : 's';   // search
  other_perms[4] = (perms & KEY_OTH_LINK) == 0 ? '-' : 'l';     // link
  other_perms[5] = (perms & KEY_OTH_SETATTR) == 0 ? '-' : 'a';  // setattr

  return absl::StrFormat(
      "id=%d type=%s uid=%d gid=%d perms=0x%x "
      "[process=%s,user=%s,group=%s,other=%s] desc=%s",
      described_key.key_id, described_key.type, described_key.uid,
      described_key.gid, described_key.perm, process_perms, user_perms,
      group_perms, other_perms, described_key.description);
}

bool operator==(const DescribedKey& lhs, const DescribedKey& rhs) {
  if (lhs.key_id != rhs.key_id) {
    return false;
  }
  if (lhs.type != rhs.type) {
    return false;
  }
  if (lhs.uid != rhs.uid) {
    return false;
  }
  if (lhs.gid != rhs.gid) {
    return false;
  }
  if (lhs.perm != rhs.perm) {
    return false;
  }
  if (lhs.description != rhs.description) {
    return false;
  }
  return true;
}

bool operator!=(const DescribedKey& lhs, const DescribedKey& rhs) {
  return !(lhs == rhs);
}

PosixErrorOr<DescribedKey> DescribeKey(int64_t key_id) {
  ASSIGN_OR_RETURN_ERRNO(int64_t resolved_id,
                         keyctl(KEYCTL_GET_KEYRING_ID, key_id));
  if (resolved_id <= 0) {
    if (key_id == KEY_SPEC_SESSION_KEYRING) {
      return PosixError(
          -1, absl::StrFormat("Could not resolve session keyring (errno=%d)",
                              errno));
    }
    return PosixError(
        -1, absl::StrFormat("Could not resolve key id %d (errno=%d)", key_id,
                            errno));
  }
  DescribedKey described_key;
  described_key.key_id = resolved_id;
  char described_key_buf[1024];
  ASSIGN_OR_RETURN_ERRNO(
      int64_t buf_bytes,
      keyctl(KEYCTL_DESCRIBE, key_id, (uint64_t)(described_key_buf), 1024, 0));
  if (buf_bytes <= 0) {
    if (key_id == KEY_SPEC_SESSION_KEYRING) {
      return PosixError(-1, "Could not describe session keyring");
    }
    return PosixError(-1, absl::StrFormat("Could not describe key %d", key_id));
  }
  // Remove one byte from the key size because the returned length
  // includes the \0 at the end of the buffer.
  described_key.full_desc = std::string(described_key_buf, buf_bytes - 1);
  int i = 0;
  for (absl::string_view element :
       absl::StrSplit(described_key.full_desc, ';')) {
    switch (i) {
      case 0:
        described_key.type = element;
        break;
      case 1:
        if (!absl::SimpleAtoi(element, &described_key.uid)) {
          return PosixError(-1, absl::StrFormat("Could not parse uid from: %s",
                                                described_key.full_desc));
        }
        break;
      case 2:
        if (!absl::SimpleAtoi(element, &described_key.gid)) {
          return PosixError(-1, absl::StrFormat("Could not parse gid from: %s",
                                                described_key.full_desc));
        }
        break;
      case 3:
        described_key.perm = std::stoull(std::string(element), nullptr, 16);
        break;
      case 4:
        described_key.description = std::string(element);
        break;
      default:
        return PosixError(
            -1,
            absl::StrFormat(
                "Key string had more than the expected number of elements: %s",
                described_key.full_desc));
    }
    i++;
  }
  return described_key;
}

TEST(KeysTest, GetCurrentSessionKeyring) {
  DescribedKey key =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  std::cerr << "Session key: " << DescribedKeyString(key) << std::endl;
  EXPECT_TRUE(absl::StartsWith(key.description, "_ses"))
      << "Unexpected name for session keyring";
}

TEST(KeysTest, GetCurrentSessionKeyringViaID) {
  DescribedKey key_via_special_id =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  std::cerr << "Session key (retrieved via KEY_SPEC_SESSION_KEYRING): "
            << DescribedKeyString(key_via_special_id) << std::endl;
  DescribedKey key_via_actual_id =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(key_via_special_id.key_id));
  std::cerr << "Session key (retrieved via explicit ID "
            << key_via_special_id.key_id
            << "): " << DescribedKeyString(key_via_special_id) << std::endl;
  EXPECT_EQ(key_via_special_id, key_via_actual_id);
}

TEST(KeysTest, GetKeyringThatDoesNotExist) {
  // We don't know which keyring IDs do exist, so we just iterate until we find
  // one that doesn't exist. Surely we'll find one eventually.
  char described_key_buf[1024];
  uint32_t key_id;
  bool found_non_existent_key = false;
  for (int i = 0; i < 100; ++i) {
    key_id = absl::Uniform<uint32_t>(absl::InsecureBitGen(), 0,
                                     std::numeric_limits<uint32_t>::max());
    PosixErrorOr<int64_t> buf_bytes =
        keyctl(KEYCTL_DESCRIBE, key_id, (uint64_t)(described_key_buf), 1024, 0);
    if (!buf_bytes.ok() && errno == ENOKEY) {
      found_non_existent_key = true;
      break;
    }
  }
  EXPECT_TRUE(found_non_existent_key) << "Did not find any non-existent key ID";
}

TEST(KeysTest, DescribeKeyWithNullBuffer) {
  DescribedKey session_key =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  int64_t desc_length = ASSERT_NO_ERRNO_AND_VALUE(
      keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING, 0));
  EXPECT_EQ(desc_length, session_key.full_desc.length() + 1);
}

TEST(KeysTest, DescribeKeyWithTooSmallBuffer) {
  DescribedKey session_key =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  char described_key_buf[4];
  ASSERT_LT(4, session_key.full_desc.length());
  int64_t desc_length = ASSERT_NO_ERRNO_AND_VALUE(
      keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING,
             (uint64_t)(described_key_buf), 0, 0));
  EXPECT_EQ(desc_length, session_key.full_desc.length() + 1);
}

TEST(KeysTest, ChildThreadInheritsSessionKeyring) {
  DescribedKey parent_key =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  std::cerr << "Parent session keyring before spawning child thread: "
            << DescribedKeyString(parent_key) << std::endl;
  DescribedKey child_key;
  ScopedThread([&] {
    child_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Child session keyring: " << DescribedKeyString(child_key)
              << std::endl;
  }).Join();
  EXPECT_EQ(parent_key, child_key)
      << "Child session keyring did not match parent session keyring: child="
      << DescribedKeyString(child_key)
      << " vs parent=" << DescribedKeyString(parent_key);
}

TEST(KeysTest, ChildThreadInheritsSessionKeyringCreatedAfterChildIsBorn) {
  DescribedKey first_parent_key;
  DescribedKey second_parent_key;
  DescribedKey child_key;
  ScopedThread([&] {
    int64_t session_keyring_id =
        ASSERT_NO_ERRNO_AND_VALUE(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
    ASSERT_GT(session_keyring_id, 0)
        << "Failed to join session keyring: " << errno;
    first_parent_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Parent session keyring before spawning child: "
              << DescribedKeyString(first_parent_key) << std::endl;
    ScopedThread([&] {
      absl::Mutex mu;
      bool child_ready = false;
      bool parent_keyring_created = false;
      ScopedThread child([&] {
        absl::MutexLock child_ml(mu);
        child_ready = true;
        std::cerr << "Child is spawned and waiting for parent." << std::endl;
        mu.Await(absl::Condition(&parent_keyring_created));
        child_key =
            ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
        std::cerr << "Child session keyring: " << DescribedKeyString(child_key)
                  << std::endl;
      });
      [&] {
        absl::MutexLock parent_ml(mu);
        mu.Await(absl::Condition(&child_ready));
        int64_t session_keyring_id =
            ASSERT_NO_ERRNO_AND_VALUE(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
        ASSERT_GT(session_keyring_id, 0)
            << "Failed to join session keyring: " << errno;
        second_parent_key =
            ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
        std::cerr << "Parent session keyring after spawning child: "
                  << DescribedKeyString(second_parent_key) << std::endl;
        parent_keyring_created = true;
      }();
      child.Join();
    }).Join();
  }).Join();
  ASSERT_NE(first_parent_key, second_parent_key);
  ASSERT_EQ(first_parent_key, child_key);
}

TEST(KeysTest, JoinNewNamedSessionKeyring) {
  constexpr absl::string_view kKeyringName = "my_little_keyring";
  DescribedKey child_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
    child_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Child session keyring after joining new session keyring: "
              << DescribedKeyString(child_key) << std::endl;
  }).Join();
  EXPECT_EQ(child_key.description, kKeyringName);
}

TEST(KeysTest, ChildJoinsNewSessionKeyring) {
  DescribedKey parent_key_before =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  std::cerr << "Parent session keyring before spawning child thread: "
            << DescribedKeyString(parent_key_before) << std::endl;
  DescribedKey child_key;
  ScopedThread([&] {
    int64_t session_keyring_id =
        ASSERT_NO_ERRNO_AND_VALUE(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
    ASSERT_GT(session_keyring_id, 0)
        << "Failed to join session keyring: " << errno;
    child_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Child session keyring after joining new session keyring: "
              << DescribedKeyString(child_key) << std::endl;
    ASSERT_EQ(child_key.key_id, session_keyring_id);
  }).Join();
  DescribedKey parent_key_after =
      ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  std::cerr << "Parent session keyring after child finished: "
            << DescribedKeyString(parent_key_after) << std::endl;
  EXPECT_EQ(parent_key_before, parent_key_after)
      << "Parent session keyring changed after child did its thing: "
      << DescribedKeyString(parent_key_after) << " vs "
      << DescribedKeyString(parent_key_before);
  EXPECT_NE(parent_key_before, child_key)
      << "Child session keyring did not change after joining new session "
         "keyring: "
      << DescribedKeyString(child_key);
}

TEST(KeysTest, ExistingNamedSessionKeyringIsNew) {
  constexpr absl::string_view kKeyringName = "my_little_keyring";
  DescribedKey parent_key;
  DescribedKey first_child_key;
  DescribedKey second_child_initial_key;
  DescribedKey second_child_existing_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
    parent_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    absl::Mutex mu;
    bool first_child_created_keyring = false;
    ScopedThread first_child([&] {
      absl::MutexLock ml(mu);
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
      first_child_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "First child's session keyring: "
                << DescribedKeyString(first_child_key) << std::endl;
      first_child_created_keyring = true;
    });
    ScopedThread([&] {
      absl::MutexLock ml(mu);
      second_child_initial_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Session child's initial session keyring: "
                << DescribedKeyString(second_child_initial_key) << std::endl;
      mu.Await(absl::Condition(&first_child_created_keyring));
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
      second_child_existing_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Session child's second session keyring: "
                << DescribedKeyString(second_child_existing_key) << std::endl;
    }).Join();
    first_child.Join();
  }).Join();
  EXPECT_EQ(parent_key, second_child_initial_key);
  EXPECT_EQ(first_child_key.description, kKeyringName);
  EXPECT_NE(parent_key, first_child_key);
  EXPECT_NE(first_child_key, second_child_existing_key);
  EXPECT_EQ(first_child_key.description, second_child_existing_key.description);
  EXPECT_NE(first_child_key.key_id, second_child_existing_key.key_id);
}

TEST(KeysTest, SetAndRetrieveKeyPermissions) {
  DescribedKey before_key;
  DescribedKey after_key;
  DescribedKey deeper_key;
  uint64_t new_perms = 0;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
    before_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Child session keyring after joining new session keyring: "
              << DescribedKeyString(before_key) << std::endl;
    new_perms = (before_key.perm | KEY_USR_SEARCH) & 0xffffffff;
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, new_perms));
    after_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr
        << "Child session keyring after changing session keyring permissions: "
        << DescribedKeyString(after_key) << std::endl;
    ScopedThread([&] {
      deeper_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Second-level child session keyring: "
                << DescribedKeyString(deeper_key) << std::endl;
    }).Join();
  }).Join();
  EXPECT_NE(new_perms, 0) << "New permissions are empty";
  EXPECT_NE(before_key.perm, new_perms)
      << "Permissions were not actually requested to change, please update "
         "permissions mask";
  EXPECT_EQ(after_key.perm, new_perms)
      << "Permissions were not updated correctly";
  EXPECT_EQ(after_key, deeper_key) << "Permissions were not inherited";
}

TEST(KeysTest, JoinExistingNamedKeyringFromParent) {
  constexpr absl::string_view kKeyringName = "my_little_keyring";
  constexpr absl::string_view kOtherKeyringName = "my_other_keyring";
  DescribedKey first_level_key;
  DescribedKey second_level_key;
  DescribedKey third_level_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
    DescribedKey before_perms_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    uint64_t perms = (before_perms_key.perm | 0x80008) & 0xffffffff;
    ASSERT_NO_ERRNO(keyctl(KEYCTL_SETPERM, before_perms_key.key_id, perms));
    first_level_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "First-level child keyring: "
              << DescribedKeyString(first_level_key) << std::endl;
    ScopedThread([&] {
      ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING,
                             (uint64_t)(kOtherKeyringName.data())));
      second_level_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Second-level child keyring: "
                << DescribedKeyString(second_level_key) << std::endl;
      ScopedThread([&] {
        ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING,
                               (uint64_t)(kKeyringName.data())));
        third_level_key =
            ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
        std::cerr << "Third-level child keyring: "
                  << DescribedKeyString(third_level_key) << std::endl;
      }).Join();
    }).Join();
  }).Join();
  // The key_id is different per process, so don't compare that.
  // We use the permissions field to verify that the keyring is the same
  // between the first and the third level, to show that the same keyring
  // is being looked up.
  // However, the second level didn't look up the same keyring, so its
  // permissions should be different.
  EXPECT_EQ(first_level_key.perm, third_level_key.perm);
  EXPECT_NE(first_level_key.perm, second_level_key.perm);
}

TEST(KeysTest, DefaultKeyPermissions) {
  constexpr absl::string_view kKeyringName = "named_session_keyring";
  DescribedKey default_named_session_key;
  DescribedKey default_unnamed_session_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
    default_named_session_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  }).Join();
  ScopedThread([&] {
    ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
    default_unnamed_session_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
  }).Join();
  std::cerr << "Default named session keyring: "
            << DescribedKeyString(default_named_session_key) << std::endl;
  std::cerr << "Default unnamed session keyring: "
            << DescribedKeyString(default_unnamed_session_key) << std::endl;
  // Possessor permissions:
  uint64_t key_pos_all = KEY_POS_VIEW | KEY_POS_READ | KEY_POS_WRITE |
                         KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
  EXPECT_EQ(default_unnamed_session_key.perm & key_pos_all, key_pos_all);
  EXPECT_EQ(default_named_session_key.perm & key_pos_all, key_pos_all);

  // User permissions:
  // These differ depending on whether the keyring is named or not.
  EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_VIEW, KEY_USR_VIEW);
  EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_READ, KEY_USR_READ);
  EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_WRITE, 0);
  EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_SEARCH, 0);
  EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_LINK, 0);
  EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_SETATTR, 0);
  EXPECT_EQ(default_unnamed_session_key.perm | KEY_USR_LINK,
            default_named_session_key.perm);

  // Group permissions:
  uint64_t key_group_all = KEY_GRP_VIEW | KEY_GRP_READ | KEY_GRP_WRITE |
                           KEY_GRP_SEARCH | KEY_GRP_LINK | KEY_GRP_SETATTR;
  EXPECT_EQ(default_unnamed_session_key.perm & key_group_all, 0);
  EXPECT_EQ(default_named_session_key.perm & key_group_all, 0);

  // Other permissions:
  uint64_t key_other_all = KEY_OTH_VIEW | KEY_OTH_READ | KEY_OTH_WRITE |
                           KEY_OTH_SEARCH | KEY_OTH_LINK | KEY_OTH_SETATTR;
  EXPECT_EQ(default_unnamed_session_key.perm & key_other_all, 0);
  EXPECT_EQ(default_named_session_key.perm & key_other_all, 0);
}

TEST(KeysTest, EnforceKeyPermissions) {
  ScopedThread([&] {
    constexpr absl::string_view kKeyringName = "my_little_keyring";
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
    DescribedKey key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    ASSERT_NO_ERRNO(keyctl(KEYCTL_SETPERM, key.key_id, 0 /* No permissions */));
    EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING),
                PosixErrorIs(EACCES))
        << "Session keyring can be described";
    EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, key.key_id), PosixErrorIs(EACCES))
        << "Session keyring can be described by ID";
    ASSERT_THAT(keyctl(KEYCTL_SETPERM, key.key_id, 0), PosixErrorIs(EACCES))
        << "Session keyring perms can be changed after locking them down";
    ScopedThread([&] {
      EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING),
                  PosixErrorIs(EACCES))
          << "Session keyring can be described in child";
      EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, key.key_id), PosixErrorIs(EACCES))
          << "Session keyring can be described by ID in child";
      ASSERT_THAT(keyctl(KEYCTL_SETPERM, key.key_id, 0), PosixErrorIs(EACCES))
          << "Session keyring perms can be changed after locking them down in "
             "parent";
    }).Join();
  }).Join();
}

// JoiningNonSearchableNamedKeyring verifies what happens when joining an
// existing named keyring without the search permission.
TEST(KeysTest, JoiningNonSearchableNamedKeyring) {
  constexpr absl::string_view kKeyringName = "my_little_keyring";
  DescribedKey first_key;
  DescribedKey second_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
    first_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "First child keyring: " << DescribedKeyString(first_key)
              << std::endl;
    uint64_t non_searchable_perms =
        first_key.perm &
        ~(KEY_POS_SEARCH | KEY_USR_SEARCH | KEY_GRP_SEARCH | KEY_OTH_SEARCH);
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, non_searchable_perms));
    ScopedThread([&] {
      // The man page says this should fail with EACCES, but Linux actually
      // creates a new keyring with the same name instead.
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
      second_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Second child keyring: " << DescribedKeyString(first_key)
                << std::endl;
    }).Join();
  }).Join();
  ASSERT_NE(first_key.key_id, second_key.key_id);
}

// JoiningSearchableNamedKeyring verifies what happens when joining an
// existing named keyring with the search permission.
TEST(KeysTest, JoiningSearchableNamedKeyring) {
  constexpr absl::string_view kKeyringName = "my_little_keyring";
  DescribedKey searchable_key;
  DescribedKey second_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
    DescribedKey initial_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Initial session keyring: " << DescribedKeyString(initial_key)
              << std::endl;
    uint64_t searchable_perms = initial_key.perm | KEY_USR_SEARCH;
    ASSERT_NO_ERRNO(
        keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, searchable_perms));
    searchable_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Searchable session keyring: "
              << DescribedKeyString(searchable_key) << std::endl;
    ScopedThread([&] {
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
      second_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Second keyring: " << DescribedKeyString(second_key)
                << std::endl;
    }).Join();
  }).Join();
  EXPECT_EQ(searchable_key.key_id, second_key.key_id);
}

TEST(KeysTest, SearchableKeyringIsSharedAcrossThreads) {
  constexpr absl::string_view kKeyringName = "my_little_keyring";
  DescribedKey parent_key;
  DescribedKey first_child_final_key;
  DescribedKey second_child_final_key;
  ScopedThread([&] {
    ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING));
    parent_key =
        ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
    std::cerr << "Parent session keyring: " << DescribedKeyString(parent_key)
              << std::endl;
    absl::Mutex mu;

    // We're going to do a complicated dance.
    // Each of the following booleans is used to gate on the steps described
    // above it.

    // - Spawn two threads, have them wait on each other until they are both
    //   actually running code.
    bool first_child_ready = false;
    bool second_child_ready = false;

    // - Have the first thread create a named keyring.
    // - Have the first thread change this keyring to be searchable by user.
    bool first_child_created_keyring = false;

    // - Have the second thread join it by name.
    // - Have the second thread flip a bit in its permission field:
    //   KEY_GRP_LINK
    bool second_child_modified_keyring = false;

    // - Have the first thread re-read the permissions of its keyring.
    // - Have the first thread flip another bit in the permission field:
    //   KEY_OTH_LINK
    bool first_child_modified_keyring = false;

    // - Have the second thread re-read the permissions of its keyring.
    // - Verify that the final keys from both threads match and have both bits
    //   flipped.

    ScopedThread first_child([&] {
      absl::MutexLock ml(mu);
      first_child_ready = true;
      mu.Await(absl::Condition(&second_child_ready));
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
      DescribedKey initial_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "First child: initial session keyring: "
                << DescribedKeyString(initial_key) << std::endl;
      uint64_t searchable_perms = initial_key.perm | KEY_USR_SEARCH;
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, searchable_perms));
      DescribedKey searchable_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "First child: searchable session keyring: "
                << DescribedKeyString(searchable_key) << std::endl;
      first_child_created_keyring = true;
      mu.Await(absl::Condition(&second_child_modified_keyring));
      DescribedKey modified_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr
          << "First child: session keyring after second thread modified it: "
          << DescribedKeyString(modified_key) << std::endl;
      uint64_t new_perms = modified_key.perm | KEY_OTH_LINK;
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, new_perms));
      first_child_final_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "First child: final session keyring: "
                << DescribedKeyString(first_child_final_key) << std::endl;
      first_child_modified_keyring = true;
    });
    ScopedThread second_child([&] {
      absl::MutexLock ml(mu);
      second_child_ready = true;
      mu.Await(absl::Condition(&first_child_ready));
      mu.Await(absl::Condition(&first_child_created_keyring));
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data())));
      DescribedKey initial_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Second child: initial session keyring: "
                << DescribedKeyString(initial_key) << std::endl;
      uint64_t new_perms = initial_key.perm | KEY_GRP_LINK;
      ASSERT_NO_ERRNO(
          keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, new_perms));
      DescribedKey modified_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr
          << "Second child: session keyring after modifying its permissions: "
          << DescribedKeyString(modified_key) << std::endl;
      second_child_modified_keyring = true;
      mu.Await(absl::Condition(&first_child_modified_keyring));
      second_child_final_key =
          ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING));
      std::cerr << "Second child: final session keyring: "
                << DescribedKeyString(second_child_final_key) << std::endl;
    });
    first_child.Join();
    second_child.Join();
  }).Join();
  EXPECT_NE(parent_key, first_child_final_key);
  EXPECT_NE(parent_key, second_child_final_key);
  EXPECT_NE(parent_key.perm, first_child_final_key.perm);
  EXPECT_EQ(first_child_final_key.key_id, second_child_final_key.key_id);
  for (const uint64_t bit :
       {KEY_USR_LINK, KEY_USR_SEARCH, KEY_GRP_LINK, KEY_OTH_LINK}) {
    EXPECT_EQ(parent_key.perm & bit, 0) << "Bit " << bit << " in parent key";
    EXPECT_EQ(first_child_final_key.perm & bit, bit)
        << "Bit " << bit << " in first child key";
    EXPECT_EQ(second_child_final_key.perm & bit, bit)
        << "Bit " << bit << " in second child key";
  }
  EXPECT_EQ(first_child_final_key.perm, second_child_final_key.perm);
}

}  // namespace
}  // namespace testing
}  // namespace gvisor
