From 2aa843c8c69f625da86dd2f979aa98571a8e880d Mon Sep 17 00:00:00 2001 From: "jln@chromium.org" Date: Tue, 26 Nov 2013 19:45:15 +0000 Subject: Linux sandbox: move CurrentProcessHasOpenDirectories Move CurrentProcessHasOpenDirectories() to the Credentials class and rename it to HasOpenDirectory(). Also add some unittests. This is a re-land of https://codereview.chromium.org/85403011/. BUG=312380 R=jorgelo@chromium.org, mmoss@google.com Review URL: https://codereview.chromium.org/88243003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237390 0039d316-1c4b-4281-b951-d872f2087c98 --- sandbox/linux/services/credentials.cc | 61 ++++++++++++++++++++++++++ sandbox/linux/services/credentials.h | 19 ++++++-- sandbox/linux/services/credentials_unittest.cc | 44 +++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) (limited to 'sandbox') diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc index 0af5a42b..cea757c 100644 --- a/sandbox/linux/services/credentials.cc +++ b/sandbox/linux/services/credentials.cc @@ -4,9 +4,13 @@ #include "sandbox/linux/services/credentials.h" +#include #include +#include #include #include +#include +#include #include #include "base/basictypes.h" @@ -49,6 +53,15 @@ struct FILECloser { // TODO(jln): fix base/. typedef scoped_ptr ScopedFILE; +struct DIRCloser { + void operator()(DIR* d) const { + DCHECK(d); + PCHECK(0 == closedir(d)); + } +}; + +typedef scoped_ptr ScopedDIR; + COMPILE_ASSERT((base::is_same::value), UidAndGidAreSameType); // generic_id_t can be used for either uid_t or gid_t. typedef uid_t generic_id_t; @@ -143,6 +156,51 @@ Credentials::Credentials() { Credentials::~Credentials() { } +bool Credentials::HasOpenDirectory(int proc_fd) { + int proc_self_fd = -1; + if (proc_fd >= 0) { + proc_self_fd = openat(proc_fd, "self/fd", O_DIRECTORY | O_RDONLY); + } else { + proc_self_fd = openat(AT_FDCWD, "/proc/self/fd", O_DIRECTORY | O_RDONLY); + if (proc_self_fd < 0) { + // If not available, guess false. + // TODO(mostynb@opera.com): add a CHECK_EQ(ENOENT, errno); Figure out what + // other situations are here. http://crbug.com/314985 + return false; + } + } + CHECK_GE(proc_self_fd, 0); + + // Ownership of proc_self_fd is transferred here, it must not be closed + // or modified afterwards except via dir. + ScopedDIR dir(fdopendir(proc_self_fd)); + CHECK(dir); + + struct dirent e; + struct dirent* de; + while (!readdir_r(dir.get(), &e, &de) && de) { + if (strcmp(e.d_name, ".") == 0 || strcmp(e.d_name, "..") == 0) { + continue; + } + + int fd_num; + CHECK(base::StringToInt(e.d_name, &fd_num)); + if (fd_num == proc_fd || fd_num == proc_self_fd) { + continue; + } + + struct stat s; + // It's OK to use proc_self_fd here, fstatat won't modify it. + CHECK(fstatat(proc_self_fd, e.d_name, &s, 0) == 0); + if (S_ISDIR(s.st_mode)) { + return true; + } + } + + // No open unmanaged directories found. + return false; +} + bool Credentials::DropAllCapabilities() { ScopedCap cap(cap_init()); CHECK(cap); @@ -199,6 +257,9 @@ bool Credentials::MoveToNewUserNS() { } bool Credentials::DropFileSystemAccess() { + // Chrooting to a safe empty dir will only be safe if no directory file + // descriptor is available to the process. + DCHECK(!HasOpenDirectory(-1)); return ChrootToSafeEmptyDir(); } diff --git a/sandbox/linux/services/credentials.h b/sandbox/linux/services/credentials.h index 80b2ec1..c23db93 100644 --- a/sandbox/linux/services/credentials.h +++ b/sandbox/linux/services/credentials.h @@ -26,6 +26,20 @@ class Credentials { Credentials(); ~Credentials(); + // Checks whether the current process has any directory file descriptor open. + // Directory file descriptors are "capabilities" that would let a process use + // system calls such as openat() to bypass restrictions such as + // DropFileSystemAccess(). + // Sometimes it's useful to call HasOpenDirectory() after file system access + // has been dropped. In this case, |proc_fd| should be a file descriptor to + // /proc. The file descriptor in |proc_fd| will be ignored by + // HasOpenDirectory() and remains owned by the caller. It is very important + // for the caller to close it. + // If /proc is available, |proc_fd| can be passed as -1. + // If |proc_fd| is -1 and /proc is not available, this function will return + // false. + bool HasOpenDirectory(int proc_fd); + // Drop all capabilities in the effective, inheritable and permitted sets for // the current process. bool DropAllCapabilities(); @@ -52,9 +66,8 @@ class Credentials { // CAP_SYS_CHROOT can be acquired by using the MoveToNewUserNS() API. // Make sure to call DropAllCapabilities() after this call to prevent // escapes. - // To be secure, it's very important for this API to not be called with any - // directory file descriptor present. TODO(jln): integrate with - // crbug.com/269806 when available. + // To be secure, it's very important for this API to not be called while the + // process has any directory file descriptor open. bool DropFileSystemAccess(); private: diff --git a/sandbox/linux/services/credentials_unittest.cc b/sandbox/linux/services/credentials_unittest.cc index da61cd5..9160bf7 100644 --- a/sandbox/linux/services/credentials_unittest.cc +++ b/sandbox/linux/services/credentials_unittest.cc @@ -5,14 +5,20 @@ #include "sandbox/linux/services/credentials.h" #include +#include #include +#include +#include #include +#include "base/file_util.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" +using file_util::ScopedFD; + namespace sandbox { namespace { @@ -52,6 +58,44 @@ TEST(Credentials, CreateAndDestroy) { scoped_ptr cred2(new Credentials); } +TEST(Credentials, HasOpenDirectory) { + Credentials creds; + // No open directory should exist at startup. + EXPECT_FALSE(creds.HasOpenDirectory(-1)); + { + // Have a "/dev" file descriptor around. + int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); + ScopedFD dev_fd_closer(&dev_fd); + EXPECT_TRUE(creds.HasOpenDirectory(-1)); + } + EXPECT_FALSE(creds.HasOpenDirectory(-1)); +} + +TEST(Credentials, HasOpenDirectoryWithFD) { + Credentials creds; + + int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); + ScopedFD proc_fd_closer(&proc_fd); + ASSERT_LE(0, proc_fd); + + // Don't pass |proc_fd|, an open directory (proc_fd) should + // be detected. + EXPECT_TRUE(creds.HasOpenDirectory(-1)); + // Pass |proc_fd| and no open directory should be detected. + EXPECT_FALSE(creds.HasOpenDirectory(proc_fd)); + + { + // Have a "/dev" file descriptor around. + int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); + ScopedFD dev_fd_closer(&dev_fd); + EXPECT_TRUE(creds.HasOpenDirectory(proc_fd)); + } + + // The "/dev" file descriptor should now be closed, |proc_fd| is the only + // directory file descriptor open. + EXPECT_FALSE(creds.HasOpenDirectory(proc_fd)); +} + SANDBOX_TEST(Credentials, DropAllCaps) { Credentials creds; CHECK(creds.DropAllCapabilities()); -- cgit v1.1