Print final FS mounts in sandboxee's chroot

After all requested filesystem mounts are fully mounted under a sandboxee's virtual chroot, print a list of the outside paths and a list of the inside chroot paths that the outside paths are mapped to. This provides a valuable insight while debugging sandboxed binaries.

PiperOrigin-RevId: 247130923
Change-Id: I42b4b3db68d826587c0fe8127aabbead38bc6f20
This commit is contained in:
Sandboxed API Team 2019-05-07 18:29:51 -07:00 committed by Copybara-Service
parent bfcd28bb91
commit f29a5a81ed
5 changed files with 139 additions and 4 deletions

View File

@ -299,6 +299,7 @@ cc_library(
"//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/sandbox2/util:file_base",
"//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/util:raw_logging",
"//sandboxed_api/util:status",
"//sandboxed_api/util:statusor",
@ -377,7 +378,6 @@ cc_library(
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_protobuf//:protobuf",
],
)
@ -390,6 +390,7 @@ cc_test(
":mounts",
":testing",
"//sandboxed_api/sandbox2/util:file_base",
"//sandboxed_api/sandbox2/util:file_helpers",
"//sandboxed_api/sandbox2/util:temp_file",
"//sandboxed_api/util:status_matchers",
"@com_google_absl//absl/strings",

View File

@ -16,9 +16,10 @@
#include "sandboxed_api/sandbox2/monitor.h"
// clang-format off
#include <linux/posix_types.h> // NOLINT: Needs to come before linux/ipc.h
#include <linux/ipc.h>
// clang-format on
#include <sched.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
@ -59,6 +60,7 @@
#include "sandboxed_api/sandbox2/stack-trace.h"
#include "sandboxed_api/sandbox2/syscall.h"
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/util/raw_logging.h"
ABSL_FLAG(bool, sandbox2_report_on_sandboxee_signal, true,
"Report sandbox2 sandboxee deaths caused by signals");
@ -108,6 +110,16 @@ Monitor::~Monitor() {
}
}
namespace {
void LogContainer(const std::vector<std::string>& container) {
for (size_t i = 0; i < container.size(); ++i) {
SAPI_RAW_LOG(INFO, "[%4d]=%s", i, container[i]);
}
}
} // namespace
void Monitor::Run() {
using DecrementCounter = decltype(setup_counter_);
std::unique_ptr<DecrementCounter, std::function<void(DecrementCounter*)>>
@ -139,6 +151,18 @@ void Monitor::Run() {
return;
}
if (SAPI_VLOG_IS_ON(1) && policy_->GetNamespace() != nullptr) {
std::vector<std::string> outside_entries;
std::vector<std::string> inside_entries;
policy_->GetNamespace()->mounts().RecursivelyListMounts(
/*outside_entries=*/&outside_entries,
/*inside_entries=*/&inside_entries);
SAPI_RAW_VLOG(1, "Outside entries mapped to chroot:");
LogContainer(outside_entries);
SAPI_RAW_VLOG(1, "Inside entries as they appear in chroot:");
LogContainer(inside_entries);
}
// Don't trace the child: it will allow to use 'strace -f' with the whole
// sandbox master/monitor, which ptrace_attach'es to the child.
int clone_flags = CLONE_UNTRACED;

View File

@ -30,8 +30,6 @@
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
@ -521,4 +519,39 @@ void Mounts::CreateMounts(const std::string& root_path) const {
sandbox2::CreateMounts(mount_tree_, root_path, true);
}
namespace {
void RecursivelyListMountsImpl(const MountTree& tree,
const std::string& tree_path,
std::vector<std::string>* outside_entries,
std::vector<std::string>* inside_entries) {
const MountTree::Node& node = tree.node();
if (node.has_dir_node()) {
const char* rw_str = node.dir_node().is_ro() ? "R " : "W ";
inside_entries->emplace_back(absl::StrCat(rw_str, tree_path, "/"));
outside_entries->emplace_back(absl::StrCat(node.dir_node().outside(), "/"));
} else if (node.has_file_node()) {
const char* rw_str = node.file_node().is_ro() ? "R " : "W ";
inside_entries->emplace_back(absl::StrCat(rw_str, tree_path));
outside_entries->emplace_back(absl::StrCat(node.file_node().outside()));
} else if (node.has_tmpfs_node()) {
outside_entries->emplace_back(
absl::StrCat("tmpfs: ", node.tmpfs_node().tmpfs_options()));
}
for (const auto& subentry : tree.entries()) {
RecursivelyListMountsImpl(subentry.second,
absl::StrCat(tree_path, "/", subentry.first),
outside_entries, inside_entries);
}
}
} // namespace
void Mounts::RecursivelyListMounts(std::vector<std::string>* outside_entries,
std::vector<std::string>* inside_entries) {
RecursivelyListMountsImpl(GetMountTree(), "", outside_entries,
inside_entries);
}
} // namespace sandbox2

