Commit b558ba51 authored by Chris Palmer's avatar Chris Palmer Committed by Commit Bot

[sandbox] Remove address space limits on Linux.

Unfortunately, it does not appear possible to set RLIMIT_AS such that it will
both (a) be high enough to support V8's and WebAssembly's address space
requirements while also (b) being low enough to mitigate exploits using integer
overflows that require large allocations, heap spray, or other memory-hungry
attack modes.

Bug: 800348
TBR: jln
Change-Id: I4ec234709a4e49abde1c22f9c63a87bac107b8a0
Reviewed-on: https://chromium-review.googlesource.com/1097553
Commit-Queue: Chris Palmer <palmer@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarJorge Lucangeli Obes <jorgelo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#568073}
parent 715eedd0
...@@ -78,7 +78,8 @@ void RestrictAddressSpaceUsage() { ...@@ -78,7 +78,8 @@ void RestrictAddressSpaceUsage() {
// This could almost certainly be set to zero. GLibc's allocator and others // This could almost certainly be set to zero. GLibc's allocator and others
// would fall-back to mmap if brk() fails. // would fall-back to mmap if brk() fails.
const rlim_t kNewDataSegmentMaxSize = std::numeric_limits<int>::max(); const rlim_t kNewDataSegmentMaxSize = std::numeric_limits<int>::max();
CHECK(sandbox::ResourceLimits::Lower(RLIMIT_DATA, kNewDataSegmentMaxSize)); CHECK_EQ(0,
sandbox::ResourceLimits::Lower(RLIMIT_DATA, kNewDataSegmentMaxSize));
#if defined(ARCH_CPU_64_BITS) #if defined(ARCH_CPU_64_BITS)
// NaCl's x86-64 sandbox allocated 88GB address of space during startup: // NaCl's x86-64 sandbox allocated 88GB address of space during startup:
...@@ -94,7 +95,7 @@ void RestrictAddressSpaceUsage() { ...@@ -94,7 +95,7 @@ void RestrictAddressSpaceUsage() {
// bits when running under 64 bits kernels. Set a limit in case this happens. // bits when running under 64 bits kernels. Set a limit in case this happens.
const rlim_t kNewAddressSpaceLimit = std::numeric_limits<uint32_t>::max(); const rlim_t kNewAddressSpaceLimit = std::numeric_limits<uint32_t>::max();
#endif #endif
CHECK(sandbox::ResourceLimits::Lower(RLIMIT_AS, kNewAddressSpaceLimit)); CHECK_EQ(0, sandbox::ResourceLimits::Lower(RLIMIT_AS, kNewAddressSpaceLimit));
} }
} // namespace } // namespace
......
...@@ -8,40 +8,42 @@ ...@@ -8,40 +8,42 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/time.h> #include <sys/time.h>
#include <errno.h>
#include <algorithm> #include <algorithm>
namespace sandbox { namespace sandbox {
// static // static
bool ResourceLimits::Lower(int resource, rlim_t limit) { int ResourceLimits::Lower(int resource, rlim_t limit) {
return LowerSoftAndHardLimits(resource, limit, limit); return LowerSoftAndHardLimits(resource, limit, limit);
} }
// static // static
bool ResourceLimits::LowerSoftAndHardLimits(int resource, int ResourceLimits::LowerSoftAndHardLimits(int resource,
rlim_t soft_limit, rlim_t soft_limit,
rlim_t hard_limit) { rlim_t hard_limit) {
struct rlimit old_rlimit; struct rlimit old_rlimit;
if (getrlimit(resource, &old_rlimit)) if (getrlimit(resource, &old_rlimit))
return false; return errno;
// Make sure we don't raise the existing limit. // Make sure we don't raise the existing limit.
const struct rlimit new_rlimit = {std::min(old_rlimit.rlim_cur, soft_limit), const struct rlimit new_rlimit = {std::min(old_rlimit.rlim_cur, soft_limit),
std::min(old_rlimit.rlim_max, hard_limit)}; std::min(old_rlimit.rlim_max, hard_limit)};
return setrlimit(resource, &new_rlimit) == 0; return setrlimit(resource, &new_rlimit) ? errno : 0;
} }
// static // static
bool ResourceLimits::AdjustCurrent(int resource, long long int change) { int ResourceLimits::AdjustCurrent(int resource, long long int change) {
struct rlimit old_rlimit; struct rlimit old_rlimit;
if (getrlimit(resource, &old_rlimit)) if (getrlimit(resource, &old_rlimit))
return false; return errno;
base::CheckedNumeric<rlim_t> checked_limit = old_rlimit.rlim_cur; base::CheckedNumeric<rlim_t> checked_limit = old_rlimit.rlim_cur;
checked_limit += change; checked_limit += change;
const rlim_t new_limit = checked_limit.ValueOrDefault(old_rlimit.rlim_max); const rlim_t new_limit = checked_limit.ValueOrDefault(old_rlimit.rlim_max);
const struct rlimit new_rlimit = {std::min(new_limit, old_rlimit.rlim_max), const struct rlimit new_rlimit = {std::min(new_limit, old_rlimit.rlim_max),
old_rlimit.rlim_max}; old_rlimit.rlim_max};
// setrlimit will fail if limit > old_rlimit.rlim_max. // setrlimit will fail if limit > old_rlimit.rlim_max.
return setrlimit(resource, &new_rlimit) == 0; return setrlimit(resource, &new_rlimit) ? errno : 0;
} }
} // namespace sandbox } // namespace sandbox
...@@ -17,19 +17,22 @@ namespace sandbox { ...@@ -17,19 +17,22 @@ namespace sandbox {
class SANDBOX_EXPORT ResourceLimits { class SANDBOX_EXPORT ResourceLimits {
public: public:
// Lower the soft and hard limit of |resource| to |limit|. If the current // Lower the soft and hard limit of |resource| to |limit|. If the current
// limit is lower than |limit|, keep it. // limit is lower than |limit|, keep it. Returns 0 on success, or |errno|.
static bool Lower(int resource, rlim_t limit) WARN_UNUSED_RESULT; static int Lower(int resource, rlim_t limit) WARN_UNUSED_RESULT;
// Lower the soft limit of |resource| to |limit| and the hard limit to
// |max|. If the current limit is lower than the new values, keep it. // Lower the soft limit of |resource| to |limit| and the hard limit to |max|.
static bool LowerSoftAndHardLimits(int resource, // If the current limit is lower than the new values, keep it. Returns 0 on
rlim_t soft_limit, // success, or |errno|.
rlim_t hard_limit) WARN_UNUSED_RESULT; static int LowerSoftAndHardLimits(int resource,
rlim_t soft_limit,
rlim_t hard_limit) WARN_UNUSED_RESULT;
// Change soft limit of |resource| to the current limit plus |change|. If // Change soft limit of |resource| to the current limit plus |change|. If
// |resource + change| is larger than the hard limit, the soft limit is set to // |resource + change| is larger than the hard limit, the soft limit is set to
// be the same as the hard limit. Fails and returns false if the underlying // be the same as the hard limit. Fails and returns false if the underlying
// call to setrlimit fails. // call to setrlimit fails. Returns 0 on success, or |errno|.
static bool AdjustCurrent(int resource, static int AdjustCurrent(int resource,
long long int change) WARN_UNUSED_RESULT; long long int change) WARN_UNUSED_RESULT;
private: private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceLimits); DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceLimits);
......
...@@ -29,7 +29,7 @@ namespace { ...@@ -29,7 +29,7 @@ namespace {
// all ASAN builds. // all ASAN builds.
SANDBOX_TEST(ResourceLimits, MAYBE_NoFork) { SANDBOX_TEST(ResourceLimits, MAYBE_NoFork) {
// Make sure that fork will fail with EAGAIN. // Make sure that fork will fail with EAGAIN.
SANDBOX_ASSERT(ResourceLimits::Lower(RLIMIT_NPROC, 0)); SANDBOX_ASSERT(ResourceLimits::Lower(RLIMIT_NPROC, 0) == 0);
errno = 0; errno = 0;
pid_t pid = fork(); pid_t pid = fork();
// Reap any child if fork succeeded. // Reap any child if fork succeeded.
......
...@@ -388,12 +388,19 @@ bool SandboxLinux::InitializeSandbox(SandboxType sandbox_type, ...@@ -388,12 +388,19 @@ bool SandboxLinux::InitializeSandbox(SandboxType sandbox_type,
if (options.engage_namespace_sandbox) if (options.engage_namespace_sandbox)
EngageNamespaceSandbox(false /* from_zygote */); EngageNamespaceSandbox(false /* from_zygote */);
DCHECK(!HasOpenDirectories()) CHECK(!HasOpenDirectories())
<< "InitializeSandbox() called after unexpected directories have been " << "InitializeSandbox() called after unexpected directories have been "
<< "opened. This breaks the security of the setuid sandbox."; << "opened. This breaks the security of the setuid sandbox.";
// Attempt to limit the future size of the address space of the process. // Attempt to limit the future size of the address space of the process.
LimitAddressSpace(process_type, options); int error = 0;
const bool limited_as = LimitAddressSpace(&error);
if (error) {
// Restore errno. Internally to |LimitAddressSpace|, the errno due to
// setrlimit may be lost.
errno = error;
PCHECK(limited_as);
}
return StartSeccompBPF(sandbox_type, std::move(hook), options); return StartSeccompBPF(sandbox_type, std::move(hook), options);
} }
...@@ -413,8 +420,7 @@ bool SandboxLinux::seccomp_bpf_with_tsync_supported() const { ...@@ -413,8 +420,7 @@ bool SandboxLinux::seccomp_bpf_with_tsync_supported() const {
return seccomp_bpf_with_tsync_supported_; return seccomp_bpf_with_tsync_supported_;
} }
bool SandboxLinux::LimitAddressSpace(const std::string& process_type, bool SandboxLinux::LimitAddressSpace(int* error) {
const Options& options) {
#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \ #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
!defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER) !defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER)
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
...@@ -422,39 +428,11 @@ bool SandboxLinux::LimitAddressSpace(const std::string& process_type, ...@@ -422,39 +428,11 @@ bool SandboxLinux::LimitAddressSpace(const std::string& process_type,
return false; return false;
} }
// Limit the address space to 4 GiB. This is a hard-to-quantify, last-ditch // Unfortunately, it does not appear possible to set RLIMIT_AS such that it
// defense against exploits that have the precondition of allocating large // will both (a) be high enough to support V8's and WebAssembly's address
// amounts of memory. (For an example, see // space requirements while also (b) being low enough to mitigate exploits
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1541, which // using integer overflows that require large allocations, heap spray, or
// exploits integer overflow but requires allocating an amount of memory which // other memory-hungry attack modes.
// would overflow a 32-bit integer.)
rlim_t rlimit_as_soft = std::numeric_limits<uint32_t>::max();
rlim_t rlimit_as_hard = std::numeric_limits<uint32_t>::max();
if (sizeof(rlim_t) == 8) {
// On 64-bit platforms, V8 and possibly others will reserve massive memory
// ranges and rely on on-demand paging for allocation. (Unfortunately, even
// MADV_DONTNEED ranges count towards RLIMIT_AS, which complicates our
// ability to set tight limits while still enabling V8's strategy.) See
// https://crbug.com/169327.
//
// In the GPU process, irrespective of V8, we can exhaust a 4 GiB address
// space under normal usage. See https://crbug.com/271119.
if (process_type == switches::kRendererProcess ||
process_type == switches::kGpuProcess) {
// For now, set the limit to 16 GiB for renderer, worker, and GPU
// processes to accomodate. It may be necessary to raise this limit even
// further; see https://crbug.com/800348.
rlimit_as_soft = 1ULL << 34;
// WebAssembly memory objects use a large amount of address space for
// guard regions. To accomodate this, we allow the address space limit to
// adjust dynamically up to a certain limit. The limit is currently 4 TiB,
// which should allow enough address space for any reasonable page. See
// https://crbug.com/750378.
rlimit_as_hard = 1ULL << 42;
}
}
// By default, add a limit to the VmData memory area that would prevent // By default, add a limit to the VmData memory area that would prevent
// allocations that can't be indexed by an int. // allocations that can't be indexed by an int.
...@@ -469,14 +447,12 @@ bool SandboxLinux::LimitAddressSpace(const std::string& process_type, ...@@ -469,14 +447,12 @@ bool SandboxLinux::LimitAddressSpace(const std::string& process_type,
rlimit_data = 1ULL << 33; rlimit_data = 1ULL << 33;
} }
bool limited_as = sandbox::ResourceLimits::LowerSoftAndHardLimits( *error = sandbox::ResourceLimits::Lower(RLIMIT_DATA, rlimit_data);
RLIMIT_AS, rlimit_as_soft, rlimit_as_hard);
bool limited_data = sandbox::ResourceLimits::Lower(RLIMIT_DATA, rlimit_data);
// Cache the resource limit before turning on the sandbox. // Cache the resource limit before turning on the sandbox.
base::SysInfo::AmountOfVirtualMemory(); base::SysInfo::AmountOfVirtualMemory();
return limited_as && limited_data; return *error == 0;
#else #else
base::SysInfo::AmountOfVirtualMemory(); base::SysInfo::AmountOfVirtualMemory();
return false; return false;
......
...@@ -174,10 +174,10 @@ class SERVICE_MANAGER_SANDBOX_EXPORT SandboxLinux { ...@@ -174,10 +174,10 @@ class SERVICE_MANAGER_SANDBOX_EXPORT SandboxLinux {
PreSandboxHook hook, PreSandboxHook hook,
const Options& options); const Options& options);
// Limit the address space of the current process (and its children). // Limit the address space of the current process (and its children) to make
// to make some vulnerabilities harder to exploit. // some vulnerabilities harder to exploit. Writes the errno due to setrlimit
bool LimitAddressSpace(const std::string& process_type, // (including 0 if no error) into |error|.
const Options& options); bool LimitAddressSpace(int* error);
// Returns a file descriptor to proc. The file descriptor is no longer valid // Returns a file descriptor to proc. The file descriptor is no longer valid
// after the sandbox has been sealed. // after the sandbox has been sealed.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment