mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
795 lines
33 KiB
C++
795 lines
33 KiB
C++
// 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
|