View File

@ -47,6 +47,16 @@ class Mounts {
MountTree GetMountTree() const { return mount_tree_; }
// Lists the outside and inside entries of the input tree in the output
// parameters, in an ls-like manner. Each entry is traversed in the
// depth-first order. However, the entries on the same level of hierarchy are
// traversed in their natural order in the tree. The elements in the output
// containers match each other pairwise: outside_entries[i] is mounted as
// inside_entries[i]. The elements of inside_entries are prefixed with either
// 'R' (read-only) or 'W' (writable).
void RecursivelyListMounts(std::vector<std::string>* outside_entries,
std::vector<std::string>* inside_entries);
private:
friend class MountTreeTest;
::sapi::Status Insert(absl::string_view path, const MountTree::Node& node);

View File

@ -20,8 +20,10 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "sandboxed_api/sandbox2/testing.h"
#include "sandboxed_api/sandbox2/util/file_helpers.h"
#include "sandboxed_api/sandbox2/util/path.h"
#include "sandboxed_api/sandbox2/util/temp_file.h"
#include "sandboxed_api/util/status_matchers.h"
@ -29,6 +31,7 @@
using sapi::IsOk;
using sapi::StatusIs;
using ::testing::Eq;
using ::testing::UnorderedElementsAreArray;
namespace sandbox2 {
namespace {
@ -166,5 +169,69 @@ TEST(MountTreeTest, TestMinimalDynamicBinary) {
EXPECT_THAT(mounts.AddFile("/lib/x86_64-linux-gnu/libc.so.6"), IsOk());
}
TEST(MountTreeTest, TestList) {
struct TestCase {
const char *path;
const bool is_ro;
};
// clang-format off
const TestCase test_cases[] = {
// NOTE: Directories have a trailing '/'; files don't.
{"/a/b", true},
{"/a/c/", true},
{"/a/c/d/e/f/g", true},
{"/h", true},
{"/i/j/k", false},
{"/i/l/", false},
};
// clang-format on
Mounts mounts;
// Create actual directories and files on disk and selectively add
for (const auto &test_case : test_cases) {
const auto inside_path = test_case.path;
const std::string outside_path = absl::StrCat("/some/dir/", inside_path);
if (absl::EndsWith(outside_path, "/")) {
ASSERT_THAT(
mounts.AddDirectoryAt(file::CleanPath(outside_path),
file::CleanPath(inside_path), test_case.is_ro),
IsOk());
} else {
ASSERT_THAT(
mounts.AddFileAt(file::CleanPath(outside_path),
file::CleanPath(inside_path), test_case.is_ro),
IsOk());
}
}
std::vector<std::string> outside_entries;
std::vector<std::string> inside_entries;
mounts.RecursivelyListMounts(&outside_entries, &inside_entries);
// clang-format off
EXPECT_THAT(
inside_entries,
UnorderedElementsAreArray({
"R /a/b",
"R /a/c/",
"R /a/c/d/e/f/g",
"R /h",
"W /i/j/k",
"W /i/l/",
}));
EXPECT_THAT(
outside_entries,
UnorderedElementsAreArray({
absl::StrCat("/some/dir/", "a/b"),
absl::StrCat("/some/dir/", "a/c/"),
absl::StrCat("/some/dir/", "a/c/d/e/f/g"),
absl::StrCat("/some/dir/", "h"),
absl::StrCat("/some/dir/", "i/j/k"),
absl::StrCat("/some/dir/", "i/l/"),
}));
// clang-format on
}
} // namespace
} // namespace sandbox2