Commit 8df04628 authored by jschuh@chromium.org's avatar jschuh@chromium.org

Add sandbox support for process memory limits

BUG=335192

Review URL: https://codereview.chromium.org/319573006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@275666 0039d316-1c4b-4281-b951-d872f2087c98
parent 18c1ed3a
...@@ -496,10 +496,14 @@ void SetJobLevel(const CommandLine& cmd_line, ...@@ -496,10 +496,14 @@ void SetJobLevel(const CommandLine& cmd_line,
sandbox::JobLevel job_level, sandbox::JobLevel job_level,
uint32 ui_exceptions, uint32 ui_exceptions,
sandbox::TargetPolicy* policy) { sandbox::TargetPolicy* policy) {
if (ShouldSetJobLevel(cmd_line)) if (ShouldSetJobLevel(cmd_line)) {
#ifdef _WIN64
policy->SetJobMemoryLimit(4ULL * 1024 * 1024 * 1024);
#endif
policy->SetJobLevel(job_level, ui_exceptions); policy->SetJobLevel(job_level, ui_exceptions);
else } else {
policy->SetJobLevel(sandbox::JOB_NONE, 0); policy->SetJobLevel(sandbox::JOB_NONE, 0);
}
} }
// TODO(jschuh): Need get these restrictions applied to NaCl and Pepper. // TODO(jschuh): Need get these restrictions applied to NaCl and Pepper.
......
...@@ -278,6 +278,13 @@ DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { ...@@ -278,6 +278,13 @@ DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
break; break;
} }
case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
BOOL res = ::TerminateJobObject(tracker->job,
SBOX_FATAL_MEMORY_EXCEEDED);
DCHECK(res);
break;
}
default: { default: {
NOTREACHED(); NOTREACHED();
break; break;
......
...@@ -16,7 +16,8 @@ Job::~Job() { ...@@ -16,7 +16,8 @@ Job::~Job() {
DWORD Job::Init(JobLevel security_level, DWORD Job::Init(JobLevel security_level,
const wchar_t* job_name, const wchar_t* job_name,
DWORD ui_exceptions) { DWORD ui_exceptions,
size_t memory_limit) {
if (job_handle_) if (job_handle_)
return ERROR_ALREADY_INITIALIZED; return ERROR_ALREADY_INITIALIZED;
...@@ -52,11 +53,11 @@ DWORD Job::Init(JobLevel security_level, ...@@ -52,11 +53,11 @@ DWORD Job::Init(JobLevel security_level,
jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS; jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;
} }
case JOB_UNPROTECTED: { case JOB_UNPROTECTED: {
// The JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag is not supported on if (memory_limit) {
// Windows 2000. We need a mechanism on Windows 2000 to ensure jeli.BasicLimitInformation.LimitFlags |=
// that processes in the job are terminated when the job is closed JOB_OBJECT_LIMIT_PROCESS_MEMORY;
if (base::win::GetVersion() == base::win::VERSION_PRE_XP) jeli.ProcessMemoryLimit = memory_limit;
break; }
jeli.BasicLimitInformation.LimitFlags |= jeli.BasicLimitInformation.LimitFlags |=
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
......
...@@ -31,7 +31,8 @@ class Job { ...@@ -31,7 +31,8 @@ class Job {
// the error. // the error.
DWORD Init(JobLevel security_level, DWORD Init(JobLevel security_level,
const wchar_t* job_name, const wchar_t* job_name,
DWORD ui_exceptions); DWORD ui_exceptions,
size_t memory_limit);
// Assigns the process referenced by process_handle to the job. // Assigns the process referenced by process_handle to the job.
// If the function succeeds, the return value is ERROR_SUCCESS. If the // If the function succeeds, the return value is ERROR_SUCCESS. If the
......
...@@ -16,7 +16,7 @@ TEST(JobTest, TestCreation) { ...@@ -16,7 +16,7 @@ TEST(JobTest, TestCreation) {
{ {
// Create the job. // Create the job.
Job job; Job job;
ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0));
// check if the job exists. // check if the job exists.
HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, FALSE, HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, FALSE,
...@@ -40,7 +40,7 @@ TEST(JobTest, TestDetach) { ...@@ -40,7 +40,7 @@ TEST(JobTest, TestDetach) {
{ {
// Create the job. // Create the job.
Job job; Job job;
ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0));
job_handle = job.Detach(); job_handle = job.Detach();
ASSERT_TRUE(job_handle != NULL); ASSERT_TRUE(job_handle != NULL);
...@@ -73,7 +73,7 @@ TEST(JobTest, TestExceptions) { ...@@ -73,7 +73,7 @@ TEST(JobTest, TestExceptions) {
// Create the job. // Create the job.
Job job; Job job;
ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name",
JOB_OBJECT_UILIMIT_READCLIPBOARD)); JOB_OBJECT_UILIMIT_READCLIPBOARD, 0));
job_handle = job.Detach(); job_handle = job.Detach();
ASSERT_TRUE(job_handle != NULL); ASSERT_TRUE(job_handle != NULL);
...@@ -93,7 +93,7 @@ TEST(JobTest, TestExceptions) { ...@@ -93,7 +93,7 @@ TEST(JobTest, TestExceptions) {
{ {
// Create the job. // Create the job.
Job job; Job job;
ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0));
job_handle = job.Detach(); job_handle = job.Detach();
ASSERT_TRUE(job_handle != NULL); ASSERT_TRUE(job_handle != NULL);
...@@ -115,8 +115,8 @@ TEST(JobTest, TestExceptions) { ...@@ -115,8 +115,8 @@ TEST(JobTest, TestExceptions) {
TEST(JobTest, DoubleInit) { TEST(JobTest, DoubleInit) {
// Create the job. // Create the job.
Job job; Job job;
ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0));
ASSERT_EQ(ERROR_ALREADY_INITIALIZED, job.Init(JOB_LOCKDOWN, L"test", 0)); ASSERT_EQ(ERROR_ALREADY_INITIALIZED, job.Init(JOB_LOCKDOWN, L"test", 0, 0));
} }
// Tests the error case when we use a method and the object is not yet // Tests the error case when we use a method and the object is not yet
...@@ -131,34 +131,35 @@ TEST(JobTest, NoInit) { ...@@ -131,34 +131,35 @@ TEST(JobTest, NoInit) {
// Tests the initialization of the job with different security level. // Tests the initialization of the job with different security level.
TEST(JobTest, SecurityLevel) { TEST(JobTest, SecurityLevel) {
Job job1; Job job1;
ASSERT_EQ(ERROR_SUCCESS, job1.Init(JOB_LOCKDOWN, L"job1", 0)); ASSERT_EQ(ERROR_SUCCESS, job1.Init(JOB_LOCKDOWN, L"job1", 0, 0));
Job job2; Job job2;
ASSERT_EQ(ERROR_SUCCESS, job2.Init(JOB_RESTRICTED, L"job2", 0)); ASSERT_EQ(ERROR_SUCCESS, job2.Init(JOB_RESTRICTED, L"job2", 0, 0));
Job job3; Job job3;
ASSERT_EQ(ERROR_SUCCESS, job3.Init(JOB_LIMITED_USER, L"job3", 0)); ASSERT_EQ(ERROR_SUCCESS, job3.Init(JOB_LIMITED_USER, L"job3", 0, 0));
Job job4; Job job4;
ASSERT_EQ(ERROR_SUCCESS, job4.Init(JOB_INTERACTIVE, L"job4", 0)); ASSERT_EQ(ERROR_SUCCESS, job4.Init(JOB_INTERACTIVE, L"job4", 0, 0));
Job job5; Job job5;
ASSERT_EQ(ERROR_SUCCESS, job5.Init(JOB_UNPROTECTED, L"job5", 0)); ASSERT_EQ(ERROR_SUCCESS, job5.Init(JOB_UNPROTECTED, L"job5", 0, 0));
// JOB_NONE means we run without a job object so Init should fail. // JOB_NONE means we run without a job object so Init should fail.
Job job6; Job job6;
ASSERT_EQ(ERROR_BAD_ARGUMENTS, job6.Init(JOB_NONE, L"job6", 0)); ASSERT_EQ(ERROR_BAD_ARGUMENTS, job6.Init(JOB_NONE, L"job6", 0, 0));
Job job7; Job job7;
ASSERT_EQ(ERROR_BAD_ARGUMENTS, job7.Init( ASSERT_EQ(ERROR_BAD_ARGUMENTS, job7.Init(
static_cast<JobLevel>(JOB_NONE+1), L"job7", 0)); static_cast<JobLevel>(JOB_NONE+1), L"job7", 0, 0));
} }
// Tests the method "AssignProcessToJob". // Tests the method "AssignProcessToJob".
TEST(JobTest, ProcessInJob) { TEST(JobTest, ProcessInJob) {
// Create the job. // Create the job.
Job job; Job job;
ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_UNPROTECTED, L"job_test_process", 0)); ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_UNPROTECTED, L"job_test_process", 0,
0));
BOOL result = FALSE; BOOL result = FALSE;
......
...@@ -146,7 +146,7 @@ DWORD StartRestrictedProcessInJob(wchar_t *command_line, ...@@ -146,7 +146,7 @@ DWORD StartRestrictedProcessInJob(wchar_t *command_line,
JobLevel job_level, JobLevel job_level,
HANDLE *const job_handle_ret) { HANDLE *const job_handle_ret) {
Job job; Job job;
DWORD err_code = job.Init(job_level, NULL, 0); DWORD err_code = job.Init(job_level, NULL, 0, 0);
if (ERROR_SUCCESS != err_code) if (ERROR_SUCCESS != err_code)
return err_code; return err_code;
......
...@@ -128,6 +128,11 @@ class TargetPolicy { ...@@ -128,6 +128,11 @@ class TargetPolicy {
// Note: the recommended level is JOB_RESTRICTED or JOB_LOCKDOWN. // Note: the recommended level is JOB_RESTRICTED or JOB_LOCKDOWN.
virtual ResultCode SetJobLevel(JobLevel job_level, uint32 ui_exceptions) = 0; virtual ResultCode SetJobLevel(JobLevel job_level, uint32 ui_exceptions) = 0;
// Sets a hard limit on the size of the commit set for the sandboxed process.
// If the limit is reached, the process will be terminated with
// SBOX_FATAL_MEMORY_EXCEEDED (7012).
virtual ResultCode SetJobMemoryLimit(size_t memory_limit) = 0;
// Specifies the desktop on which the application is going to run. If the // Specifies the desktop on which the application is going to run. If the
// desktop does not exist, it will be created. If alternate_winstation is // desktop does not exist, it will be created. If alternate_winstation is
// set to true, the desktop will be created on an alternate window station. // set to true, the desktop will be created on an alternate window station.
......
...@@ -80,6 +80,7 @@ PolicyBase::PolicyBase() ...@@ -80,6 +80,7 @@ PolicyBase::PolicyBase()
initial_level_(USER_LOCKDOWN), initial_level_(USER_LOCKDOWN),
job_level_(JOB_LOCKDOWN), job_level_(JOB_LOCKDOWN),
ui_exceptions_(0), ui_exceptions_(0),
memory_limit_(0),
use_alternate_desktop_(false), use_alternate_desktop_(false),
use_alternate_winstation_(false), use_alternate_winstation_(false),
file_system_init_(false), file_system_init_(false),
...@@ -170,11 +171,22 @@ TokenLevel PolicyBase::GetLockdownTokenLevel() const{ ...@@ -170,11 +171,22 @@ TokenLevel PolicyBase::GetLockdownTokenLevel() const{
} }
ResultCode PolicyBase::SetJobLevel(JobLevel job_level, uint32 ui_exceptions) { ResultCode PolicyBase::SetJobLevel(JobLevel job_level, uint32 ui_exceptions) {
if (memory_limit_ && job_level == JOB_NONE) {
return SBOX_ERROR_BAD_PARAMS;
}
job_level_ = job_level; job_level_ = job_level;
ui_exceptions_ = ui_exceptions; ui_exceptions_ = ui_exceptions;
return SBOX_ALL_OK; return SBOX_ALL_OK;
} }
ResultCode PolicyBase::SetJobMemoryLimit(size_t memory_limit) {
if (memory_limit && job_level_ == JOB_NONE) {
return SBOX_ERROR_BAD_PARAMS;
}
memory_limit_ = memory_limit;
return SBOX_ALL_OK;
}
ResultCode PolicyBase::SetAlternateDesktop(bool alternate_winstation) { ResultCode PolicyBase::SetAlternateDesktop(bool alternate_winstation) {
use_alternate_desktop_ = true; use_alternate_desktop_ = true;
use_alternate_winstation_ = alternate_winstation; use_alternate_winstation_ = alternate_winstation;
...@@ -471,7 +483,8 @@ ResultCode PolicyBase::MakeJobObject(HANDLE* job) { ...@@ -471,7 +483,8 @@ ResultCode PolicyBase::MakeJobObject(HANDLE* job) {
if (job_level_ != JOB_NONE) { if (job_level_ != JOB_NONE) {
// Create the windows job object. // Create the windows job object.
Job job_obj; Job job_obj;
DWORD result = job_obj.Init(job_level_, NULL, ui_exceptions_); DWORD result = job_obj.Init(job_level_, NULL, ui_exceptions_,
memory_limit_);
if (ERROR_SUCCESS != result) { if (ERROR_SUCCESS != result) {
return SBOX_ERROR_GENERIC; return SBOX_ERROR_GENERIC;
} }
......
...@@ -45,6 +45,7 @@ class PolicyBase : public Dispatcher, public TargetPolicy { ...@@ -45,6 +45,7 @@ class PolicyBase : public Dispatcher, public TargetPolicy {
virtual TokenLevel GetLockdownTokenLevel() const OVERRIDE; virtual TokenLevel GetLockdownTokenLevel() const OVERRIDE;
virtual ResultCode SetJobLevel(JobLevel job_level, virtual ResultCode SetJobLevel(JobLevel job_level,
uint32 ui_exceptions) OVERRIDE; uint32 ui_exceptions) OVERRIDE;
virtual ResultCode SetJobMemoryLimit(size_t memory_limit) OVERRIDE;
virtual ResultCode SetAlternateDesktop(bool alternate_winstation) OVERRIDE; virtual ResultCode SetAlternateDesktop(bool alternate_winstation) OVERRIDE;
virtual base::string16 GetAlternateDesktop() const OVERRIDE; virtual base::string16 GetAlternateDesktop() const OVERRIDE;
virtual ResultCode CreateAlternateDesktop(bool alternate_winstation) OVERRIDE; virtual ResultCode CreateAlternateDesktop(bool alternate_winstation) OVERRIDE;
...@@ -127,6 +128,7 @@ class PolicyBase : public Dispatcher, public TargetPolicy { ...@@ -127,6 +128,7 @@ class PolicyBase : public Dispatcher, public TargetPolicy {
TokenLevel initial_level_; TokenLevel initial_level_;
JobLevel job_level_; JobLevel job_level_;
uint32 ui_exceptions_; uint32 ui_exceptions_;
size_t memory_limit_;
bool use_alternate_desktop_; bool use_alternate_desktop_;
bool use_alternate_winstation_; bool use_alternate_winstation_;
// Helps the file system policy initialization. // Helps the file system policy initialization.
......
...@@ -58,6 +58,7 @@ enum TerminationCodes { ...@@ -58,6 +58,7 @@ enum TerminationCodes {
SBOX_FATAL_CACHEDISABLE = 7009, // Failed to forbid HCKU caching. SBOX_FATAL_CACHEDISABLE = 7009, // Failed to forbid HCKU caching.
SBOX_FATAL_CLOSEHANDLES = 7010, // Failed to close pending handles. SBOX_FATAL_CLOSEHANDLES = 7010, // Failed to close pending handles.
SBOX_FATAL_MITIGATION = 7011, // Could not set the mitigation policy. SBOX_FATAL_MITIGATION = 7011, // Could not set the mitigation policy.
SBOX_FATAL_MEMORY_EXCEEDED = 7012, // Exceeded the job memory limit.
SBOX_FATAL_LAST SBOX_FATAL_LAST
}; };
......
...@@ -310,5 +310,24 @@ SBOX_TESTS_COMMAND int SleepCmd(int argc, wchar_t **argv) { ...@@ -310,5 +310,24 @@ SBOX_TESTS_COMMAND int SleepCmd(int argc, wchar_t **argv) {
return SBOX_TEST_SUCCEEDED; return SBOX_TEST_SUCCEEDED;
} }
SBOX_TESTS_COMMAND int AllocateCmd(int argc, wchar_t **argv) {
if (argc != 1)
return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
size_t mem_size = static_cast<size_t>(_wtoll(argv[0]));
void* memory = ::VirtualAlloc(NULL, mem_size, MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (!memory) {
// We need to give the broker a chance to kill our process on failure.
::Sleep(5000);
return SBOX_TEST_DENIED;
}
if (!::VirtualFree(memory, 0, MEM_RELEASE))
return SBOX_TEST_FAILED;
return SBOX_TEST_SUCCEEDED;
}
} // namespace sandbox } // namespace sandbox
...@@ -217,4 +217,25 @@ TEST(ValidationSuite, TestThread) { ...@@ -217,4 +217,25 @@ TEST(ValidationSuite, TestThread) {
EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command));
} }
// Tests if an over-limit allocation will be denied.
TEST(ValidationSuite, TestMemoryLimit) {
TestRunner runner;
wchar_t command[1024] = {0};
const int kAllocationSize = 256 * 1024 * 1024;
wsprintf(command, L"AllocateCmd %d", kAllocationSize);
runner.GetPolicy()->SetJobMemoryLimit(kAllocationSize);
EXPECT_EQ(SBOX_FATAL_MEMORY_EXCEEDED, runner.RunTest(command));
}
// Tests a large allocation will succeed absent limits.
TEST(ValidationSuite, TestMemoryNoLimit) {
TestRunner runner;
wchar_t command[1024] = {0};
const int kAllocationSize = 256 * 1024 * 1024;
wsprintf(command, L"AllocateCmd %d", kAllocationSize);
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command));
}
} // namespace sandbox } // namespace sandbox
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