blob: a194c21d3d697bd07c49b81666b1c9ecea36943c [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <linux/capability.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <cstddef>
#include <string>
#include <vector>
#include <base/logging.h>
#include <base/files/file_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
namespace {
constexpr uid_t kChronosUid = 1000;
const base::FilePath kProcSelfMountinfoPath =
base::FilePath("/proc/self/mountinfo");
bool HasCap(size_t cap_index) {
return prctl(PR_CAPBSET_READ, cap_index) == 1;
}
bool VerifyNonRootIds() {
// Check that real and effective user and group IDs match 'chronos'.
uid_t effective_uid, real_uid;
gid_t effective_gid, real_gid;
// get(e){u,g}id() functions never fail.
effective_uid = geteuid();
real_uid = getuid();
effective_gid = getegid();
real_gid = getgid();
bool res = true;
if (effective_uid != kChronosUid) {
LOG(ERROR) << "Effective user ID is " << effective_uid << ", expected "
<< kChronosUid;
res = false;
}
if (real_uid != kChronosUid) {
LOG(ERROR) << "Real user ID is " << real_uid << ", expected "
<< kChronosUid;
res = false;
}
if (effective_gid != kChronosUid) {
LOG(ERROR) << "Effective group ID is " << effective_gid << ", expected "
<< kChronosUid;
res = false;
}
if (real_gid != kChronosUid) {
LOG(ERROR) << "Real group ID is " << real_gid << ", expected "
<< kChronosUid;
res = false;
}
return res;
}
bool VerifyCapNetBindServiceOnly() {
// Check that the process has dropped all capabilities except for
// CAP_NET_BIND_SERVICE.
bool res = true;
for (size_t cap_idx = 0; cap_idx <= CAP_LAST_CAP; cap_idx++) {
if (HasCap(cap_idx) && cap_idx != CAP_NET_BIND_SERVICE) {
LOG(ERROR) << "Process has capability " << cap_idx
<< " in the bounding set, expected only CAP_NET_BIND_SERVICE";
res = false;
}
}
return res;
}
std::vector<std::string> ReadMounts() {
std::string mountinfo;
if (!base::ReadFileToStringNonBlocking(kProcSelfMountinfoPath, &mountinfo)) {
return std::vector<std::string>();
}
return base::SplitString(mountinfo, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
}
bool VerifyNonInitMountNamespace() {
// It's not really possible for a process to check whether it is inside a
// non-init mount namespace, since the point of namespaces is for their
// existence to be transparent to userspace.
//
// Work around this by checking whether the root mount has parent ID 1. See
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt section 3.5
// for more details.
auto mounts = ReadMounts();
for (const auto& mount : mounts) {
// These entries are of the form:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,noatime
// (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
// (1) mount ID: unique identifier of the mount, may be reused after umount
// (2) parent ID: ID of parent (or of self for the top of the mount tree)
std::vector<std::string_view> fields = base::SplitStringPiece(
mount, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
// If the root mount has parent ID 1, the process is running in the init
// mount namespace.
if (fields[3] == "/" && fields[4] == "/") {
int parent_id = std::stoi(std::string(fields[1]));
if (parent_id == 1) {
LOG(ERROR) << "Root mount parent ID is " << parent_id << ", expected 1";
}
return parent_id != 1;
}
}
// If the root mount is not found, return false.
return false;
}
bool VerifyNonInitPidNamespace() {
// It's not really possible for a process to check whether it is inside a
// non-init PID namespace, since the point of namespaces is for their
// existence to be transparent to userspace.
//
// However, it's extremely unlikely for a userspace process to get PID 2 in
// the init PID namespace, since this is normally the PID that kthreadd gets.
//
// PID namespaces set up by Minijail will give the sandboxed process PID 2
// because PID 1 inside the namespace will be taken by the namespace init
// process provided by Minijail.
//
// It's not true that any random process inside a non-init PID namespace will
// have PID 2, since the size of the process tree inside the namespace is only
// limited by system resources. However, for the purposes of a sandboxing
// codelab, checking the Minijail case should be sufficient.
pid_t pid = getpid();
if (pid != 2) {
LOG(ERROR) << "PID is " << pid << ", expected 2";
return false;
}
return true;
}
} // namespace
int main(int argc, char** argv) {
CHECK(VerifyNonRootIds()) << "User/group IDs are not properly set up";
CHECK(VerifyCapNetBindServiceOnly())
<< "Capabilities are not properly set up";
CHECK(VerifyNonInitMountNamespace()) << "Running in the init mount namespace";
CHECK(VerifyNonInitPidNamespace()) << "Running in the init PID namespace";
LOG(INFO) << "Successfully sandboxed!";
return 0;
}