From af7f464485b6831eeabd91276fa2d558c3e55878 Mon Sep 17 00:00:00 2001 From: rickyz Date: Wed, 11 Feb 2015 15:32:41 -0800 Subject: [PATCH] Deny setgroups if needed before writing to gid_map. On Linux 3.18.2 and later, in order to write to /proc/PID/gid_map, either setgroups must be disabled in the target process's user namespace, or the writer must have CAP_SETGID. This change disables setgroups by writing to /proc/PID/setgroups. Thanks neusepoff for catching this issue. BUG=457362 Review URL: https://codereview.chromium.org/909333002 Cr-Commit-Position: refs/heads/master@{#315865} --- sandbox/linux/services/credentials.cc | 8 +++++-- sandbox/linux/services/namespace_sandbox.cc | 14 ++++++++--- sandbox/linux/services/namespace_utils.cc | 24 ++++++++++++++++++- sandbox/linux/services/namespace_utils.h | 11 +++++++++ .../services/namespace_utils_unittest.cc | 7 ++++++ 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc index ce5eeda78ed177..e571ddec59776a 100644 --- a/sandbox/linux/services/credentials.cc +++ b/sandbox/linux/services/credentials.cc @@ -203,13 +203,17 @@ bool Credentials::MoveToNewUserNS() { return false; } + if (NamespaceUtils::KernelSupportsDenySetgroups()) { + PCHECK(NamespaceUtils::DenySetgroups()); + } + // The current {r,e,s}{u,g}id is now an overflow id (c.f. // /proc/sys/kernel/overflowuid). Setup the uid and gid maps. DCHECK(GetRESIds(NULL, NULL)); const char kGidMapFile[] = "/proc/self/gid_map"; const char kUidMapFile[] = "/proc/self/uid_map"; - CHECK(NamespaceUtils::WriteToIdMapFile(kGidMapFile, gid)); - CHECK(NamespaceUtils::WriteToIdMapFile(kUidMapFile, uid)); + PCHECK(NamespaceUtils::WriteToIdMapFile(kGidMapFile, gid)); + PCHECK(NamespaceUtils::WriteToIdMapFile(kUidMapFile, uid)); DCHECK(GetRESIds(NULL, NULL)); return true; } diff --git a/sandbox/linux/services/namespace_sandbox.cc b/sandbox/linux/services/namespace_sandbox.cc index d5856882e35707..0f371ebfd3198e 100644 --- a/sandbox/linux/services/namespace_sandbox.cc +++ b/sandbox/linux/services/namespace_sandbox.cc @@ -28,18 +28,26 @@ namespace { class WriteUidGidMapDelegate : public base::LaunchOptions::PreExecDelegate { public: - WriteUidGidMapDelegate() : uid_(getuid()), gid_(getgid()) {} + WriteUidGidMapDelegate() + : uid_(getuid()), + gid_(getgid()), + supports_deny_setgroups_( + NamespaceUtils::KernelSupportsDenySetgroups()) {} ~WriteUidGidMapDelegate() override {} void RunAsyncSafe() override { + if (supports_deny_setgroups_) { + RAW_CHECK(NamespaceUtils::DenySetgroups()); + } RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid_)); RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid_)); } private: - uid_t uid_; - gid_t gid_; + const uid_t uid_; + const gid_t gid_; + const bool supports_deny_setgroups_; DISALLOW_COPY_AND_ASSIGN(WriteUidGidMapDelegate); }; diff --git a/sandbox/linux/services/namespace_utils.cc b/sandbox/linux/services/namespace_utils.cc index cf4c37a58033d6..82a544453fbbdb 100644 --- a/sandbox/linux/services/namespace_utils.cc +++ b/sandbox/linux/services/namespace_utils.cc @@ -27,6 +27,8 @@ namespace { bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +const char kProcSelfSetgroups[] = "/proc/self/setgroups"; } // namespace // static @@ -43,7 +45,7 @@ bool NamespaceUtils::WriteToIdMapFile(const char* map_file, generic_id_t id) { const generic_id_t outside_id = id; char mapping[64]; - ssize_t len = + const ssize_t len = base::strings::SafeSPrintf(mapping, "%d %d 1\n", inside_id, outside_id); const ssize_t rc = HANDLE_EINTR(write(fd, mapping, len)); RAW_CHECK(IGNORE_EINTR(close(fd)) == 0); @@ -92,4 +94,24 @@ bool NamespaceUtils::KernelSupportsUnprivilegedNamespace(int type) { return base::PathExists(base::FilePath(path)); } +// static +bool NamespaceUtils::KernelSupportsDenySetgroups() { + return base::PathExists(base::FilePath(kProcSelfSetgroups)); +} + +// static +bool NamespaceUtils::DenySetgroups() { + // This function needs to be async-signal-safe. + int fd = HANDLE_EINTR(open(kProcSelfSetgroups, O_WRONLY)); + if (fd == -1) { + return false; + } + + static const char kDeny[] = "deny"; + const ssize_t len = sizeof(kDeny) - 1; + const ssize_t rc = HANDLE_EINTR(write(fd, kDeny, len)); + RAW_CHECK(IGNORE_EINTR(close(fd)) == 0); + return rc == len; +} + } // namespace sandbox diff --git a/sandbox/linux/services/namespace_utils.h b/sandbox/linux/services/namespace_utils.h index 6f3a2a86079e48..f3c88a94522622 100644 --- a/sandbox/linux/services/namespace_utils.h +++ b/sandbox/linux/services/namespace_utils.h @@ -33,6 +33,17 @@ class SANDBOX_EXPORT NamespaceUtils { // not work from within a sandbox. static bool KernelSupportsUnprivilegedNamespace(int type); + // Returns true if the kernel supports denying setgroups in a user namespace. + // On kernels where this is supported, DenySetgroups must be called before a + // gid mapping can be added. + static bool KernelSupportsDenySetgroups(); + + // Disables setgroups() within the current user namespace. On Linux 3.18.2 and + // later, this is required in order to write to /proc/self/gid_map without + // having CAP_SETGID. Callers can determine whether is this needed with + // KernelSupportsDenySetgroups. This function is async-signal-safe. + static bool DenySetgroups() WARN_UNUSED_RESULT; + private: DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceUtils); }; diff --git a/sandbox/linux/services/namespace_utils_unittest.cc b/sandbox/linux/services/namespace_utils_unittest.cc index 42b469d8bbbb74..41ed7e89a6e5bd 100644 --- a/sandbox/linux/services/namespace_utils_unittest.cc +++ b/sandbox/linux/services/namespace_utils_unittest.cc @@ -40,10 +40,17 @@ SANDBOX_TEST(NamespaceUtils, WriteToIdMapFile) { const uid_t uid = getuid(); const gid_t gid = getgid(); + const bool supports_deny_setgroups = + NamespaceUtils::KernelSupportsDenySetgroups(); + const pid_t pid = base::ForkWithFlags(CLONE_NEWUSER | SIGCHLD, nullptr, nullptr); ASSERT_NE(-1, pid); if (pid == 0) { + if (supports_deny_setgroups) { + RAW_CHECK(NamespaceUtils::DenySetgroups()); + } + RAW_CHECK(getuid() != uid); RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid)); RAW_CHECK(getuid() == uid);