diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-01-29 16:35:13 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-02-01 15:33:35 +0000 |
commit | c8c2d1901aec01e934adf561a9fdf0cc776cdef8 (patch) | |
tree | 9157c3d9815e5870799e070b113813bec53e0535 /chromium/sandbox | |
parent | abefd5095b41dac94ca451d784ab6e27372e981a (diff) | |
download | qtwebengine-chromium-c8c2d1901aec01e934adf561a9fdf0cc776cdef8.tar.gz |
BASELINE: Update Chromium to 64.0.3282.139
Change-Id: I1cae68fe9c94ff7608b26b8382fc19862cdb293a
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/sandbox')
42 files changed, 2032 insertions, 440 deletions
diff --git a/chromium/sandbox/linux/BUILD.gn b/chromium/sandbox/linux/BUILD.gn index 4b321e2a5c9..8c9cf2c9458 100644 --- a/chromium/sandbox/linux/BUILD.gn +++ b/chromium/sandbox/linux/BUILD.gn @@ -112,6 +112,7 @@ source_set("sandbox_linux_unittests_sources") { ":sandbox", ":sandbox_linux_test_utils", "//base", + "//base/third_party/dynamic_annotations", "//testing/gtest", ] @@ -250,6 +251,7 @@ component("seccomp_bpf") { deps = [ ":sandbox_services", "//base", + "//base/third_party/dynamic_annotations", ] if (is_nacl_nonsfi) { @@ -374,6 +376,7 @@ component("sandbox_services") { ] deps = [ "//base", + "//base/third_party/dynamic_annotations", ] if (compile_credentials || is_nacl_nonsfi) { @@ -457,6 +460,7 @@ if (compile_suid_client || is_nacl_nonsfi) { deps = [ ":sandbox_services", "//base", + "//base/third_party/dynamic_annotations", ] if (is_nacl_nonsfi) { diff --git a/chromium/sandbox/linux/PRESUBMIT.py b/chromium/sandbox/linux/PRESUBMIT.py new file mode 100644 index 00000000000..01059c23fac --- /dev/null +++ b/chromium/sandbox/linux/PRESUBMIT.py @@ -0,0 +1,35 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +def PostUploadHook(cl, change, output_api): + """git cl upload will call this hook after the issue is created/modified. + + This will add extra trybot coverage for non-default Android architectures + that have a history of breaking with Seccomp changes. + """ + def affects_seccomp(f): + seccomp_paths = [ + 'bpf_dsl/', + 'seccomp-bpf/', + 'seccomp-bpf-helpers/', + 'system_headers/', + 'tests/' + ] + # If the file path contains any of the above fragments, it affects + # the Seccomp implementation. + affected_any = map(lambda sp: sp in f.LocalPath(), seccomp_paths) + return reduce(lambda a, b: a or b, affected_any) + + if not change.AffectedFiles(file_filter=affects_seccomp): + return [] + + return output_api.EnsureCQIncludeTrybotsAreAdded( + cl, + [ + 'master.tryserver.chromium.android:android_arm64_dbg_recipe', + 'master.tryserver.chromium.android:android_compile_mips_dbg', + 'master.tryserver.chromium.android:android_compile_x64_dbg', + 'master.tryserver.chromium.android:android_compile_x86_dbg', + ], + 'Automatically added Android multi-arch compile bots to run on CQ.') diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc index 76eccba1199..440ef20681f 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc @@ -158,6 +158,7 @@ ResultExpr RestrictPrctl() { .CASES((PR_GET_NAME, PR_SET_NAME, PR_GET_DUMPABLE, PR_SET_DUMPABLE #if defined(OS_ANDROID) , PR_SET_VMA, PR_SET_PTRACER, PR_SET_TIMERSLACK + , PR_GET_NO_NEW_PRIVS // Enable PR_SET_TIMERSLACK_PID, an Android custom prctl which is used in: // https://android.googlesource.com/platform/system/core/+/lollipop-release/libcutils/sched_policy.c. diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index c61e8e72888..9ff79d79658 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -15,6 +15,7 @@ #include "base/logging.h" #include "base/macros.h" #include "base/posix/eintr_wrapper.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/third_party/valgrind/valgrind.h" #include "sandbox/linux/bpf_dsl/bpf_dsl.h" #include "sandbox/linux/bpf_dsl/codegen.h" @@ -36,8 +37,6 @@ namespace sandbox { namespace { -bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } - // Check if the kernel supports seccomp-filter (a.k.a. seccomp mode 2) via // prctl(). bool KernelSupportsSeccompBPF() { @@ -125,7 +124,7 @@ SandboxBPF::~SandboxBPF() { bool SandboxBPF::SupportsSeccompSandbox(SeccompLevel level) { // Never pretend to support seccomp with Valgrind, as it // throws the tool off. - if (IsRunningOnValgrind()) { + if (RunningOnValgrind()) { return false; } diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc index 13176096cd0..5f59cbbcc65 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc @@ -9,6 +9,7 @@ #include <memory> #include "base/logging.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "sandbox/linux/bpf_dsl/policy.h" #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" @@ -45,7 +46,7 @@ void SandboxBPFTestRunner::Run() { printf("This BPF test is not fully running in this configuration!\n"); // Android and Valgrind are the only configurations where we accept not // having kernel BPF support. - if (!IsAndroid() && !IsRunningOnValgrind()) { + if (!IsAndroid() && !RunningOnValgrind()) { const bool seccomp_bpf_is_supported = false; SANDBOX_ASSERT(seccomp_bpf_is_supported); } diff --git a/chromium/sandbox/linux/services/credentials.cc b/chromium/sandbox/linux/services/credentials.cc index 0a0b6eb2b1a..d97f0946621 100644 --- a/chromium/sandbox/linux/services/credentials.cc +++ b/chromium/sandbox/linux/services/credentials.cc @@ -23,6 +23,7 @@ #include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "base/process/launch.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/third_party/valgrind/valgrind.h" #include "build/build_config.h" #include "sandbox/linux/services/namespace_utils.h" @@ -36,8 +37,6 @@ namespace sandbox { namespace { -bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } - const int kExitSuccess = 0; #if defined(__clang__) @@ -259,7 +258,7 @@ bool Credentials::HasCapability(Capability cap) { bool Credentials::CanCreateProcessInNewUserNS() { // Valgrind will let clone(2) pass-through, but doesn't support unshare(), // so always consider UserNS unsupported there. - if (IsRunningOnValgrind()) { + if (RunningOnValgrind()) { return false; } diff --git a/chromium/sandbox/linux/services/namespace_utils.cc b/chromium/sandbox/linux/services/namespace_utils.cc index 97add26f8ff..83749adf037 100644 --- a/chromium/sandbox/linux/services/namespace_utils.cc +++ b/chromium/sandbox/linux/services/namespace_utils.cc @@ -20,15 +20,12 @@ #include "base/posix/eintr_wrapper.h" #include "base/process/launch.h" #include "base/strings/safe_sprintf.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/third_party/valgrind/valgrind.h" namespace sandbox { namespace { -bool IsRunningOnValgrind() { - return RUNNING_ON_VALGRIND; -} - const char kProcSelfSetgroups[] = "/proc/self/setgroups"; } // namespace @@ -57,7 +54,7 @@ bool NamespaceUtils::WriteToIdMapFile(const char* map_file, generic_id_t id) { bool NamespaceUtils::KernelSupportsUnprivilegedNamespace(int type) { // Valgrind will let clone(2) pass-through, but doesn't support unshare(), // so always consider namespaces unsupported there. - if (IsRunningOnValgrind()) { + if (RunningOnValgrind()) { return false; } diff --git a/chromium/sandbox/linux/services/thread_helpers_unittests.cc b/chromium/sandbox/linux/services/thread_helpers_unittests.cc index fe1080bf9e1..78e6d837e20 100644 --- a/chromium/sandbox/linux/services/thread_helpers_unittests.cc +++ b/chromium/sandbox/linux/services/thread_helpers_unittests.cc @@ -14,6 +14,7 @@ #include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "base/process/process_metrics.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" #include "build/build_config.h" @@ -30,7 +31,7 @@ namespace { #if !defined(THREAD_SANITIZER) int GetRaceTestIterations() { - if (IsRunningOnValgrind()) { + if (RunningOnValgrind()) { return 2; } else { return 1000; diff --git a/chromium/sandbox/linux/syscall_broker/DEPS b/chromium/sandbox/linux/syscall_broker/DEPS index 70d9b18aa10..c477f7d3639 100644 --- a/chromium/sandbox/linux/syscall_broker/DEPS +++ b/chromium/sandbox/linux/syscall_broker/DEPS @@ -1,3 +1,4 @@ include_rules = [ "+sandbox/linux/system_headers", + "+sandbox/linux/bpf_dsl", ] diff --git a/chromium/sandbox/linux/syscall_broker/broker_client.cc b/chromium/sandbox/linux/syscall_broker/broker_client.cc index c16d39090d1..277e61a2c4f 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_client.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_client.cc @@ -9,8 +9,7 @@ #include <stddef.h> #include <stdint.h> #include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> + #include <utility> #include "base/logging.h" @@ -141,6 +140,94 @@ int BrokerClient::Open(const char* pathname, int flags) const { return PathAndFlagsSyscall(COMMAND_OPEN, pathname, flags); } -} // namespace syscall_broker +int BrokerClient::Stat(const char* pathname, struct stat* sb) { + return StatFamilySyscall(COMMAND_STAT, pathname, sb, sizeof(*sb)); +} + +int BrokerClient::Stat64(const char* pathname, struct stat64* sb) { + return StatFamilySyscall(COMMAND_STAT64, pathname, sb, sizeof(*sb)); +} + +int BrokerClient::StatFamilySyscall(IPCCommand syscall_type, + const char* pathname, + void* result_ptr, + size_t expected_result_size) const { + if (fast_check_in_client_ && + !broker_policy_.GetFileNameIfAllowedToAccess(pathname, R_OK, nullptr)) { + return -broker_policy_.denied_errno(); + } + + base::Pickle write_pickle; + write_pickle.WriteInt(syscall_type); + write_pickle.WriteString(pathname); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = base::UnixDomainSocket::SendRecvMsg( + ipc_channel_.get(), reply_buf, sizeof(reply_buf), &returned_fd, + write_pickle); + + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + int return_length = 0; + const char* return_data = nullptr; + if (!iter.ReadInt(&return_value)) + return -ENOMEM; + if (return_value < 0) + return return_value; + if (!iter.ReadData(&return_data, &return_length)) + return -ENOMEM; + if (static_cast<size_t>(return_length) != expected_result_size) + return -ENOMEM; + memcpy(result_ptr, return_data, expected_result_size); + return return_value; +} + +int BrokerClient::Rename(const char* oldpath, const char* newpath) { + if (fast_check_in_client_) { + bool ignore; + if (!broker_policy_.GetFileNameIfAllowedToOpen(oldpath, O_RDWR, nullptr, + &ignore) || + !broker_policy_.GetFileNameIfAllowedToOpen(newpath, O_RDWR, nullptr, + &ignore)) { + return -broker_policy_.denied_errno(); + } + } + base::Pickle write_pickle; + write_pickle.WriteInt(COMMAND_RENAME); + write_pickle.WriteString(oldpath); + write_pickle.WriteString(newpath); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = base::UnixDomainSocket::SendRecvMsg( + ipc_channel_.get(), reply_buf, sizeof(reply_buf), &returned_fd, + write_pickle); + + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + if (!iter.ReadInt(&return_value)) + return -ENOMEM; + + return return_value; +} + +} // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_client.h b/chromium/sandbox/linux/syscall_broker/broker_client.h index 2dfef8150ca..9c818578235 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_client.h +++ b/chromium/sandbox/linux/syscall_broker/broker_client.h @@ -5,6 +5,10 @@ #ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ #define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + #include "base/macros.h" #include "sandbox/linux/syscall_broker/broker_channel.h" #include "sandbox/linux/syscall_broker/broker_common.h" @@ -42,6 +46,7 @@ class BrokerClient { // It's similar to the access() system call and will return -errno on errors. // This is async signal safe. int Access(const char* pathname, int mode) const; + // Can be used in place of open(). // The implementation only supports certain white listed flags and will // return -EPERM on other flags. @@ -49,10 +54,30 @@ class BrokerClient { // This is async signal safe. int Open(const char* pathname, int flags) const; + // Can be used in place of stat()/stat64(). + // It's similar to the stat() system call and will return -errno on errors. + // This is async signal safe. + int Stat(const char* pathname, struct stat* sb); + int Stat64(const char* pathname, struct stat64* sb); + + // Can be used in place of rename(). + // It's similar to the rename() system call and will return -errno on errors. + // This is async signal safe. + int Rename(const char* oldpath, const char* newpath); + // Get the file descriptor used for IPC. This is used for tests. int GetIPCDescriptor() const { return ipc_channel_.get(); } private: + int PathAndFlagsSyscall(IPCCommand syscall_type, + const char* pathname, + int flags) const; + + int StatFamilySyscall(IPCCommand syscall_type, + const char* pathname, + void* result_ptr, + size_t expected_result_size) const; + const BrokerPolicy& broker_policy_; const BrokerChannel::EndPoint ipc_channel_; const bool fast_check_in_client_; // Whether to forward a request that we @@ -61,10 +86,6 @@ class BrokerClient { const bool quiet_failures_for_tests_; // Disable certain error message when // testing for failures. - int PathAndFlagsSyscall(IPCCommand syscall_type, - const char* pathname, - int flags) const; - DISALLOW_COPY_AND_ASSIGN(BrokerClient); }; diff --git a/chromium/sandbox/linux/syscall_broker/broker_common.h b/chromium/sandbox/linux/syscall_broker/broker_common.h index 25aafa7ed2a..30b2ce19762 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_common.h +++ b/chromium/sandbox/linux/syscall_broker/broker_common.h @@ -32,6 +32,9 @@ enum IPCCommand { COMMAND_INVALID = 0, COMMAND_OPEN, COMMAND_ACCESS, + COMMAND_STAT, + COMMAND_STAT64, + COMMAND_RENAME, }; } // namespace syscall_broker diff --git a/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc b/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc index 39073444467..3947145eaf6 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc @@ -14,7 +14,6 @@ #include "sandbox/linux/syscall_broker/broker_common.h" namespace sandbox { - namespace syscall_broker { // Async signal safe @@ -52,25 +51,21 @@ bool BrokerFilePermission::ValidatePath(const char* path) { // methods are async signal safe in common standard libs. // TODO(leecam): remove dependency on std::string bool BrokerFilePermission::MatchPath(const char* requested_filename) const { - const char* path = path_.c_str(); - if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) { - // Note: This prefix match will allow any path under the whitelisted - // path, for any number of directory levels. E.g. if the whitelisted - // path is /good/ then the following will be permitted by the policy. - // /good/file1 - // /good/folder/file2 - // /good/folder/folder2/file3 - // If an attacker could make 'folder' a symlink to ../../ they would have - // access to the entire filesystem. - // Whitelisting with multiple depths is useful, e.g /proc/ but - // the system needs to ensure symlinks can not be created! - // That said if an attacker can convert any of the absolute paths - // to a symlink they can control any file on the system also. - return true; - } else if (strcmp(requested_filename, path) == 0) { - return true; - } - return false; + // Note: This recursive match will allow any path under the whitelisted + // path, for any number of directory levels. E.g. if the whitelisted + // path is /good/ then the following will be permitted by the policy. + // /good/file1 + // /good/folder/file2 + // /good/folder/folder2/file3 + // If an attacker could make 'folder' a symlink to ../../ they would have + // access to the entire filesystem. + // Whitelisting with multiple depths is useful, e.g /proc/ but + // the system needs to ensure symlinks can not be created! + // That said if an attacker can convert any of the absolute paths + // to a symlink they can control any file on the system also. + return recursive_ + ? strncmp(requested_filename, path_.c_str(), path_.length()) == 0 + : strcmp(requested_filename, path_.c_str()) == 0; } // Async signal safe. @@ -82,45 +77,39 @@ bool BrokerFilePermission::CheckAccess(const char* requested_filename, const char** file_to_access) const { // First, check if |mode| is existence, ability to read or ability // to write. We do not support X_OK. - if (mode != F_OK && mode & ~(R_OK | W_OK)) { + if (mode != F_OK && mode & ~(R_OK | W_OK)) return false; - } if (!ValidatePath(requested_filename)) return false; - if (!MatchPath(requested_filename)) { + if (!MatchPath(requested_filename)) return false; - } + bool allowed = false; switch (mode) { case F_OK: - if (allow_read_ || allow_write_) - allowed = true; + allowed = allow_read_ || allow_write_; break; case R_OK: - if (allow_read_) - allowed = true; + allowed = allow_read_; break; case W_OK: - if (allow_write_) - allowed = true; + allowed = allow_write_; break; case R_OK | W_OK: - if (allow_read_ && allow_write_) - allowed = true; + allowed = allow_read_ && allow_write_; break; default: - return false; + break; } + if (!allowed) + return false; - if (allowed && file_to_access) { - if (!recursive_) - *file_to_access = path_.c_str(); - else - *file_to_access = requested_filename; - } - return allowed; + if (file_to_access) + *file_to_access = recursive_ ? requested_filename : path_.c_str(); + + return true; } // Async signal safe. @@ -134,9 +123,8 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, if (!ValidatePath(requested_filename)) return false; - if (!MatchPath(requested_filename)) { + if (!MatchPath(requested_filename)) return false; - } // First, check the access mode is valid. const int access_mode = flags & O_ACCMODE; @@ -165,8 +153,8 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, return false; } - // If this file is to be unlinked, ensure it's created. - if (unlink_ && !(flags & O_CREAT)) { + // If this file is to be temporary, ensure it's created. + if (temporary_only_ && !(flags & O_CREAT)) { return false; } @@ -178,7 +166,6 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, // Now check that all the flags are known to us. const int creation_and_status_flags = flags & ~O_ACCMODE; - const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT | O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | @@ -190,55 +177,49 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, if (has_unknown_flags) return false; - if (file_to_open) { - if (!recursive_) - *file_to_open = path_.c_str(); - else - *file_to_open = requested_filename; - } + if (file_to_open) + *file_to_open = recursive_ ? requested_filename : path_.c_str(); + if (unlink_after_open) - *unlink_after_open = unlink_; + *unlink_after_open = temporary_only_; return true; } const char* BrokerFilePermission::GetErrorMessageForTests() { - static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission"; - return kInvalidBrokerFileString; + return "Invalid BrokerFilePermission"; } BrokerFilePermission::BrokerFilePermission(const std::string& path, bool recursive, - bool unlink, + bool temporary_only, bool allow_read, bool allow_write, bool allow_create) : path_(path), recursive_(recursive), - unlink_(unlink), + temporary_only_(temporary_only), allow_read_(allow_read), allow_write_(allow_write), allow_create_(allow_create) { - // Validate this permission and die if invalid! - // Must have enough length for a '/' CHECK(path_.length() > 0) << GetErrorMessageForTests(); + // Whitelisted paths must be absolute. CHECK(path_[0] == '/') << GetErrorMessageForTests(); - // Don't allow unlinking on creation without create permission - if (unlink_) { + // Don't allow temporary creation without create permission + if (temporary_only_) CHECK(allow_create) << GetErrorMessageForTests(); - } + + // Recursive paths must have a trailing slash, absolutes must not. const char last_char = *(path_.rbegin()); - // Recursive paths must have a trailing slash - if (recursive_) { + if (recursive_) CHECK(last_char == '/') << GetErrorMessageForTests(); - } else { + else CHECK(last_char != '/') << GetErrorMessageForTests(); - } } } // namespace syscall_broker -} // namespace sandbox
\ No newline at end of file +} // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_file_permission.h b/chromium/sandbox/linux/syscall_broker/broker_file_permission.h index ddc62d56291..b39ac6ca1b9 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_file_permission.h +++ b/chromium/sandbox/linux/syscall_broker/broker_file_permission.h @@ -11,7 +11,6 @@ #include "sandbox/sandbox_export.h" namespace sandbox { - namespace syscall_broker { // BrokerFilePermission defines a path for whitelisting. @@ -45,11 +44,12 @@ class SANDBOX_EXPORT BrokerFilePermission { return BrokerFilePermission(path, false, false, true, true, true); } - static BrokerFilePermission ReadWriteCreateUnlink(const std::string& path) { - return BrokerFilePermission(path, false, true, true, true, true); + static BrokerFilePermission ReadWriteCreateRecursive( + const std::string& path) { + return BrokerFilePermission(path, true, false, true, true, true); } - static BrokerFilePermission ReadWriteCreateUnlinkRecursive( + static BrokerFilePermission ReadWriteCreateTemporaryRecursive( const std::string& path) { return BrokerFilePermission(path, true, true, true, true, true); } @@ -60,16 +60,17 @@ class SANDBOX_EXPORT BrokerFilePermission { // the |requested_filename| in the case of a recursive match, // or a pointer the matched path in the whitelist if an absolute // match. - // If not NULL |unlink_after_open| is set to point to true if the - // caller should unlink the path after opening. + // If not NULL, |unlink_after_open| is set to point to true if the + // caller is required to unlink the path after opening. // Async signal safe if |file_to_open| is NULL. bool CheckOpen(const char* requested_filename, int flags, const char** file_to_open, bool* unlink_after_open) const; + // Returns true if |requested_filename| is allowed to be accessed // by this permission as per access(2). - // If |file_to_open| is not NULL it is set to point to either + // If |file_to_open| is not NULL, it is set to point to either // the |requested_filename| in the case of a recursive match, // or a pointer to the matched path in the whitelist if an absolute // match. @@ -81,9 +82,11 @@ class SANDBOX_EXPORT BrokerFilePermission { private: friend class BrokerFilePermissionTester; + + // NOTE: Validates the permission and dies if invalid! BrokerFilePermission(const std::string& path, bool recursive, - bool unlink, + bool temporary_only, bool allow_read, bool allow_write, bool allow_create); @@ -102,18 +105,17 @@ class SANDBOX_EXPORT BrokerFilePermission { static const char* GetErrorMessageForTests(); // These are not const as std::vector requires copy-assignment and this class - // is stored in vectors. All methods are marked const so - // the compiler will still enforce no changes outside of the constructor. + // is stored in vectors. All methods are marked const so the compiler will + // still enforce no changes outside of the constructor. std::string path_; - bool recursive_; // Allow everything under this path. |path| must be a dir. - bool unlink_; // unlink after opening. + bool recursive_; // Allow everything under |path| (must be a dir). + bool temporary_only_; // File must be unlink'd after opening. bool allow_read_; bool allow_write_; bool allow_create_; }; } // namespace syscall_broker - } // namespace sandbox -#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_
\ No newline at end of file +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ diff --git a/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc b/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc index 83840779f96..dc56b4cd417 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc @@ -231,20 +231,11 @@ void CheckUnlink(BrokerFilePermission& perm, ASSERT_TRUE(unlink); } -TEST(BrokerFilePermission, ReadWriteCreateUnlink) { - const char kPath[] = "/tmp/good"; - BrokerFilePermission perm = - BrokerFilePermission::ReadWriteCreateUnlink(kPath); - CheckUnlink(perm, kPath, O_RDWR); - // Don't do anything here, so that ASSERT works in the subfunction as - // expected. -} - -TEST(BrokerFilePermission, ReadWriteCreateUnlinkRecursive) { +TEST(BrokerFilePermission, ReadWriteCreateTemporaryRecursive) { const char kPath[] = "/tmp/good/"; const char kPathFile[] = "/tmp/good/file"; BrokerFilePermission perm = - BrokerFilePermission::ReadWriteCreateUnlinkRecursive(kPath); + BrokerFilePermission::ReadWriteCreateTemporaryRecursive(kPath); CheckUnlink(perm, kPathFile, O_RDWR); // Don't do anything here, so that ASSERT works in the subfunction as // expected. diff --git a/chromium/sandbox/linux/syscall_broker/broker_host.cc b/chromium/sandbox/linux/syscall_broker/broker_host.cc index 6e1daccdb95..375747e19f2 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_host.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_host.cc @@ -22,6 +22,7 @@ #include "base/pickle.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/third_party/valgrind/valgrind.h" #include "sandbox/linux/syscall_broker/broker_common.h" #include "sandbox/linux/syscall_broker/broker_policy.h" @@ -33,27 +34,17 @@ namespace syscall_broker { namespace { -bool IsRunningOnValgrind() { - return RUNNING_ON_VALGRIND; -} - // A little open(2) wrapper to handle some oddities for us. In the general case // make a direct system call since we want to keep in control of the broker // process' system calls profile to be able to loosely sandbox it. int sys_open(const char* pathname, int flags) { // Hardcode mode to rw------- when creating files. - int mode; - if (flags & O_CREAT) { - mode = 0600; - } else { - mode = 0; - } - if (IsRunningOnValgrind()) { + int mode = (flags & O_CREAT) ? 0600 : 0; + if (RunningOnValgrind()) { // Valgrind does not support AT_FDCWD, just use libc's open() in this case. return open(pathname, flags, mode); - } else { - return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); } + return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); } // Open |requested_filename| with |flags| if allowed by our policy. @@ -113,50 +104,111 @@ void AccessFileForIPC(const BrokerPolicy& policy, } } -// Handle a |command_type| request contained in |iter| and send the reply -// on |reply_ipc|. -// Currently COMMAND_OPEN and COMMAND_ACCESS are supported. +// Perform stat(2) on |requested_filename| and marshal the result to +// |write_pickle|. +void StatFileForIPC(const BrokerPolicy& policy, + IPCCommand command_type, + const std::string& requested_filename, + base::Pickle* write_pickle) { + DCHECK(write_pickle); + DCHECK(command_type == COMMAND_STAT || command_type == COMMAND_STAT64); + const char* file_to_access = nullptr; + if (!policy.GetFileNameIfAllowedToAccess(requested_filename.c_str(), F_OK, + &file_to_access)) { + write_pickle->WriteInt(-policy.denied_errno()); + return; + } + if (command_type == COMMAND_STAT) { + struct stat sb; + if (stat(file_to_access, &sb) < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(0); + write_pickle->WriteData(reinterpret_cast<char*>(&sb), sizeof(sb)); + } else { + struct stat64 sb; + if (stat64(file_to_access, &sb) < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(0); + write_pickle->WriteData(reinterpret_cast<char*>(&sb), sizeof(sb)); + } +} + +// Perform rename(2) on |old_filename| to |new_filename| and marshal the +// result to |write_pickle|. +void RenameFileForIPC(const BrokerPolicy& policy, + const std::string& old_filename, + const std::string& new_filename, + base::Pickle* write_pickle) { + DCHECK(write_pickle); + bool ignore; + const char* old_file_to_access = nullptr; + const char* new_file_to_access = nullptr; + if (!policy.GetFileNameIfAllowedToOpen(old_filename.c_str(), O_RDWR, + &old_file_to_access, &ignore) || + !policy.GetFileNameIfAllowedToOpen(new_filename.c_str(), O_RDWR, + &new_file_to_access, &ignore)) { + write_pickle->WriteInt(-policy.denied_errno()); + return; + } + if (rename(old_file_to_access, new_file_to_access) < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(0); +} + +// Handle a |command_type| request contained in |iter| and write the reply +// to |write_pickle|, adding any files opened to |opened_files|. bool HandleRemoteCommand(const BrokerPolicy& policy, - IPCCommand command_type, - int reply_ipc, - base::PickleIterator iter) { - // Currently all commands have two arguments: filename and flags. - std::string requested_filename; - int flags = 0; - if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) + base::PickleIterator iter, + base::Pickle* write_pickle, + std::vector<int>* opened_files) { + int command_type; + if (!iter.ReadInt(&command_type)) return false; - base::Pickle write_pickle; - std::vector<int> opened_files; - switch (command_type) { - case COMMAND_ACCESS: - AccessFileForIPC(policy, requested_filename, flags, &write_pickle); + case COMMAND_ACCESS: { + std::string requested_filename; + int flags = 0; + if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) + return false; + AccessFileForIPC(policy, requested_filename, flags, write_pickle); + break; + } + case COMMAND_OPEN: { + std::string requested_filename; + int flags = 0; + if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) + return false; + OpenFileForIPC(policy, requested_filename, flags, write_pickle, + opened_files); break; - case COMMAND_OPEN: - OpenFileForIPC( - policy, requested_filename, flags, &write_pickle, &opened_files); + } + case COMMAND_STAT: + case COMMAND_STAT64: { + std::string requested_filename; + if (!iter.ReadString(&requested_filename)) + return false; + StatFileForIPC(policy, static_cast<IPCCommand>(command_type), + requested_filename, write_pickle); + break; + } + case COMMAND_RENAME: { + std::string old_filename; + std::string new_filename; + if (!iter.ReadString(&old_filename) || !iter.ReadString(&new_filename)) + return false; + RenameFileForIPC(policy, old_filename, new_filename, write_pickle); break; + } default: LOG(ERROR) << "Invalid IPC command"; - break; - } - - CHECK_LE(write_pickle.size(), kMaxMessageLength); - ssize_t sent = base::UnixDomainSocket::SendMsg( - reply_ipc, write_pickle.data(), write_pickle.size(), opened_files); - - // Close anything we have opened in this process. - for (std::vector<int>::iterator it = opened_files.begin(); - it != opened_files.end(); - ++it) { - int ret = IGNORE_EINTR(close(*it)); - DCHECK(!ret) << "Could not close file descriptor"; - } - - if (sent <= 0) { - LOG(ERROR) << "Could not send IPC reply"; - return false; + return false; } return true; } @@ -197,34 +249,29 @@ BrokerHost::RequestStatus BrokerHost::HandleRequest() const { base::Pickle pickle(buf, msg_len); base::PickleIterator iter(pickle); - int command_type; - if (iter.ReadInt(&command_type)) { - bool command_handled = false; - // Go through all the possible IPC messages. - switch (command_type) { - case COMMAND_ACCESS: - case COMMAND_OPEN: - // We reply on the file descriptor sent to us via the IPC channel. - command_handled = HandleRemoteCommand( - broker_policy_, static_cast<IPCCommand>(command_type), - temporary_ipc.get(), iter); - break; - default: - NOTREACHED(); - break; - } - - if (command_handled) { - return RequestStatus::SUCCESS; - } else { - return RequestStatus::FAILURE; + base::Pickle write_pickle; + std::vector<int> opened_files; + bool result = + HandleRemoteCommand(broker_policy_, iter, &write_pickle, &opened_files); + + if (result) { + CHECK_LE(write_pickle.size(), kMaxMessageLength); + ssize_t sent = base::UnixDomainSocket::SendMsg( + temporary_ipc.get(), write_pickle.data(), write_pickle.size(), + opened_files); + if (sent <= 0) { + LOG(ERROR) << "Could not send IPC reply"; + result = false; } + } - NOTREACHED(); + // Close anything we have opened in this process. + for (int fd : opened_files) { + int ret = IGNORE_EINTR(close(fd)); + DCHECK(!ret) << "Could not close file descriptor"; } - LOG(ERROR) << "Error parsing IPC request"; - return RequestStatus::FAILURE; + return result ? RequestStatus::SUCCESS : RequestStatus::FAILURE; } } // namespace syscall_broker diff --git a/chromium/sandbox/linux/syscall_broker/broker_process.cc b/chromium/sandbox/linux/syscall_broker/broker_process.cc index 30713cedcc2..46e207ede34 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_process.cc @@ -39,8 +39,7 @@ BrokerProcess::BrokerProcess( fast_check_in_client_(fast_check_in_client), quiet_failures_for_tests_(quiet_failures_for_tests), broker_pid_(-1), - policy_(denied_errno, permissions) { -} + broker_policy_(denied_errno, permissions) {} BrokerProcess::~BrokerProcess() { if (initialized_) { @@ -68,35 +67,35 @@ bool BrokerProcess::Init( DCHECK_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); #endif int child_pid = fork(); - if (child_pid == -1) { + if (child_pid == -1) return false; - } + if (child_pid) { // We are the parent and we have just forked our broker process. ipc_reader.reset(); broker_pid_ = child_pid; - broker_client_.reset(new BrokerClient(policy_, std::move(ipc_writer), + broker_client_.reset(new BrokerClient(broker_policy_, std::move(ipc_writer), fast_check_in_client_, quiet_failures_for_tests_)); initialized_ = true; return true; - } else { - // We are the broker process. Make sure to close the writer's end so that - // we get notified if the client disappears. - ipc_writer.reset(); - CHECK(broker_process_init_callback.Run()); - BrokerHost broker_host(policy_, std::move(ipc_reader)); - for (;;) { - switch (broker_host.HandleRequest()) { - case BrokerHost::RequestStatus::LOST_CLIENT: - _exit(1); - case BrokerHost::RequestStatus::SUCCESS: - case BrokerHost::RequestStatus::FAILURE: - continue; - } + } + + // We are the broker process. Make sure to close the writer's end so that + // we get notified if the client disappears. + ipc_writer.reset(); + CHECK(broker_process_init_callback.Run()); + BrokerHost broker_host(broker_policy_, std::move(ipc_reader)); + for (;;) { + switch (broker_host.HandleRequest()) { + case BrokerHost::RequestStatus::LOST_CLIENT: + _exit(1); + case BrokerHost::RequestStatus::SUCCESS: + case BrokerHost::RequestStatus::FAILURE: + continue; } - _exit(1); } + _exit(1); NOTREACHED(); return false; } @@ -115,6 +114,121 @@ int BrokerProcess::Open(const char* pathname, int flags) const { return broker_client_->Open(pathname, flags); } -} // namespace syscall_broker +int BrokerProcess::Stat(const char* pathname, struct stat* sb) const { + RAW_CHECK(initialized_); + return broker_client_->Stat(pathname, sb); +} + +int BrokerProcess::Stat64(const char* pathname, struct stat64* sb) const { + RAW_CHECK(initialized_); + return broker_client_->Stat64(pathname, sb); +} + +int BrokerProcess::Rename(const char* oldpath, const char* newpath) const { + RAW_CHECK(initialized_); + return broker_client_->Rename(oldpath, newpath); +} + +#if defined(MEMORY_SANITIZER) +#define BROKER_UNPOISON_STRING(x) __msan_unpoison_string(x) +#else +#define BROKER_UNPOISON_STRING(x) +#endif -} // namespace sandbox. +// static +intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args, + void* aux_broker_process) { + RAW_CHECK(aux_broker_process); + auto* broker_process = static_cast<BrokerProcess*>(aux_broker_process); + switch (args.nr) { +#if defined(__NR_access) + case __NR_access: + return broker_process->Access(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_open) + case __NR_open: + // http://crbug.com/372840 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); + return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_stat) + case __NR_stat: + return broker_process->Stat(reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<struct stat*>(args.args[1])); +#endif +#if defined(__NR_stat64) + case __NR_stat64: + return broker_process->Stat64( + reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<struct stat64*>(args.args[1])); +#endif +#if defined(__NR_faccessat) + case __NR_faccessat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return broker_process->Access(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#endif +#if defined(__NR_openat) + case __NR_openat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return broker_process->Open(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#endif +#if defined(__NR_fstatat) + case __NR_fstatat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + if (static_cast<int>(args.args[3]) != 0) + return -EINVAL; + return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<struct stat*>(args.args[2])); +#endif +#if defined(__NR_newfstatat) + case __NR_newfstatat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + if (static_cast<int>(args.args[3]) != 0) + return -EINVAL; + return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<struct stat*>(args.args[2])); +#endif +#if defined(__NR_rename) + case __NR_rename: + return broker_process->Rename( + reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<const char*>(args.args[1])); +#endif +#if defined(__NR_renameat) + case __NR_renameat: + if (static_cast<int>(args.args[0]) != AT_FDCWD || + static_cast<int>(args.args[2]) != AT_FDCWD) { + return -EPERM; + } + return broker_process->Rename( + reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<const char*>(args.args[3])); +#endif +#if defined(__NR_renameat2) + case __NR_renameat2: + if (static_cast<int>(args.args[0]) != AT_FDCWD || + static_cast<int>(args.args[2]) != AT_FDCWD) { + return -EPERM; + } + if (static_cast<int>(args.args[4]) != 0) + return -EINVAL; + return broker_process->Rename( + reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<const char*>(args.args[3])); +#endif + default: + RAW_CHECK(false); + return -ENOSYS; + } +} + +} // namespace syscall_broker +} // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_process.h b/chromium/sandbox/linux/syscall_broker/broker_process.h index 3c0c8090cf6..efa84bb8342 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process.h +++ b/chromium/sandbox/linux/syscall_broker/broker_process.h @@ -13,6 +13,7 @@ #include "base/macros.h" #include "base/pickle.h" #include "base/process/process.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" #include "sandbox/linux/syscall_broker/broker_policy.h" #include "sandbox/sandbox_export.h" @@ -36,14 +37,17 @@ class BrokerFilePermission; class SANDBOX_EXPORT BrokerProcess { public: // |denied_errno| is the error code returned when methods such as Open() - // or Access() are invoked on a file which is not in the whitelist. EACCESS - // would be a typical value. - // |allowed_r_files| and |allowed_w_files| are white lists of files that can - // be opened later via the Open() API, respectively for reading and writing. - // A file available read-write should be listed in both. - // |fast_check_in_client| and |quiet_failures_for_tests| are reserved for - // unit tests, don't use it. - + // or Access() are invoked on a file which is not in the whitelist (EACCESS + // would be a typical value). |permissions| describes the whitelisted set + // of files the broker is is allowed to access. |fast_check_in_client| + // controls whether doomed requests are first filtered on the client side + // before being proxied. Apart from tests, this should always be true since + // our main clients are not always well-behaved. They may have third party + // libraries that don't know about sandboxing, and typically try to open all + // sorts of stuff they don't really need. It's important to reduce this load + // given that there is only one pipeline to the broker process, and it is + // not multi-threaded. |quiet_failures_for_tests| is reserved for unit tests, + // don't use it. BrokerProcess( int denied_errno, const std::vector<syscall_broker::BrokerFilePermission>& permissions, @@ -51,6 +55,7 @@ class SANDBOX_EXPORT BrokerProcess { bool quiet_failures_for_tests = false); ~BrokerProcess(); + // Will initialize the broker process. There should be no threads at this // point, since we need to fork(). // broker_process_init_callback will be called in the new broker process, @@ -62,14 +67,29 @@ class SANDBOX_EXPORT BrokerProcess { // doesn't support execute permissions. // It's similar to the access() system call and will return -errno on errors. int Access(const char* pathname, int mode) const; + // Can be used in place of open(). Will be async signal safe. // The implementation only supports certain white listed flags and will // return -EPERM on other flags. // It's similar to the open() system call and will return -errno on errors. int Open(const char* pathname, int flags) const; + // Can be used in place of stat()/stat64(). Will be async signal safe. + // It's similar to the stat() system call and will return -errno on errors. + int Stat(const char* pathname, struct stat* sb) const; + int Stat64(const char* pathname, struct stat64* sb) const; + + // Can be used in place of rename(). Will be async signal safe. + // It's similar to the rename() system call and will return -errno on errors. + int Rename(const char* oldpath, const char* newpath) const; + int broker_pid() const { return broker_pid_; } + // Handler to be used with a bpf_dsl Trap() function to forward system calls + // to the methods above. + static intptr_t SIGSYS_Handler(const arch_seccomp_data& args, + void* aux_broker_process); + private: friend class BrokerProcessTestHelper; @@ -80,8 +100,8 @@ class SANDBOX_EXPORT BrokerProcess { bool initialized_; // Whether we've been through Init() yet. const bool fast_check_in_client_; const bool quiet_failures_for_tests_; - pid_t broker_pid_; // The PID of the broker (child). - syscall_broker::BrokerPolicy policy_; // The sandboxing policy. + pid_t broker_pid_; // The PID of the broker (child). + syscall_broker::BrokerPolicy broker_policy_; // Access policy to enforce. std::unique_ptr<syscall_broker::BrokerClient> broker_client_; DISALLOW_COPY_AND_ASSIGN(BrokerProcess); diff --git a/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc b/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc index 0d61ab7703d..a53600337a1 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc @@ -661,6 +661,169 @@ TEST(BrokerProcess, CreateFile) { } } -} // namespace syscall_broker +TEST(BrokerProcess, StatFile) { + ScopedTemporaryFile tmp_file; + EXPECT_EQ(12, write(tmp_file.fd(), "blahblahblah", 12)); + + std::string temp_str = tmp_file.full_file_name(); + const char* tempfile_name = temp_str.c_str(); + const char* nonesuch_name = "/mbogo/nonesuch"; + const bool fast_check_in_client = false; + struct stat sb; + { + // Nonexistent file with no permissions to see file. + std::vector<BrokerFilePermission> permissions; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + memset(&sb, 0, sizeof(sb)); + EXPECT_EQ(-EPERM, open_broker.Stat(nonesuch_name, &sb)); + } + { + // Actual file with no permission to see file. + std::vector<BrokerFilePermission> permissions; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + memset(&sb, 0, sizeof(sb)); + EXPECT_EQ(-EPERM, open_broker.Stat(tempfile_name, &sb)); + } + { + // Nonexistent file with permissions to see file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(nonesuch_name)); + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + memset(&sb, 0, sizeof(sb)); + EXPECT_EQ(-ENOENT, open_broker.Stat(nonesuch_name, &sb)); + } + { + // Actual file with permissions to see file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(tempfile_name)); + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + memset(&sb, 0, sizeof(sb)); + EXPECT_EQ(0, open_broker.Stat(tempfile_name, &sb)); + + // Following fields may never be consistent but should be non-zero. + // Don't trust the platform to define fields with any particular sign. + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_dev)); + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_ino)); + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_mode)); + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_uid)); + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_gid)); + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_blksize)); + EXPECT_NE(0u, static_cast<unsigned int>(sb.st_blocks)); + + // Wrote 12 bytes above which should fit in one block. + EXPECT_EQ(12, sb.st_size); + + // Can't go backwards in time, 1500000000 was some time ago. + EXPECT_LT(1500000000u, static_cast<unsigned int>(sb.st_atime)); + EXPECT_LT(1500000000u, static_cast<unsigned int>(sb.st_mtime)); + EXPECT_LT(1500000000u, static_cast<unsigned int>(sb.st_ctime)); + } +} + +TEST(BrokerProcess, RenameFile) { + std::string oldpath; + std::string newpath; + { + // Just to generate names and ensure they do not exist upon scope exit. + ScopedTemporaryFile oldfile; + ScopedTemporaryFile newfile; + oldpath = oldfile.full_file_name(); + newpath = newfile.full_file_name(); + } + // Now make a file using old path name. + int fd = open(oldpath.c_str(), O_RDWR | O_CREAT, 0600); + EXPECT_TRUE(fd > 0); + close(fd); + + EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + + { + // Check rename fails when no permission to new file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWrite(oldpath)); + + bool fast_check_in_client = false; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + + // ... and no files moved around. + EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + } + { + // Check rename fails when no permission to old file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWrite(newpath)); + + bool fast_check_in_client = false; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + + // ... and no files moved around. + EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + } + { + // Check rename fails when only read permission to first file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(oldpath)); + permissions.push_back(BrokerFilePermission::ReadWrite(newpath)); + + bool fast_check_in_client = false; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + + // ... and no files moved around. + EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + } + { + // Check rename fails when only read permission to first file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWrite(oldpath)); + permissions.push_back(BrokerFilePermission::ReadOnly(newpath)); + + bool fast_check_in_client = false; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + + // ... and no files moved around. + EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + } + { + // Check rename passes with write permissions to both files. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWrite(oldpath)); + permissions.push_back(BrokerFilePermission::ReadWrite(newpath)); + + bool fast_check_in_client = false; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + EXPECT_EQ(0, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + + // ... and files were moved around. + EXPECT_TRUE(access(oldpath.c_str(), F_OK) < 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) == 0); + } + + // Cleanup using new path name. + unlink(newpath.c_str()); +} + +} // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/mac/seatbelt_exec.cc b/chromium/sandbox/mac/seatbelt_exec.cc index 52af21e1b29..53a5e7901ae 100644 --- a/chromium/sandbox/mac/seatbelt_exec.cc +++ b/chromium/sandbox/mac/seatbelt_exec.cc @@ -4,6 +4,7 @@ #include "sandbox/mac/seatbelt_exec.h" +#include <fcntl.h> #include <stdarg.h> #include <stdio.h> #include <sys/socket.h> @@ -22,6 +23,13 @@ namespace sandbox { SeatbeltExecClient::SeatbeltExecClient() { if (pipe(pipe_) != 0) logging::PFatal("SeatbeltExecClient: pipe failed"); + + int pipe_flags = fcntl(pipe_[1], F_GETFL); + if (pipe_flags == -1) + logging::PFatal("SeatbeltExecClient: fctnl(F_GETFL) failed"); + + if (fcntl(pipe_[1], F_SETFL, pipe_flags | O_NONBLOCK) == -1) + logging::PFatal("SeatbeltExecClient: fcntl(F_SETFL) failed"); } SeatbeltExecClient::~SeatbeltExecClient() { diff --git a/chromium/sandbox/win/BUILD.gn b/chromium/sandbox/win/BUILD.gn index 22e915dcde8..48f4b1e76a0 100644 --- a/chromium/sandbox/win/BUILD.gn +++ b/chromium/sandbox/win/BUILD.gn @@ -12,6 +12,8 @@ static_library("sandbox") { sources = [ "src/acl.cc", "src/acl.h", + "src/app_container_profile.cc", + "src/app_container_profile.h", "src/broker_services.cc", "src/broker_services.h", "src/crosscall_client.h", @@ -104,6 +106,8 @@ static_library("sandbox") { "src/sandbox_types.h", "src/sandbox_utils.cc", "src/sandbox_utils.h", + "src/security_capabilities.cc", + "src/security_capabilities.h", "src/security_level.h", "src/service_resolver.cc", "src/service_resolver.h", @@ -293,6 +297,7 @@ test("sbox_validation_tests") { test("sbox_unittests") { sources = [ + "src/app_container_unittest.cc", "src/interception_unittest.cc", "src/ipc_unittest.cc", "src/job_unittest.cc", @@ -324,7 +329,6 @@ test("sandbox_poc") { "sandbox_poc/resource.h", "sandbox_poc/sandbox.cc", "sandbox_poc/sandbox.h", - "sandbox_poc/sandbox.ico", "sandbox_poc/sandbox.rc", ] diff --git a/chromium/sandbox/win/PRESUBMIT.py b/chromium/sandbox/win/PRESUBMIT.py deleted file mode 100644 index 65df23d71c8..00000000000 --- a/chromium/sandbox/win/PRESUBMIT.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -"""Presubmit script for sandbox/win. - -See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts -for more details about the presubmit API built into depot_tools. -""" - -def PostUploadHook(cl, change, output_api): - """git cl upload will call this hook after the issue is created/modified. - - This hook adds extra try bots list to the CL description in order to run - tests on the Windows 10 try bot in addition to CQ try bots. - """ - return output_api.EnsureCQIncludeTrybotsAreAdded( - cl, - [ - 'master.tryserver.chromium.win:win10_chromium_x64_rel_ng', - ], - 'Automatically added Win10 bot to run on CQ.') diff --git a/chromium/sandbox/win/src/app_container_profile.cc b/chromium/sandbox/win/src/app_container_profile.cc new file mode 100644 index 00000000000..b0cb041e72d --- /dev/null +++ b/chromium/sandbox/win/src/app_container_profile.cc @@ -0,0 +1,324 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> + +#include <aclapi.h> +#include <userenv.h> + +#include "base/strings/stringprintf.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +typedef decltype(::CreateAppContainerProfile) CreateAppContainerProfileFunc; + +typedef decltype(::DeriveAppContainerSidFromAppContainerName) + DeriveAppContainerSidFromAppContainerNameFunc; + +typedef decltype(::DeleteAppContainerProfile) DeleteAppContainerProfileFunc; + +typedef decltype(::GetAppContainerFolderPath) GetAppContainerFolderPathFunc; + +typedef decltype( + ::GetAppContainerRegistryLocation) GetAppContainerRegistryLocationFunc; + +struct FreeSidDeleter { + inline void operator()(void* ptr) const { ::FreeSid(ptr); } +}; + +bool IsValidObjectType(SE_OBJECT_TYPE object_type) { + switch (object_type) { + case SE_FILE_OBJECT: + case SE_REGISTRY_KEY: + return true; + default: + break; + } + return false; +} + +bool GetGenericMappingForType(SE_OBJECT_TYPE object_type, + GENERIC_MAPPING* generic_mapping) { + if (!IsValidObjectType(object_type)) + return false; + if (object_type == SE_FILE_OBJECT) { + generic_mapping->GenericRead = FILE_GENERIC_READ; + generic_mapping->GenericWrite = FILE_GENERIC_WRITE; + generic_mapping->GenericExecute = FILE_GENERIC_EXECUTE; + generic_mapping->GenericAll = FILE_ALL_ACCESS; + } else { + generic_mapping->GenericRead = KEY_READ; + generic_mapping->GenericWrite = KEY_WRITE; + generic_mapping->GenericExecute = KEY_EXECUTE; + generic_mapping->GenericAll = KEY_ALL_ACCESS; + } + return true; +} + +class ScopedImpersonation { + public: + ScopedImpersonation(const base::win::ScopedHandle& token) { + BOOL result = ::ImpersonateLoggedOnUser(token.Get()); + DCHECK(result); + } + + ~ScopedImpersonation() { + BOOL result = ::RevertToSelf(); + DCHECK(result); + } +}; + +} // namespace + +// static +scoped_refptr<AppContainerProfile> AppContainerProfile::Create( + const wchar_t* package_name, + const wchar_t* display_name, + const wchar_t* description) { + static auto create_app_container_profile = + reinterpret_cast<CreateAppContainerProfileFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "CreateAppContainerProfile")); + if (!create_app_container_profile) + return nullptr; + + PSID package_sid = nullptr; + HRESULT hr = create_app_container_profile( + package_name, display_name, description, nullptr, 0, &package_sid); + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) + return Open(package_name); + + if (FAILED(hr)) + return nullptr; + std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid); + return new AppContainerProfile(Sid(package_sid)); +} + +// static +scoped_refptr<AppContainerProfile> AppContainerProfile::Open( + const wchar_t* package_name) { + static auto derive_app_container_sid = + reinterpret_cast<DeriveAppContainerSidFromAppContainerNameFunc*>( + GetProcAddress(GetModuleHandle(L"userenv"), + "DeriveAppContainerSidFromAppContainerName")); + if (!derive_app_container_sid) + return nullptr; + + PSID package_sid = nullptr; + HRESULT hr = derive_app_container_sid(package_name, &package_sid); + if (FAILED(hr)) + return nullptr; + + std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid); + return new AppContainerProfile(Sid(package_sid)); +} + +// static +bool AppContainerProfile::Delete(const wchar_t* package_name) { + static auto delete_app_container_profile = + reinterpret_cast<DeleteAppContainerProfileFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "DeleteAppContainerProfile")); + if (!delete_app_container_profile) + return false; + + return SUCCEEDED(delete_app_container_profile(package_name)); +} + +AppContainerProfile::AppContainerProfile(const Sid& package_sid) + : ref_count_(0), + package_sid_(package_sid), + enable_low_privilege_app_container_(false) {} + +AppContainerProfile::~AppContainerProfile() {} + +void AppContainerProfile::AddRef() { + ::InterlockedIncrement(&ref_count_); +} + +void AppContainerProfile::Release() { + LONG ref_count = ::InterlockedDecrement(&ref_count_); + if (ref_count == 0) { + delete this; + } +} + +bool AppContainerProfile::GetRegistryLocation(REGSAM desired_access, + base::win::ScopedHandle* key) { + static GetAppContainerRegistryLocationFunc* + get_app_container_registry_location = + reinterpret_cast<GetAppContainerRegistryLocationFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "GetAppContainerRegistryLocation")); + if (!get_app_container_registry_location) + return false; + + base::win::ScopedHandle token; + if (!BuildLowBoxToken(&token)) + return false; + + ScopedImpersonation impersonation(token); + HKEY key_handle; + if (FAILED(get_app_container_registry_location(desired_access, &key_handle))) + return false; + key->Set(key_handle); + return true; +} + +bool AppContainerProfile::GetFolderPath(base::FilePath* file_path) { + static GetAppContainerFolderPathFunc* get_app_container_folder_path = + reinterpret_cast<GetAppContainerFolderPathFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "GetAppContainerFolderPath")); + if (!get_app_container_folder_path) + return false; + base::string16 sddl_str; + if (!package_sid_.ToSddlString(&sddl_str)) + return false; + base::win::ScopedCoMem<wchar_t> path_str; + if (FAILED(get_app_container_folder_path(sddl_str.c_str(), &path_str))) + return false; + *file_path = base::FilePath(path_str.get()); + return true; +} + +bool AppContainerProfile::GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) { + base::string16 sddl_str; + if (!package_sid_.ToSddlString(&sddl_str)) + return false; + *pipe_path = base::FilePath(base::StringPrintf(L"\\\\.\\pipe\\%ls\\%ls", + sddl_str.c_str(), pipe_name)); + return true; +} + +bool AppContainerProfile::AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) { + GENERIC_MAPPING generic_mapping; + if (!GetGenericMappingForType(object_type, &generic_mapping)) + return false; + PSECURITY_DESCRIPTOR sd = nullptr; + PACL dacl = nullptr; + if (GetNamedSecurityInfo( + object_name, object_type, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + nullptr, nullptr, &dacl, nullptr, &sd) != ERROR_SUCCESS) { + return false; + } + + std::unique_ptr<void, LocalFreeDeleter> sd_ptr(sd); + + if (enable_low_privilege_app_container_) { + Sid any_package_sid(::WinBuiltinAnyPackageSid); + // We can't create a LPAC token directly, so modify the DACL to simulate it. + // Set mask for ALL APPLICATION PACKAGE Sid to 0. + for (WORD index = 0; index < dacl->AceCount; ++index) { + PVOID temp_ace; + if (!GetAce(dacl, index, &temp_ace)) + return false; + PACE_HEADER header = static_cast<PACE_HEADER>(temp_ace); + if ((header->AceType != ACCESS_ALLOWED_ACE_TYPE) && + (header->AceType != ACCESS_DENIED_ACE_TYPE)) { + continue; + } + // Allowed and deny aces have the same underlying structure. + PACCESS_ALLOWED_ACE ace = static_cast<PACCESS_ALLOWED_ACE>(temp_ace); + if (!::IsValidSid(&ace->SidStart)) { + continue; + } + if (::EqualSid(&ace->SidStart, any_package_sid.GetPSID())) { + ace->Mask = 0; + } + } + } + + PRIVILEGE_SET priv_set = {}; + DWORD priv_set_length = sizeof(PRIVILEGE_SET); + + base::win::ScopedHandle token; + if (!BuildLowBoxToken(&token)) + return false; + + return !!::AccessCheck(sd, token.Get(), desired_access, &generic_mapping, + &priv_set, &priv_set_length, granted_access, + access_status); +} + +bool AppContainerProfile::AddCapability(const wchar_t* capability_name) { + return AddCapability(Sid::FromNamedCapability(capability_name), false); +} + +bool AppContainerProfile::AddCapability(WellKnownCapabilities capability) { + return AddCapability(Sid::FromKnownCapability(capability), false); +} + +bool AppContainerProfile::AddCapability(const Sid& capability_sid) { + return AddCapability(capability_sid, false); +} + +bool AppContainerProfile::AddCapability(const Sid& capability_sid, + bool impersonation_only) { + if (!capability_sid.IsValid()) + return false; + if (!impersonation_only) + capabilities_.push_back(capability_sid); + impersonation_capabilities_.push_back(capability_sid); + return true; +} + +bool AppContainerProfile::AddImpersonationCapability( + const wchar_t* capability_name) { + return AddCapability(Sid::FromNamedCapability(capability_name), true); +} + +bool AppContainerProfile::AddImpersonationCapability( + WellKnownCapabilities capability) { + return AddCapability(Sid::FromKnownCapability(capability), true); +} + +bool AppContainerProfile::AddImpersonationCapability( + const Sid& capability_sid) { + return AddCapability(capability_sid, true); +} + +const std::vector<Sid>& AppContainerProfile::GetCapabilities() { + return capabilities_; +} + +const std::vector<Sid>& AppContainerProfile::GetImpersonationCapabilities() { + return impersonation_capabilities_; +} + +Sid AppContainerProfile::GetPackageSid() const { + return package_sid_; +} + +void AppContainerProfile::SetEnableLowPrivilegeAppContainer(bool enable) { + enable_low_privilege_app_container_ = enable; +} + +bool AppContainerProfile::GetEnableLowPrivilegeAppContainer() { + return enable_low_privilege_app_container_; +} + +std::unique_ptr<SecurityCapabilities> +AppContainerProfile::GetSecurityCapabilities() { + return std::unique_ptr<SecurityCapabilities>( + new SecurityCapabilities(package_sid_, capabilities_)); +} + +bool AppContainerProfile::BuildLowBoxToken(base::win::ScopedHandle* token) { + return CreateLowBoxToken(nullptr, IMPERSONATION, + GetSecurityCapabilities().get(), nullptr, 0, + token) == ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/chromium/sandbox/win/src/app_container_profile.h b/chromium/sandbox/win/src/app_container_profile.h new file mode 100644 index 00000000000..33f79381db8 --- /dev/null +++ b/chromium/sandbox/win/src/app_container_profile.h @@ -0,0 +1,121 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ +#define SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ + +#include <windows.h> + +#include <accctrl.h> + +#include <memory> +#include <vector> + +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class AppContainerProfile { + public: + // Increments the reference count of this object. The reference count must + // be incremented if this interface is given to another component. + void AddRef(); + + // Decrements the reference count of this object. When the reference count + // is zero the object is automatically destroyed. + // Indicates that the caller is done with this interface. After calling + // release no other method should be called. + void Release(); + + // Get a handle to a registry key for this package. + bool GetRegistryLocation(REGSAM desired_access, base::win::ScopedHandle* key); + + // Get a folder path to a location for this package. + bool GetFolderPath(base::FilePath* file_path); + + // Get a pipe name usable by this AC. + bool GetPipePath(const wchar_t* pipe_name, base::FilePath* pipe_path); + + // Get the package SID for this AC. + Sid GetPackageSid() const; + + // Do an access check based on this profile for a named object. If method + // returns true then access_status reflects whether access was granted and + // granted_access gives the final access rights. The object_type can be one of + // SE_FILE_OBJECT, SE_REGISTRY_KEY, SE_REGISTRY_WOW64_32KEY. See + // ::GetNamedSecurityInfo for more information about how the enumeration is + // used and what format object_name needs to be. + bool AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status); + + // Adds a capability by name to this profile. + bool AddCapability(const wchar_t* capability_name); + // Adds a capability from a known list. + bool AddCapability(WellKnownCapabilities capability); + // Adds a capability from a SID + bool AddCapability(const Sid& capability_sid); + + // Adds an impersonation capability by name to this profile. + bool AddImpersonationCapability(const wchar_t* capability_name); + // Adds an impersonation capability from a known list. + bool AddImpersonationCapability(WellKnownCapabilities capability); + // Adds an impersonation capability from a SID + bool AddImpersonationCapability(const Sid& capability_sid); + + // Enable Low Privilege AC. + void SetEnableLowPrivilegeAppContainer(bool enable); + bool GetEnableLowPrivilegeAppContainer(); + + // Get an allocated SecurityCapabilities object for this App Container. + std::unique_ptr<SecurityCapabilities> GetSecurityCapabilities(); + + // Get a vector of capabilities. + const std::vector<Sid>& GetCapabilities(); + + // Get a vector of impersonation only capabilities. Used if the process needs + // a more privileged token to start. + const std::vector<Sid>& GetImpersonationCapabilities(); + + // Creates a new AppContainerProfile object. This will create a new profile + // if it doesn't already exist. The profile must be deleted manually using + // the Delete method if it's no longer required. + static scoped_refptr<AppContainerProfile> Create(const wchar_t* package_name, + const wchar_t* display_name, + const wchar_t* description); + + // Opens an AppContainerProfile object. No checks will be made on + // whether the package exists or not. + static scoped_refptr<AppContainerProfile> Open(const wchar_t* package_name); + + // Delete a profile based on name. Returns true if successful, or if the + // package doesn't already exist. + static bool Delete(const wchar_t* package_name); + + private: + AppContainerProfile(const Sid& package_sid); + ~AppContainerProfile(); + + bool BuildLowBoxToken(base::win::ScopedHandle* token); + bool AddCapability(const Sid& capability_sid, bool impersonation_only); + + // Standard object-lifetime reference counter. + volatile LONG ref_count_; + Sid package_sid_; + bool enable_low_privilege_app_container_; + std::vector<Sid> capabilities_; + std::vector<Sid> impersonation_capabilities_; + + DISALLOW_COPY_AND_ASSIGN(AppContainerProfile); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ diff --git a/chromium/sandbox/win/src/app_container_unittest.cc b/chromium/sandbox/win/src/app_container_unittest.cc new file mode 100644 index 00000000000..b61541f8aac --- /dev/null +++ b/chromium/sandbox/win/src/app_container_unittest.cc @@ -0,0 +1,382 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include <Sddl.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sid.h" +#include "sandbox/win/src/win_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool ValidSecurityCapabilities(PSECURITY_CAPABILITIES security_capabilities, + const Sid& package_sid, + const std::vector<Sid>& capabilities) { + if (!security_capabilities) + return false; + + if (!::EqualSid(package_sid.GetPSID(), + security_capabilities->AppContainerSid)) { + return false; + } + + // If empty then count and list of capabilities should be 0 and nullptr. + if (capabilities.empty() && !security_capabilities->CapabilityCount && + !security_capabilities->Capabilities) { + return true; + } + + if (!security_capabilities->Capabilities) + return false; + + if (security_capabilities->CapabilityCount != capabilities.size()) + return false; + + for (DWORD index = 0; index < security_capabilities->CapabilityCount; + ++index) { + if (!::EqualSid(capabilities[index].GetPSID(), + security_capabilities->Capabilities[index].Sid)) { + return false; + } + if (security_capabilities->Capabilities[index].Attributes != + SE_GROUP_ENABLED) { + return false; + } + } + + return true; +} + +bool CompareSidVectors(const std::vector<Sid>& left, + const std::vector<Sid>& right) { + if (left.size() != right.size()) + return false; + auto left_interator = left.cbegin(); + auto right_interator = right.cbegin(); + while (left_interator != left.cend()) { + if (!::EqualSid(left_interator->GetPSID(), right_interator->GetPSID())) + return false; + ++left_interator; + ++right_interator; + } + return true; +} + +bool GetProfilePath(const std::wstring& package_name, + base::FilePath* profile_path) { + base::FilePath local_app_data; + if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &local_app_data)) + return false; + *profile_path = local_app_data.Append(L"Packages").Append(package_name); + return true; +} + +bool ProfileExist(const std::wstring& package_name) { + base::FilePath profile_path; + if (!GetProfilePath(package_name, &profile_path)) + return false; + return base::PathExists(profile_path); +} + +std::wstring GenerateRandomPackageName() { + return base::StringPrintf(L"%016lX%016lX", base::RandUint64(), + base::RandUint64()); +} + +class SECURITY_ATTRIBUTES_SDDL : public SECURITY_ATTRIBUTES { + public: + SECURITY_ATTRIBUTES_SDDL(LPCWSTR sddl) : SECURITY_ATTRIBUTES() { + nLength = sizeof(SECURITY_ATTRIBUTES); + if (!::ConvertStringSecurityDescriptorToSecurityDescriptor( + sddl, SDDL_REVISION_1, &lpSecurityDescriptor, nullptr)) { + lpSecurityDescriptor = nullptr; + } + } + + ~SECURITY_ATTRIBUTES_SDDL() { + if (lpSecurityDescriptor) + ::LocalFree(lpSecurityDescriptor); + } + + bool IsValid() { return lpSecurityDescriptor != nullptr; } +}; + +std::wstring CreateSddlWithSid(const Sid& sid) { + base::string16 sddl_string; + if (!sid.ToSddlString(&sddl_string)) + return L""; + std::wstring base_sddl = L"D:(A;;GA;;;WD)(A;;GA;;;"; + return base_sddl + sddl_string + L")"; +} + +void AccessCheckFile(AppContainerProfile* profile, + const base::FilePath& path, + const Sid& sid, + DWORD desired_access, + DWORD expected_access, + BOOL expected_status) { + SECURITY_ATTRIBUTES_SDDL sa(CreateSddlWithSid(sid).c_str()); + ASSERT_TRUE(sa.IsValid()); + base::win::ScopedHandle file_handle(::CreateFile( + path.value().c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_DELETE, &sa, + CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr)); + + ASSERT_TRUE(file_handle.IsValid()); + DWORD granted_access; + BOOL access_status; + ASSERT_TRUE(profile->AccessCheck(path.value().c_str(), SE_FILE_OBJECT, + FILE_READ_DATA, &granted_access, + &access_status)); + ASSERT_EQ(expected_status, access_status); + if (access_status) + ASSERT_EQ(expected_access, granted_access); +} + +} // namespace + +TEST(AppContainerTest, SecurityCapabilities) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + // This isn't a valid package SID but it doesn't matter for this test. + Sid package_sid(::WinNullSid); + + std::vector<Sid> capabilities; + SecurityCapabilities no_capabilities(package_sid); + EXPECT_TRUE( + ValidSecurityCapabilities(&no_capabilities, package_sid, capabilities)); + + capabilities.push_back(::WinWorldSid); + SecurityCapabilities one_capability(package_sid, capabilities); + EXPECT_TRUE( + ValidSecurityCapabilities(&one_capability, package_sid, capabilities)); + + capabilities.push_back(::WinLocalSid); + SecurityCapabilities two_capabilities(package_sid, capabilities); + EXPECT_TRUE( + ValidSecurityCapabilities(&two_capabilities, package_sid, capabilities)); +} + +TEST(AppContainerTest, CreateAndDeleteAppContainerProfile) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + std::wstring package_name = GenerateRandomPackageName(); + EXPECT_FALSE(ProfileExist(package_name)); + scoped_refptr<AppContainerProfile> profile = AppContainerProfile::Create( + package_name.c_str(), L"Name", L"Description"); + ASSERT_NE(nullptr, profile.get()); + EXPECT_TRUE(ProfileExist(package_name)); + EXPECT_TRUE(AppContainerProfile::Delete(package_name.c_str())); + EXPECT_FALSE(ProfileExist(package_name)); +} + +TEST(AppContainerTest, CreateAndOpenAppContainerProfile) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + std::wstring package_name = GenerateRandomPackageName(); + EXPECT_FALSE(ProfileExist(package_name)); + scoped_refptr<AppContainerProfile> profile = AppContainerProfile::Create( + package_name.c_str(), L"Name", L"Description"); + ASSERT_NE(nullptr, profile.get()); + EXPECT_TRUE(ProfileExist(package_name)); + scoped_refptr<AppContainerProfile> open_profile = + AppContainerProfile::Open(package_name.c_str()); + ASSERT_NE(nullptr, profile.get()); + EXPECT_TRUE(::EqualSid(profile->GetPackageSid().GetPSID(), + open_profile->GetPackageSid().GetPSID())); + EXPECT_TRUE(AppContainerProfile::Delete(package_name.c_str())); + EXPECT_FALSE(ProfileExist(package_name)); + scoped_refptr<AppContainerProfile> open_profile2 = + AppContainerProfile::Open(package_name.c_str()); + EXPECT_FALSE(ProfileExist(package_name)); +} + +TEST(AppContainerTest, SetLowPrivilegeAppContainer) { + // LPAC first supported in RS1. + if (base::win::GetVersion() < base::win::VERSION_WIN10_RS1) + return; + std::wstring package_name = GenerateRandomPackageName(); + scoped_refptr<AppContainerProfile> profile = + AppContainerProfile::Open(package_name.c_str()); + ASSERT_NE(nullptr, profile.get()); + profile->SetEnableLowPrivilegeAppContainer(true); + EXPECT_TRUE(profile->GetEnableLowPrivilegeAppContainer()); +} + +TEST(AppContainerTest, OpenAppContainerProfileAndGetSecurityCapabilities) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + std::wstring package_name = GenerateRandomPackageName(); + scoped_refptr<AppContainerProfile> profile = + AppContainerProfile::Open(package_name.c_str()); + ASSERT_NE(nullptr, profile.get()); + + std::vector<Sid> capabilities; + auto no_capabilities = profile->GetSecurityCapabilities(); + ASSERT_TRUE(ValidSecurityCapabilities( + no_capabilities.get(), profile->GetPackageSid(), capabilities)); + + // No support for named capabilities prior to Win10. + if (base::win::GetVersion() >= base::win::VERSION_WIN10) { + ASSERT_TRUE(profile->AddCapability(L"FakeCapability")); + capabilities.push_back(Sid::FromNamedCapability(L"FakeCapability")); + } + + ASSERT_TRUE(profile->AddCapability(kInternetClient)); + capabilities.push_back(Sid::FromKnownCapability(kInternetClient)); + Sid sid_sddl = Sid::FromSddlString(L"S-1-15-3-1"); + ASSERT_TRUE(profile->AddCapability(sid_sddl)); + capabilities.push_back(sid_sddl); + auto with_capabilities = profile->GetSecurityCapabilities(); + ASSERT_TRUE(ValidSecurityCapabilities( + with_capabilities.get(), profile->GetPackageSid(), capabilities)); +} + +TEST(AppContainerTest, GetResources) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + std::wstring package_name = GenerateRandomPackageName(); + scoped_refptr<AppContainerProfile> profile = AppContainerProfile::Create( + package_name.c_str(), L"Name", L"Description"); + ASSERT_NE(nullptr, profile.get()); + base::win::ScopedHandle key; + EXPECT_TRUE(profile->GetRegistryLocation(KEY_READ, &key)); + EXPECT_TRUE(key.IsValid()); + key.Close(); + base::FilePath path; + EXPECT_TRUE(profile->GetFolderPath(&path)); + EXPECT_TRUE(base::PathExists(path)); + base::FilePath pipe_path; + EXPECT_TRUE(profile->GetPipePath(package_name.c_str(), &pipe_path)); + base::win::ScopedHandle pipe_handle; + pipe_handle.Set(::CreateNamedPipe( + pipe_path.value().c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE, + PIPE_UNLIMITED_INSTANCES, 0, 0, 0, nullptr)); + EXPECT_TRUE(pipe_handle.IsValid()); + EXPECT_TRUE(AppContainerProfile::Delete(package_name.c_str())); +} + +TEST(AppContainerTest, AccessCheckFile) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + // We don't need a valid profile to do the access check tests. + std::wstring package_name = GenerateRandomPackageName(); + scoped_refptr<AppContainerProfile> profile = + AppContainerProfile::Open(package_name.c_str()); + profile->AddCapability(kInternetClient); + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.GetPath().Append(package_name); + + AccessCheckFile(profile.get(), path, ::WinNullSid, FILE_READ_DATA, 0, FALSE); + AccessCheckFile(profile.get(), path, ::WinBuiltinAnyPackageSid, + FILE_READ_DATA, FILE_READ_DATA, TRUE); + AccessCheckFile(profile.get(), path, profile->GetPackageSid(), FILE_READ_DATA, + FILE_READ_DATA, TRUE); + AccessCheckFile(profile.get(), path, + Sid::FromKnownCapability(kInternetClient), FILE_READ_DATA, + FILE_READ_DATA, TRUE); + + // No support for LPAC less than Win10 RS1. + if (base::win::GetVersion() < base::win::VERSION_WIN10_RS1) + return; + profile->SetEnableLowPrivilegeAppContainer(true); + AccessCheckFile(profile.get(), path, ::WinBuiltinAnyPackageSid, + FILE_READ_DATA, 0, FALSE); + AccessCheckFile(profile.get(), path, Sid::AllRestrictedApplicationPackages(), + FILE_READ_DATA, FILE_READ_DATA, TRUE); +} + +TEST(AppContainerTest, AccessCheckRegistry) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + // We don't need a valid profile to do the access check tests. + std::wstring package_name = GenerateRandomPackageName(); + scoped_refptr<AppContainerProfile> profile = + AppContainerProfile::Open(package_name.c_str()); + // Ensure the key doesn't exist. + RegDeleteKey(HKEY_CURRENT_USER, package_name.c_str()); + SECURITY_ATTRIBUTES_SDDL sa( + CreateSddlWithSid(::WinBuiltinAnyPackageSid).c_str()); + HKEY key_handle; + ASSERT_EQ(ERROR_SUCCESS, + RegCreateKeyEx(HKEY_CURRENT_USER, package_name.c_str(), 0, nullptr, + REG_OPTION_VOLATILE, KEY_ALL_ACCESS, &sa, + &key_handle, nullptr)); + base::win::ScopedHandle key(key_handle); + std::wstring key_name = L"CURRENT_USER\\"; + key_name += package_name; + DWORD granted_access; + BOOL access_status; + + ASSERT_TRUE(profile->AccessCheck(key_name.c_str(), SE_REGISTRY_KEY, + KEY_QUERY_VALUE, &granted_access, + &access_status)); + ASSERT_TRUE(access_status); + ASSERT_EQ(DWORD{KEY_QUERY_VALUE}, granted_access); + RegDeleteKey(HKEY_CURRENT_USER, package_name.c_str()); +} + +TEST(AppContainerTest, ImpersonationCapabilities) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + std::wstring package_name = GenerateRandomPackageName(); + scoped_refptr<AppContainerProfile> profile = + AppContainerProfile::Open(package_name.c_str()); + ASSERT_NE(nullptr, profile.get()); + + std::vector<Sid> capabilities; + std::vector<Sid> impersonation_capabilities; + + ASSERT_TRUE(profile->AddCapability(kInternetClient)); + capabilities.push_back(Sid::FromKnownCapability(kInternetClient)); + impersonation_capabilities.push_back( + Sid::FromKnownCapability(kInternetClient)); + + ASSERT_TRUE(CompareSidVectors(profile->GetCapabilities(), capabilities)); + ASSERT_TRUE(CompareSidVectors(profile->GetImpersonationCapabilities(), + impersonation_capabilities)); + + ASSERT_TRUE(profile->AddImpersonationCapability(kPrivateNetworkClientServer)); + impersonation_capabilities.push_back( + Sid::FromKnownCapability(kPrivateNetworkClientServer)); + // No support for named capabilities prior to Win10. + if (base::win::GetVersion() >= base::win::VERSION_WIN10) { + ASSERT_TRUE(profile->AddImpersonationCapability(L"FakeCapability")); + impersonation_capabilities.push_back( + Sid::FromNamedCapability(L"FakeCapability")); + } + Sid sid_sddl = Sid::FromSddlString(L"S-1-15-3-1"); + ASSERT_TRUE(profile->AddImpersonationCapability(sid_sddl)); + impersonation_capabilities.push_back(sid_sddl); + ASSERT_TRUE(CompareSidVectors(profile->GetCapabilities(), capabilities)); + ASSERT_TRUE(CompareSidVectors(profile->GetImpersonationCapabilities(), + impersonation_capabilities)); +} + +} // namespace sandbox diff --git a/chromium/sandbox/win/src/broker_services.h b/chromium/sandbox/win/src/broker_services.h index 539dd3b83f0..14cfed5f947 100644 --- a/chromium/sandbox/win/src/broker_services.h +++ b/chromium/sandbox/win/src/broker_services.h @@ -13,6 +13,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" +#include "base/memory/scoped_refptr.h" #include "base/win/scoped_handle.h" #include "sandbox/win/src/crosscall_server.h" #include "sandbox/win/src/job.h" diff --git a/chromium/sandbox/win/src/filesystem_policy.cc b/chromium/sandbox/win/src/filesystem_policy.cc index dd504ab31f7..a21a0c3aacb 100644 --- a/chromium/sandbox/win/src/filesystem_policy.cc +++ b/chromium/sandbox/win/src/filesystem_policy.cc @@ -30,7 +30,7 @@ NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, ULONG create_disposition, ULONG create_options, PVOID ea_buffer, - ULONG ea_lenght, + ULONG ea_length, HANDLE target_process) { NtCreateFileFunction NtCreateFile = nullptr; ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile); @@ -39,7 +39,7 @@ NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, NTSTATUS status = NtCreateFile(&local_handle, desired_access, obj_attributes, io_status_block, nullptr, file_attributes, share_access, - create_disposition, create_options, ea_buffer, ea_lenght); + create_disposition, create_options, ea_buffer, ea_length); if (!NT_SUCCESS(status)) { return status; } diff --git a/chromium/sandbox/win/src/nt_internals.h b/chromium/sandbox/win/src/nt_internals.h index d89c9b10654..2b2dc0f2889 100644 --- a/chromium/sandbox/win/src/nt_internals.h +++ b/chromium/sandbox/win/src/nt_internals.h @@ -794,6 +794,11 @@ struct PROCESS_ACCESS_TOKEN { const unsigned int NtProcessInformationAccessToken = 9; +typedef NTSTATUS(WINAPI* RtlDeriveCapabilitySidsFromNameFunction)( + PCUNICODE_STRING SourceString, + PSID CapabilityGroupSid, + PSID CapabilitySid); + // ----------------------------------------------------------------------- // GDI OPM API and Supported Calls diff --git a/chromium/sandbox/win/src/policy_engine_opcodes.cc b/chromium/sandbox/win/src/policy_engine_opcodes.cc index 71a3ea030e2..4bac801f9bc 100644 --- a/chromium/sandbox/win/src/policy_engine_opcodes.cc +++ b/chromium/sandbox/win/src/policy_engine_opcodes.cc @@ -217,7 +217,7 @@ EvalResult OpcodeEval<OP_NUMBER_AND_MATCH>(PolicyOpcode* opcode, // Opcode OpWStringMatch: // Requires a wchar_t* in selected_param. // Argument 0 is the byte displacement of the stored string. -// Argument 1 is the lenght in chars of the stored string. +// Argument 1 is the length in chars of the stored string. // Argument 2 is the offset to apply on the input string. It has special values. // as noted in the header file. // Argument 3 is the string matching options. @@ -232,7 +232,7 @@ PolicyOpcode* OpcodeFactory::MakeOpWStringMatch(int16_t selected_param, if ('\0' == match_str[0]) return nullptr; - int lenght = lstrlenW(match_str); + int length = lstrlenW(match_str); PolicyOpcode* opcode = MakeBase(OP_WSTRING_MATCH, options, selected_param); if (!opcode) @@ -241,7 +241,7 @@ PolicyOpcode* OpcodeFactory::MakeOpWStringMatch(int16_t selected_param, if (0 == delta_str) return nullptr; opcode->SetArgument(0, delta_str); - opcode->SetArgument(1, lenght); + opcode->SetArgument(1, length); opcode->SetArgument(2, start_position); opcode->SetArgument(3, match_opts); return opcode; @@ -291,8 +291,8 @@ EvalResult OpcodeEval<OP_WSTRING_MATCH>(PolicyOpcode* opcode, if (start_position >= 0) { if (kSeekToEnd == start_position) { start_position = source_len - match_len; - } else if (match_opts & EXACT_LENGHT) { - // A sub-case of case 3 is when the EXACT_LENGHT flag is on + } else if (match_opts & EXACT_LENGTH) { + // A sub-case of case 3 is when the EXACT_LENGTH flag is on // the match needs to be not just substring but full match. if ((match_len + start_position) != source_len) { return EVAL_FALSE; @@ -365,8 +365,8 @@ PolicyOpcode* OpcodeFactory::MakeBase(OpcodeID opcode_id, ptrdiff_t OpcodeFactory::AllocRelative(void* start, const wchar_t* str, - size_t lenght) { - size_t bytes = lenght * sizeof(wchar_t); + size_t length) { + size_t bytes = length * sizeof(wchar_t); if (memory_size() < bytes) return 0; memory_bottom_ -= bytes; diff --git a/chromium/sandbox/win/src/policy_engine_opcodes.h b/chromium/sandbox/win/src/policy_engine_opcodes.h index 00a7299fb2f..53a5c5a5ce0 100644 --- a/chromium/sandbox/win/src/policy_engine_opcodes.h +++ b/chromium/sandbox/win/src/policy_engine_opcodes.h @@ -224,7 +224,7 @@ class PolicyOpcode { enum StringMatchOptions { CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. - EXACT_LENGHT = 2 // Don't do substring match. Do full string match. + EXACT_LENGTH = 2 // Don't do substring match. Do full string match. }; // Opcodes that do string comparisons take a parameter that is the starting @@ -362,7 +362,7 @@ class OpcodeFactory { // Allocates (and copies) a string (of size length) inside the buffer and // returns the displacement with respect to start. - ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t lenght); + ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t length); // Points to the lowest currently available address of the memory // used to make the opcodes. This pointer increments as opcodes are made. diff --git a/chromium/sandbox/win/src/policy_low_level.cc b/chromium/sandbox/win/src/policy_low_level.cc index beb0e86bcd5..86e2a13dcad 100644 --- a/chromium/sandbox/win/src/policy_low_level.cc +++ b/chromium/sandbox/win/src/policy_low_level.cc @@ -206,7 +206,7 @@ bool PolicyRule::GenStringOpcode(RuleType rule_type, *skip_count = 0; } else { if (last_call) { - match_opts = static_cast<StringMatchOptions>(EXACT_LENGHT | match_opts); + match_opts = static_cast<StringMatchOptions>(EXACT_LENGTH | match_opts); } op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), 0, match_opts, options); diff --git a/chromium/sandbox/win/src/restricted_token_unittest.cc b/chromium/sandbox/win/src/restricted_token_unittest.cc index 170aa5cc336..fad38cc8c20 100644 --- a/chromium/sandbox/win/src/restricted_token_unittest.cc +++ b/chromium/sandbox/win/src/restricted_token_unittest.cc @@ -13,6 +13,8 @@ #include <vector> #include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/security_capabilities.h" #include "sandbox/win/src/sid.h" #include "testing/gtest/include/gtest/gtest.h" @@ -62,6 +64,106 @@ void TestDefaultDalc(bool restricted_required) { ASSERT_FALSE(logon_sid_found); } +bool GetVariableTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS information_class, + std::vector<char>* information) { + DWORD return_length; + if (!::GetTokenInformation(token, information_class, nullptr, 0, + &return_length)) { + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + } + + information->resize(return_length); + return !!::GetTokenInformation(token, information_class, information->data(), + return_length, &return_length); +} + +bool GetVariableTokenInformation(const base::win::ScopedHandle& token, + TOKEN_INFORMATION_CLASS information_class, + std::vector<char>* information) { + return GetVariableTokenInformation(token.Get(), information_class, + information); +} + +void CheckLowBoxToken(const base::win::ScopedHandle& token, + TOKEN_TYPE token_type, + PSECURITY_CAPABILITIES security_capabilities) { + DWORD appcontainer; + DWORD return_length; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenIsAppContainer, + &appcontainer, sizeof(appcontainer), + &return_length)); + ASSERT_TRUE(appcontainer); + TOKEN_TYPE token_type_real; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenType, &token_type_real, + sizeof(token_type_real), &return_length)); + ASSERT_EQ(token_type_real, token_type); + if (token_type == ::TokenImpersonation) { + SECURITY_IMPERSONATION_LEVEL imp_level; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenImpersonationLevel, + &imp_level, sizeof(imp_level), + &return_length)); + ASSERT_EQ(imp_level, ::SecurityImpersonation); + } + + std::vector<char> package_sid_buf; + ASSERT_TRUE(GetVariableTokenInformation(token, ::TokenAppContainerSid, + &package_sid_buf)); + PTOKEN_APPCONTAINER_INFORMATION package_sid = + reinterpret_cast<PTOKEN_APPCONTAINER_INFORMATION>(package_sid_buf.data()); + EXPECT_TRUE(::EqualSid(security_capabilities->AppContainerSid, + package_sid->TokenAppContainer)); + + std::vector<char> capabilities_buf; + ASSERT_TRUE(GetVariableTokenInformation(token, ::TokenCapabilities, + &capabilities_buf)); + PTOKEN_GROUPS capabilities = + reinterpret_cast<PTOKEN_GROUPS>(capabilities_buf.data()); + ASSERT_EQ(capabilities->GroupCount, security_capabilities->CapabilityCount); + for (DWORD index = 0; index < capabilities->GroupCount; ++index) { + EXPECT_EQ(capabilities->Groups[index].Attributes, + security_capabilities->Capabilities[index].Attributes); + EXPECT_TRUE(::EqualSid(capabilities->Groups[index].Sid, + security_capabilities->Capabilities[index].Sid)); + } +} + +// Checks if a sid is in the restricting list of the restricted token. +// Asserts if it's not the case. If count is a positive number, the number of +// elements in the restricting sids list has to be equal. +void CheckRestrictingSid(HANDLE restricted_token, ATL::CSid sid, int count) { + std::vector<char> memory; + ASSERT_TRUE(GetVariableTokenInformation(restricted_token, + ::TokenRestrictedSids, &memory)); + PTOKEN_GROUPS groups = reinterpret_cast<PTOKEN_GROUPS>(memory.data()); + ATL::CTokenGroups atl_groups(*groups); + + if (count >= 0) + ASSERT_EQ(static_cast<unsigned>(count), atl_groups.GetCount()); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + atl_groups.GetSidsAndAttributes(&sids, &attributes); + + bool present = false; + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (sids[i] == sid) { + present = true; + break; + } + } + + ASSERT_TRUE(present); +} + +void CheckRestrictingSid(const ATL::CAccessToken& restricted_token, + ATL::CSid sid, + int count) { + CheckRestrictingSid(restricted_token.GetHandle(), sid, count); +} + } // namespace // Tests the initializatioin with an invalid token handle. @@ -441,40 +543,6 @@ TEST(RestrictedTokenTest, DeletePrivilege) { } } -// Checks if a sid is in the restricting list of the restricted token. -// Asserts if it's not the case. If count is a positive number, the number of -// elements in the restricting sids list has to be equal. -void CheckRestrictingSid(const ATL::CAccessToken& restricted_token, - ATL::CSid sid, - int count) { - DWORD length = 8192; - BYTE* memory = new BYTE[length]; - TOKEN_GROUPS* groups = reinterpret_cast<TOKEN_GROUPS*>(memory); - ASSERT_TRUE(::GetTokenInformation(restricted_token.GetHandle(), - TokenRestrictedSids, groups, length, - &length)); - - ATL::CTokenGroups atl_groups(*groups); - delete[] memory; - - if (count >= 0) - ASSERT_EQ(static_cast<unsigned>(count), atl_groups.GetCount()); - - ATL::CSid::CSidArray sids; - ATL::CAtlArray<DWORD> attributes; - atl_groups.GetSidsAndAttributes(&sids, &attributes); - - bool present = false; - for (unsigned int i = 0; i < sids.GetCount(); ++i) { - if (sids[i] == sid) { - present = true; - break; - } - } - - ASSERT_TRUE(present); -} - // Tests the method AddRestrictingSid. TEST(RestrictedTokenTest, AddRestrictingSid) { RestrictedToken token; @@ -579,16 +647,11 @@ TEST(RestrictedTokenTest, AddMultipleRestrictingSids) { ATL::CSid session; restricted_token.GetLogonSid(&session); - DWORD length = 8192; - BYTE* memory = new BYTE[length]; - TOKEN_GROUPS* groups = reinterpret_cast<TOKEN_GROUPS*>(memory); - ASSERT_TRUE(::GetTokenInformation(restricted_token.GetHandle(), - TokenRestrictedSids, groups, length, - &length)); - + std::vector<char> memory; + ASSERT_TRUE(GetVariableTokenInformation(restricted_token.GetHandle(), + ::TokenRestrictedSids, &memory)); + PTOKEN_GROUPS groups = reinterpret_cast<PTOKEN_GROUPS>(memory.data()); ATL::CTokenGroups atl_groups(*groups); - delete[] memory; - ASSERT_EQ(3u, atl_groups.GetCount()); } @@ -651,4 +714,71 @@ TEST(RestrictedTokenTest, LockdownDefaultDaclNoLogonSid) { ASSERT_EQ(DWORD{ERROR_SUCCESS}, token.GetRestrictedToken(&handle)); } +TEST(RestrictedTokenTest, LowBoxToken) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + base::win::ScopedHandle token; + + Sid package_sid = Sid::FromSddlString(L"S-1-15-2-1-2-3-4-5-6-7"); + SecurityCapabilities caps_no_capabilities(package_sid); + + ASSERT_EQ(DWORD{ERROR_INVALID_PARAMETER}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_no_capabilities, nullptr, + 0, nullptr)); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_no_capabilities, nullptr, + 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_no_capabilities); + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, IMPERSONATION, &caps_no_capabilities, + nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenImpersonation, &caps_no_capabilities); + + std::vector<Sid> capabilities; + capabilities.push_back(Sid::FromKnownCapability(kInternetClient)); + capabilities.push_back(Sid::FromKnownCapability(kPrivateNetworkClientServer)); + SecurityCapabilities caps_with_capabilities(package_sid, capabilities); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_with_capabilities, + nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_with_capabilities); + + RestrictedToken restricted_token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, restricted_token.Init(nullptr)); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + restricted_token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + restricted_token.GetRestrictedToken(&token_handle)); + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(token_handle.Get(), PRIMARY, + &caps_with_capabilities, nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_with_capabilities); + CheckRestrictingSid(token.Get(), ATL::Sids::World(), 1); + + SecurityCapabilities caps_for_handles( + Sid::FromSddlString(L"S-1-15-2-1-2-3-4-5-6-8")); + base::win::ScopedHandle object_handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxObjectDirectory(caps_for_handles.AppContainerSid, true, + &object_handle)); + HANDLE saved_handles[] = {object_handle.Get()}; + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(token_handle.Get(), PRIMARY, &caps_for_handles, + saved_handles, 1, &token)); + ASSERT_TRUE(token.IsValid()); + object_handle.Close(); + ASSERT_FALSE(object_handle.IsValid()); + ASSERT_EQ(DWORD{ERROR_ALREADY_EXISTS}, + CreateLowBoxObjectDirectory(caps_for_handles.AppContainerSid, false, + &object_handle)); +} + } // namespace sandbox diff --git a/chromium/sandbox/win/src/restricted_token_utils.cc b/chromium/sandbox/win/src/restricted_token_utils.cc index 8a8e95812f7..104ae07caab 100644 --- a/chromium/sandbox/win/src/restricted_token_utils.cc +++ b/chromium/sandbox/win/src/restricted_token_utils.cc @@ -7,15 +7,19 @@ #include <aclapi.h> #include <sddl.h> +#include <memory> #include <vector> #include "base/logging.h" +#include "base/strings/stringprintf.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" #include "sandbox/win/src/job.h" #include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/sandbox_utils.h" #include "sandbox/win/src/security_level.h" #include "sandbox/win/src/sid.h" +#include "sandbox/win/src/win_utils.h" namespace sandbox { @@ -294,4 +298,103 @@ DWORD HardenProcessIntegrityLevelPolicy() { return HardenTokenIntegrityLevelPolicy(token.Get()); } +DWORD CreateLowBoxToken(HANDLE base_token, + TokenType token_type, + PSECURITY_CAPABILITIES security_capabilities, + PHANDLE saved_handles, + DWORD saved_handles_count, + base::win::ScopedHandle* token) { + NtCreateLowBoxToken CreateLowBoxToken = nullptr; + ResolveNTFunctionPtr("NtCreateLowBoxToken", &CreateLowBoxToken); + + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return ERROR_CALL_NOT_IMPLEMENTED; + + if (token_type != PRIMARY && token_type != IMPERSONATION) + return ERROR_INVALID_PARAMETER; + + if (!token) + return ERROR_INVALID_PARAMETER; + + base::win::ScopedHandle base_token_handle; + if (!base_token) { + HANDLE process_token = nullptr; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &process_token)) { + return ::GetLastError(); + } + base_token_handle.Set(process_token); + base_token = process_token; + } + OBJECT_ATTRIBUTES obj_attr; + InitializeObjectAttributes(&obj_attr, nullptr, 0, nullptr, nullptr); + HANDLE token_lowbox = nullptr; + + NTSTATUS status = CreateLowBoxToken( + &token_lowbox, base_token, TOKEN_ALL_ACCESS, &obj_attr, + security_capabilities->AppContainerSid, + security_capabilities->CapabilityCount, + security_capabilities->Capabilities, saved_handles_count, saved_handles); + if (!NT_SUCCESS(status)) + return GetLastErrorFromNtStatus(status); + + base::win::ScopedHandle token_lowbox_handle(token_lowbox); + DCHECK(token_lowbox_handle.IsValid()); + + // Default from NtCreateLowBoxToken is a Primary token. + if (token_type == PRIMARY) { + token->Set(token_lowbox_handle.Take()); + return ERROR_SUCCESS; + } + + HANDLE dup_handle = nullptr; + if (!::DuplicateTokenEx(token_lowbox_handle.Get(), TOKEN_ALL_ACCESS, nullptr, + ::SecurityImpersonation, ::TokenImpersonation, + &dup_handle)) { + return ::GetLastError(); + } + + token->Set(dup_handle); + + return ERROR_SUCCESS; +} + +DWORD CreateLowBoxObjectDirectory(PSID lowbox_sid, + bool open_directory, + base::win::ScopedHandle* directory) { + DWORD session_id = 0; + if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) + return ::GetLastError(); + + LPWSTR sid_string = nullptr; + if (!::ConvertSidToStringSid(lowbox_sid, &sid_string)) + return ::GetLastError(); + + std::unique_ptr<wchar_t, LocalFreeDeleter> sid_string_ptr(sid_string); + base::string16 directory_path = base::StringPrintf( + L"\\Sessions\\%d\\AppContainerNamedObjects\\%ls", session_id, sid_string); + + NtCreateDirectoryObjectFunction CreateObjectDirectory = nullptr; + ResolveNTFunctionPtr("NtCreateDirectoryObject", &CreateObjectDirectory); + + OBJECT_ATTRIBUTES obj_attr; + UNICODE_STRING obj_name; + DWORD attributes = OBJ_CASE_INSENSITIVE; + if (open_directory) + attributes |= OBJ_OPENIF; + + sandbox::InitObjectAttribs(directory_path, attributes, nullptr, &obj_attr, + &obj_name, nullptr); + + HANDLE handle = nullptr; + NTSTATUS status = + CreateObjectDirectory(&handle, DIRECTORY_ALL_ACCESS, &obj_attr); + + if (!NT_SUCCESS(status)) + return GetLastErrorFromNtStatus(status); + directory->Set(handle); + + return ERROR_SUCCESS; +} + } // namespace sandbox diff --git a/chromium/sandbox/win/src/restricted_token_utils.h b/chromium/sandbox/win/src/restricted_token_utils.h index f181557742b..10815b00b89 100644 --- a/chromium/sandbox/win/src/restricted_token_utils.h +++ b/chromium/sandbox/win/src/restricted_token_utils.h @@ -72,6 +72,31 @@ DWORD HardenTokenIntegrityLevelPolicy(HANDLE token); // holes. DWORD HardenProcessIntegrityLevelPolicy(); +// Create a lowbox token. This is not valid prior to Windows 8. +// |base_token| a base token to derive the lowbox token from. Can be nullptr. +// |security_capabilities| list of LowBox capabilities to use when creating the +// token. +// |token| is the output value containing the handle of the newly created +// restricted token. +// |lockdown_default_dacl| indicates the token's default DACL should be locked +// down to restrict what other process can open kernel resources created while +// running under the token. +DWORD CreateLowBoxToken(HANDLE base_token, + TokenType token_type, + PSECURITY_CAPABILITIES security_capabilities, + PHANDLE saved_handles, + DWORD saved_handles_count, + base::win::ScopedHandle* token); + +// Create a lowbox object directory token. This is not valid prior to Windows 8. +// This returns the Win32 error code from the operation. +// |lowbox_sid| the SID for the LowBox. +// |open_directory| open the directory if it already exists. +// |directory| is the output value for the directory object. +DWORD CreateLowBoxObjectDirectory(PSID lowbox_sid, + bool open_directory, + base::win::ScopedHandle* directory); + } // namespace sandbox #endif // SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ diff --git a/chromium/sandbox/win/src/sandbox_policy.h b/chromium/sandbox/win/src/sandbox_policy.h index dac9e8c297d..29f8705bff7 100644 --- a/chromium/sandbox/win/src/sandbox_policy.h +++ b/chromium/sandbox/win/src/sandbox_policy.h @@ -172,9 +172,6 @@ class TargetPolicy { // than the current level, the sandbox will fail to start. virtual ResultCode SetDelayedIntegrityLevel(IntegrityLevel level) = 0; - // Sets a capability to be enabled for the sandboxed process' AppContainer. - virtual ResultCode SetCapability(const wchar_t* sid) = 0; - // Sets the LowBox token for sandboxed process. This is mutually exclusive // with SetAppContainer method. virtual ResultCode SetLowBox(const wchar_t* sid) = 0; diff --git a/chromium/sandbox/win/src/sandbox_policy_base.cc b/chromium/sandbox/win/src/sandbox_policy_base.cc index 5ded3e12a22..867a549651b 100644 --- a/chromium/sandbox/win/src/sandbox_policy_base.cc +++ b/chromium/sandbox/win/src/sandbox_policy_base.cc @@ -28,6 +28,7 @@ #include "sandbox/win/src/restricted_token_utils.h" #include "sandbox/win/src/sandbox_policy.h" #include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/security_capabilities.h" #include "sandbox/win/src/sync_policy.h" #include "sandbox/win/src/target_process.h" #include "sandbox/win/src/top_level_dispatcher.h" @@ -64,39 +65,6 @@ bool IsInheritableHandle(HANDLE handle) { return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; } -HANDLE CreateLowBoxObjectDirectory(PSID lowbox_sid) { - DWORD session_id = 0; - if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) - return nullptr; - - LPWSTR sid_string = nullptr; - if (!::ConvertSidToStringSid(lowbox_sid, &sid_string)) - return nullptr; - - base::string16 directory_path = - base::StringPrintf(L"\\Sessions\\%d\\AppContainerNamedObjects\\%ls", - session_id, sid_string) - .c_str(); - ::LocalFree(sid_string); - - NtCreateDirectoryObjectFunction CreateObjectDirectory = nullptr; - ResolveNTFunctionPtr("NtCreateDirectoryObject", &CreateObjectDirectory); - - OBJECT_ATTRIBUTES obj_attr; - UNICODE_STRING obj_name; - sandbox::InitObjectAttribs(directory_path, OBJ_CASE_INSENSITIVE | OBJ_OPENIF, - nullptr, &obj_attr, &obj_name, nullptr); - - HANDLE handle = nullptr; - NTSTATUS status = - CreateObjectDirectory(&handle, DIRECTORY_ALL_ACCESS, &obj_attr); - - if (!NT_SUCCESS(status)) - return nullptr; - - return handle; -} - } // namespace namespace sandbox { @@ -313,11 +281,6 @@ ResultCode PolicyBase::SetDelayedIntegrityLevel( return SBOX_ALL_OK; } -ResultCode PolicyBase::SetCapability(const wchar_t* sid) { - capabilities_.push_back(sid); - return SBOX_ALL_OK; -} - ResultCode PolicyBase::SetLowBox(const wchar_t* sid) { if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) return SBOX_ERROR_UNSUPPORTED; @@ -475,29 +438,23 @@ ResultCode PolicyBase::MakeTokens(base::win::ScopedHandle* initial, } if (lowbox_sid_) { - NtCreateLowBoxToken CreateLowBoxToken = nullptr; - ResolveNTFunctionPtr("NtCreateLowBoxToken", &CreateLowBoxToken); - OBJECT_ATTRIBUTES obj_attr; - InitializeObjectAttributes(&obj_attr, nullptr, 0, nullptr, nullptr); - HANDLE token_lowbox = nullptr; - - if (!lowbox_directory_.IsValid()) - lowbox_directory_.Set(CreateLowBoxObjectDirectory(lowbox_sid_)); - DCHECK(lowbox_directory_.IsValid()); + if (!lowbox_directory_.IsValid()) { + result = + CreateLowBoxObjectDirectory(lowbox_sid_, true, &lowbox_directory_); + DCHECK(result == ERROR_SUCCESS); + } // The order of handles isn't important in the CreateLowBoxToken call. // The kernel will maintain a reference to the object directory handle. HANDLE saved_handles[1] = {lowbox_directory_.Get()}; DWORD saved_handles_count = lowbox_directory_.IsValid() ? 1 : 0; - NTSTATUS status = CreateLowBoxToken( - &token_lowbox, lockdown->Get(), TOKEN_ALL_ACCESS, &obj_attr, - lowbox_sid_, 0, nullptr, saved_handles_count, saved_handles); - if (!NT_SUCCESS(status)) + Sid package_sid(lowbox_sid_); + SecurityCapabilities caps(package_sid); + if (CreateLowBoxToken(lockdown->Get(), PRIMARY, &caps, saved_handles, + saved_handles_count, lowbox) != ERROR_SUCCESS) { return SBOX_ERROR_GENERIC; - - DCHECK(token_lowbox); - lowbox->Set(token_lowbox); + } } // Create the 'better' token. We use this token as the one that the main diff --git a/chromium/sandbox/win/src/sandbox_policy_base.h b/chromium/sandbox/win/src/sandbox_policy_base.h index 50ee61845db..61a66419ada 100644 --- a/chromium/sandbox/win/src/sandbox_policy_base.h +++ b/chromium/sandbox/win/src/sandbox_policy_base.h @@ -53,7 +53,6 @@ class PolicyBase final : public TargetPolicy { ResultCode SetIntegrityLevel(IntegrityLevel integrity_level) override; IntegrityLevel GetIntegrityLevel() const override; ResultCode SetDelayedIntegrityLevel(IntegrityLevel integrity_level) override; - ResultCode SetCapability(const wchar_t* sid) override; ResultCode SetLowBox(const wchar_t* sid) override; ResultCode SetProcessMitigations(MitigationFlags flags) override; MitigationFlags GetProcessMitigations() override; @@ -153,7 +152,6 @@ class PolicyBase final : public TargetPolicy { // target process. A null set means we need to close all handles of the // given type. HandleCloser handle_closer_; - std::vector<base::string16> capabilities_; PSID lowbox_sid_; base::win::ScopedHandle lowbox_directory_; std::unique_ptr<Dispatcher> dispatcher_; diff --git a/chromium/sandbox/win/src/security_capabilities.cc b/chromium/sandbox/win/src/security_capabilities.cc new file mode 100644 index 00000000000..0df446b5bee --- /dev/null +++ b/chromium/sandbox/win/src/security_capabilities.cc @@ -0,0 +1,33 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/security_capabilities.h" +#include "base/numerics/safe_conversions.h" + +namespace sandbox { + +SecurityCapabilities::SecurityCapabilities(const Sid& package_sid, + const std::vector<Sid>& capabilities) + : SECURITY_CAPABILITIES(), + capabilities_(capabilities), + package_sid_(package_sid) { + AppContainerSid = package_sid_.GetPSID(); + if (capabilities_.empty()) + return; + + capability_sids_.resize(capabilities_.size()); + for (size_t index = 0; index < capabilities_.size(); ++index) { + capability_sids_[index].Sid = capabilities_[index].GetPSID(); + capability_sids_[index].Attributes = SE_GROUP_ENABLED; + } + CapabilityCount = base::checked_cast<DWORD>(capability_sids_.size()); + Capabilities = capability_sids_.data(); +} + +SecurityCapabilities::SecurityCapabilities(const Sid& package_sid) + : SecurityCapabilities(package_sid, std::vector<Sid>()) {} + +SecurityCapabilities::~SecurityCapabilities() {} + +} // namespace sandbox diff --git a/chromium/sandbox/win/src/security_capabilities.h b/chromium/sandbox/win/src/security_capabilities.h new file mode 100644 index 00000000000..7a66e59743a --- /dev/null +++ b/chromium/sandbox/win/src/security_capabilities.h @@ -0,0 +1,34 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SECURITY_CAPABILITIES_H_ +#define SANDBOX_SRC_SECURITY_CAPABILITIES_H_ + +#include <windows.h> + +#include <vector> + +#include "base/macros.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class SecurityCapabilities final : public SECURITY_CAPABILITIES { + public: + explicit SecurityCapabilities(const Sid& package_sid); + SecurityCapabilities(const Sid& package_sid, + const std::vector<Sid>& capabilities); + ~SecurityCapabilities(); + + private: + std::vector<Sid> capabilities_; + std::vector<SID_AND_ATTRIBUTES> capability_sids_; + Sid package_sid_; + + DISALLOW_COPY_AND_ASSIGN(SecurityCapabilities); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SECURITY_CAPABILITIES_H_
\ No newline at end of file diff --git a/chromium/sandbox/win/src/sid.cc b/chromium/sandbox/win/src/sid.cc index 5c7754817d8..3507ad7c346 100644 --- a/chromium/sandbox/win/src/sid.cc +++ b/chromium/sandbox/win/src/sid.cc @@ -16,49 +16,36 @@ namespace sandbox { namespace { -typedef decltype( - ::DeriveCapabilitySidsFromName) DeriveCapabilitySidsFromNameFunc; - -class SidArray { - public: - SidArray() : count_(0), sids_(nullptr) {} - - ~SidArray() { - if (sids_) { - for (size_t index = 0; index < count_; ++index) { - ::LocalFree(sids_[index]); - } - ::LocalFree(sids_); - } - } - - DWORD count() { return count_; } - PSID* sids() { return sids_; } - PDWORD count_ptr() { return &count_; } - PSID** sids_ptr() { return &sids_; } - - private: - DWORD count_; - PSID* sids_; -}; - -const wchar_t* WellKnownCapabilityToName(WellKnownCapabilities capability) { +DWORD WellKnownCapabilityToRid(WellKnownCapabilities capability) { switch (capability) { case kInternetClient: - return L"internetClient"; + return SECURITY_CAPABILITY_INTERNET_CLIENT; case kInternetClientServer: - return L"internetClientServer"; - case kRegistryRead: - return L"registryRead"; - case kLpacCryptoServices: - return L"lpacCryptoServices"; - case kEnterpriseAuthentication: - return L"enterpriseAuthentication"; + return SECURITY_CAPABILITY_INTERNET_CLIENT_SERVER; case kPrivateNetworkClientServer: - return L"privateNetworkClientServer"; + return SECURITY_CAPABILITY_PRIVATE_NETWORK_CLIENT_SERVER; + case kPicturesLibrary: + return SECURITY_CAPABILITY_PICTURES_LIBRARY; + case kVideosLibrary: + return SECURITY_CAPABILITY_VIDEOS_LIBRARY; + case kMusicLibrary: + return SECURITY_CAPABILITY_MUSIC_LIBRARY; + case kDocumentsLibrary: + return SECURITY_CAPABILITY_DOCUMENTS_LIBRARY; + case kEnterpriseAuthentication: + return SECURITY_CAPABILITY_ENTERPRISE_AUTHENTICATION; + case kSharedUserCertificates: + return SECURITY_CAPABILITY_SHARED_USER_CERTIFICATES; + case kRemovableStorage: + return SECURITY_CAPABILITY_REMOVABLE_STORAGE; + case kAppointments: + return SECURITY_CAPABILITY_APPOINTMENTS; + case kContacts: + return SECURITY_CAPABILITY_CONTACTS; default: - return nullptr; + break; } + return 0; } } // namespace @@ -81,33 +68,39 @@ Sid::Sid(WELL_KNOWN_SID_TYPE type) { } Sid Sid::FromKnownCapability(WellKnownCapabilities capability) { - return Sid::FromNamedCapability(WellKnownCapabilityToName(capability)); + DWORD capability_rid = WellKnownCapabilityToRid(capability); + if (!capability_rid) + return Sid(); + SID_IDENTIFIER_AUTHORITY capability_authority = { + SECURITY_APP_PACKAGE_AUTHORITY}; + DWORD sub_authorities[] = {SECURITY_CAPABILITY_BASE_RID, capability_rid}; + return FromSubAuthorities(&capability_authority, 2, sub_authorities); } Sid Sid::FromNamedCapability(const wchar_t* capability_name) { - DeriveCapabilitySidsFromNameFunc* derive_capablity_sids = - (DeriveCapabilitySidsFromNameFunc*)GetProcAddress( - GetModuleHandle(L"kernelbase"), "DeriveCapabilitySidsFromName"); - if (!derive_capablity_sids) + RtlDeriveCapabilitySidsFromNameFunction derive_capability_sids = nullptr; + ResolveNTFunctionPtr("RtlDeriveCapabilitySidsFromName", + &derive_capability_sids); + RtlInitUnicodeStringFunction init_unicode_string = nullptr; + ResolveNTFunctionPtr("RtlInitUnicodeString", &init_unicode_string); + + if (!derive_capability_sids || !init_unicode_string) return Sid(); if (!capability_name || ::wcslen(capability_name) == 0) return Sid(); - SidArray capability_group_sids; - SidArray capability_sids; - - if (!derive_capablity_sids(capability_name, capability_group_sids.sids_ptr(), - capability_group_sids.count_ptr(), - capability_sids.sids_ptr(), - capability_sids.count_ptr())) { - return Sid(); - } + UNICODE_STRING name = {}; + init_unicode_string(&name, capability_name); + Sid capability_sid; + Sid group_sid; - if (capability_sids.count() < 1) + NTSTATUS status = + derive_capability_sids(&name, group_sid.sid_, capability_sid.sid_); + if (!NT_SUCCESS(status)) return Sid(); - return Sid(capability_sids.sids()[0]); + return capability_sid; } Sid Sid::FromSddlString(const wchar_t* sddl_sid) { diff --git a/chromium/sandbox/win/src/sid.h b/chromium/sandbox/win/src/sid.h index acaaa744ba7..4ef22e54602 100644 --- a/chromium/sandbox/win/src/sid.h +++ b/chromium/sandbox/win/src/sid.h @@ -11,13 +11,20 @@ namespace sandbox { +// Known capabilities defined in Windows 8. enum WellKnownCapabilities { kInternetClient, kInternetClientServer, - kRegistryRead, - kLpacCryptoServices, - kEnterpriseAuthentication, kPrivateNetworkClientServer, + kPicturesLibrary, + kVideosLibrary, + kMusicLibrary, + kDocumentsLibrary, + kEnterpriseAuthentication, + kSharedUserCertificates, + kRemovableStorage, + kAppointments, + kContacts, kMaxWellKnownCapability }; @@ -34,7 +41,8 @@ class Sid { // Create a Sid from an AppContainer capability name. The name can be // completely arbitrary. static Sid FromNamedCapability(const wchar_t* capability_name); - // Create a Sid from a known capability enumeration value. + // Create a Sid from a known capability enumeration value. The Sids + // match with the list defined in Windows 8. static Sid FromKnownCapability(WellKnownCapabilities capability); // Create a Sid from a SDDL format string, such as S-1-1-0. static Sid FromSddlString(const wchar_t* sddl_sid); diff --git a/chromium/sandbox/win/src/sid_unittest.cc b/chromium/sandbox/win/src/sid_unittest.cc index 0f4fc9bb07f..5b01d592acc 100644 --- a/chromium/sandbox/win/src/sid_unittest.cc +++ b/chromium/sandbox/win/src/sid_unittest.cc @@ -35,10 +35,14 @@ bool EqualSid(const Sid& sid, const wchar_t* sddl_sid) { return equal; } -struct CapabilityTestEntry { - WellKnownCapabilities capability_; - const wchar_t* capability_name_; - const wchar_t* sddl_sid_; +struct KnownCapabilityTestEntry { + WellKnownCapabilities capability; + const wchar_t* sddl_sid; +}; + +struct NamedCapabilityTestEntry { + const wchar_t* capability_name; + const wchar_t* sddl_sid; }; } // namespace @@ -95,22 +99,38 @@ TEST(SidTest, GetPSID) { ASSERT_TRUE(EqualSid(Sid(::WinProxySid), ATL::Sids::Proxy())); } -TEST(SidTest, AppContainer) { - const CapabilityTestEntry capabilities[] = { - {kInternetClient, L"internetClient", L"S-1-15-3-1"}, - {kInternetClientServer, L"internetClientServer", L"S-1-15-3-2"}, - {kRegistryRead, L"registryRead", - L"S-1-15-3-1024-1065365936-1281604716-3511738428-" - "1654721687-432734479-3232135806-4053264122-3456934681"}, - {kLpacCryptoServices, L"lpacCryptoServices", - L"S-1-15-3-1024-3203351429-2120443784-2872670797-" - "1918958302-2829055647-4275794519-765664414-2751773334"}, - {kEnterpriseAuthentication, L"enterpriseAuthentication", L"S-1-15-3-8"}, - {kPrivateNetworkClientServer, L"privateNetworkClientServer", - L"S-1-15-3-3"}}; +TEST(SidTest, KnownCapability) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + Sid sid_invalid_well_known = + Sid::FromKnownCapability(kMaxWellKnownCapability); + EXPECT_FALSE(sid_invalid_well_known.IsValid()); + + const KnownCapabilityTestEntry capabilities[] = { + {kInternetClient, L"S-1-15-3-1"}, + {kInternetClientServer, L"S-1-15-3-2"}, + {kPrivateNetworkClientServer, L"S-1-15-3-3"}, + {kPicturesLibrary, L"S-1-15-3-4"}, + {kVideosLibrary, L"S-1-15-3-5"}, + {kMusicLibrary, L"S-1-15-3-6"}, + {kDocumentsLibrary, L"S-1-15-3-7"}, + {kEnterpriseAuthentication, L"S-1-15-3-8"}, + {kSharedUserCertificates, L"S-1-15-3-9"}, + {kRemovableStorage, L"S-1-15-3-10"}, + {kAppointments, L"S-1-15-3-11"}, + {kContacts, L"S-1-15-3-12"}, + }; - // No support for AppContainer less than Win10 RS2. - if (base::win::GetVersion() < base::win::VERSION_WIN10_RS2) + for (auto capability : capabilities) { + EXPECT_TRUE(EqualSid(Sid::FromKnownCapability(capability.capability), + capability.sddl_sid)) + << "Known Capability: " << capability.sddl_sid; + } +} + +TEST(SidTest, NamedCapability) { + if (base::win::GetVersion() < base::win::VERSION_WIN10) return; Sid sid_nullptr = Sid::FromNamedCapability(nullptr); @@ -119,18 +139,22 @@ TEST(SidTest, AppContainer) { Sid sid_empty = Sid::FromNamedCapability(L""); EXPECT_FALSE(sid_empty.IsValid()); - WellKnownCapabilities invalid_well_known = - static_cast<WellKnownCapabilities>(kMaxWellKnownCapability + 1); - Sid sid_invalid_well_known = Sid::FromKnownCapability(invalid_well_known); - EXPECT_FALSE(sid_invalid_well_known.IsValid()); + const NamedCapabilityTestEntry capabilities[] = { + {L"internetClient", L"S-1-15-3-1"}, + {L"internetClientServer", L"S-1-15-3-2"}, + {L"registryRead", + L"S-1-15-3-1024-1065365936-1281604716-3511738428-" + "1654721687-432734479-3232135806-4053264122-3456934681"}, + {L"lpacCryptoServices", + L"S-1-15-3-1024-3203351429-2120443784-2872670797-" + "1918958302-2829055647-4275794519-765664414-2751773334"}, + {L"enterpriseAuthentication", L"S-1-15-3-8"}, + {L"privateNetworkClientServer", L"S-1-15-3-3"}}; for (auto capability : capabilities) { - EXPECT_TRUE(EqualSid(Sid::FromNamedCapability(capability.capability_name_), - capability.sddl_sid_)) - << "Named Capability: " << capability.sddl_sid_; - EXPECT_TRUE(EqualSid(Sid::FromKnownCapability(capability.capability_), - capability.sddl_sid_)) - << "Known Capability: " << capability.sddl_sid_; + EXPECT_TRUE(EqualSid(Sid::FromNamedCapability(capability.capability_name), + capability.sddl_sid)) + << "Named Capability: " << capability.sddl_sid; } } |