Chromium Code Reviews| Index: base/memory/shared_memory_win_unittest.cc |
| diff --git a/base/memory/shared_memory_win_unittest.cc b/base/memory/shared_memory_win_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6e19083f73586cebed0fa9f42093ae023b776f38 |
| --- /dev/null |
| +++ b/base/memory/shared_memory_win_unittest.cc |
| @@ -0,0 +1,220 @@ |
| +// 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. |
| + |
| +#include <windows.h> |
| +#include <sddl.h> |
| + |
| +#include "base/command_line.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/memory/shared_memory.h" |
| +#include "base/process/process.h" |
| +#include "base/rand_util.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "base/strings/sys_string_conversions.h" |
| +#include "base/test/multiprocess_test.h" |
| +#include "base/test/test_timeouts.h" |
| +#include "base/win/scoped_handle.h" |
| +#include "testing/multiprocess_func_list.h" |
| + |
| +namespace base { |
| +namespace { |
| +const char* kHandleSwitchName = "shared_memory_win_test_switch"; |
| + |
| +// Creates a process token with a low integrity SID. |
| +win::ScopedHandle CreateLowIntegritySID() { |
| + HANDLE process_token_raw = nullptr; |
| + BOOL success = ::OpenProcessToken(GetCurrentProcess(), |
| + TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | |
| + TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, |
| + &process_token_raw); |
| + if (!success) |
| + return base::win::ScopedHandle(); |
| + win::ScopedHandle process_token(process_token_raw); |
| + |
| + HANDLE lowered_process_token_raw = nullptr; |
| + success = |
| + ::DuplicateTokenEx(process_token.Get(), 0, NULL, SecurityImpersonation, |
| + TokenPrimary, &lowered_process_token_raw); |
| + if (!success) |
| + return base::win::ScopedHandle(); |
| + win::ScopedHandle lowered_process_token(lowered_process_token_raw); |
| + |
| + // Low integrity SID |
| + WCHAR integrity_sid_string[20] = L"S-1-16-4096"; |
| + PSID integrity_sid = nullptr; |
| + success = ::ConvertStringSidToSid(integrity_sid_string, &integrity_sid); |
| + if (!success) |
| + return base::win::ScopedHandle(); |
| + |
| + TOKEN_MANDATORY_LABEL TIL = {0}; |
|
Nico
2016/02/19 03:33:01
FAILED: ninja -t msvc -e environment.x86 -- C:\b\b
|
| + TIL.Label.Attributes = SE_GROUP_INTEGRITY; |
| + TIL.Label.Sid = integrity_sid; |
| + success = ::SetTokenInformation( |
| + lowered_process_token.Get(), TokenIntegrityLevel, &TIL, |
| + sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(integrity_sid)); |
| + if (!success) |
| + return base::win::ScopedHandle(); |
| + return lowered_process_token; |
| +} |
| + |
| +// Reads a HANDLE from the pipe as a raw int, least significant digit first. |
| +win::ScopedHandle ReadHandleFromPipe(HANDLE pipe) { |
| + // Read from parent pipe. |
| + const size_t buf_size = 1000; |
| + char buffer[buf_size]; |
| + memset(buffer, 0, buf_size); |
| + DWORD bytes_read; |
| + BOOL success = ReadFile(pipe, buffer, buf_size, &bytes_read, NULL); |
| + |
| + if (!success || bytes_read == 0) { |
| + LOG(ERROR) << "Failed to read handle from pipe."; |
| + return win::ScopedHandle(); |
| + } |
| + |
| + int handle_as_int = 0; |
| + int power_of_ten = 1; |
| + for (unsigned int i = 0; i < bytes_read; ++i) { |
| + handle_as_int += buffer[i] * power_of_ten; |
| + power_of_ten *= 10; |
| + } |
| + |
| + return win::ScopedHandle(reinterpret_cast<HANDLE>(handle_as_int)); |
| +} |
| + |
| +// Writes a HANDLE to a pipe as a raw int, least significant digit first. |
| +void WriteHandleToPipe(HANDLE pipe, HANDLE handle) { |
| + uint32_t handle_as_int = reinterpret_cast<uint32_t>(handle); |
|
brucedawson
2016/02/20 00:49:52
VC++ 2015 prefers double casts for the truncation-
|
| + |
| + scoped_ptr<char, base::FreeDeleter> buffer(static_cast<char*>(malloc(1000))); |
| + size_t index = 0; |
| + while (handle_as_int > 0) { |
| + buffer.get()[index] = handle_as_int % 10; |
| + handle_as_int /= 10; |
| + ++index; |
| + } |
| + |
| + ::ConnectNamedPipe(pipe, nullptr); |
| + DWORD written; |
| + ASSERT_TRUE(::WriteFile(pipe, buffer.get(), index, &written, NULL)); |
| +} |
| + |
| +// Creates a communication pipe with the given name. |
| +win::ScopedHandle CreateCommunicationPipe(const std::wstring& name) { |
| + return win::ScopedHandle(CreateNamedPipe(name.c_str(), // pipe name |
| + PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, |
| + 1000, 1000, 0, NULL)); |
| +} |
| + |
| +// Generates a random name for a communication pipe. |
| +std::wstring CreateCommunicationPipeName() { |
| + uint64_t rand_values[4]; |
| + RandBytes(&rand_values, sizeof(rand_values)); |
| + std::wstring child_pipe_name = StringPrintf( |
| + L"\\\\.\\pipe\\SharedMemoryWinTest_%016llx%016llx%016llx%016llx", |
| + rand_values[0], rand_values[1], rand_values[2], rand_values[3]); |
| + return child_pipe_name; |
| +} |
| + |
| +class SharedMemoryWinTest : public base::MultiProcessTest { |
| + protected: |
| + CommandLine MakeCmdLine(const std::string& procname) override { |
| + CommandLine line = base::MultiProcessTest::MakeCmdLine(procname); |
| + line.AppendSwitchASCII(kHandleSwitchName, communication_pipe_name_); |
| + return line; |
| + } |
| + |
| + std::string communication_pipe_name_; |
| +}; |
| + |
| +MULTIPROCESS_TEST_MAIN(LowerPermissions) { |
| + std::string handle_name = |
| + CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kHandleSwitchName); |
| + std::wstring handle_name16 = SysUTF8ToWide(handle_name); |
| + win::ScopedHandle parent_pipe( |
| + ::CreateFile(handle_name16.c_str(), // pipe name |
| + GENERIC_READ, |
| + 0, // no sharing |
| + NULL, // default security attributes |
| + OPEN_EXISTING, // opens existing pipe |
| + 0, // default attributes |
| + NULL)); // no template file |
| + if (parent_pipe.Get() == INVALID_HANDLE_VALUE) { |
| + LOG(ERROR) << "Failed to open communication pipe."; |
| + return 1; |
| + } |
| + |
| + win::ScopedHandle received_handle = ReadHandleFromPipe(parent_pipe.Get()); |
| + if (!received_handle.Get()) { |
| + LOG(ERROR) << "Failed to read handle from pipe."; |
| + return 1; |
| + } |
| + |
| + // Attempting to add the WRITE_DAC permission should fail. |
| + HANDLE duped_handle; |
| + BOOL success = ::DuplicateHandle(GetCurrentProcess(), received_handle.Get(), |
| + GetCurrentProcess(), &duped_handle, |
| + FILE_MAP_READ | WRITE_DAC, FALSE, 0); |
| + if (success) { |
| + LOG(ERROR) << "Should not have been able to add WRITE_DAC permission."; |
| + return 1; |
| + } |
| + |
| + // Attempting to add the FILE_MAP_WRITE permission should fail. |
| + success = ::DuplicateHandle(GetCurrentProcess(), received_handle.Get(), |
| + GetCurrentProcess(), &duped_handle, |
| + FILE_MAP_READ | FILE_MAP_WRITE, FALSE, 0); |
| + if (success) { |
| + LOG(ERROR) << "Should not have been able to add FILE_MAP_WRITE permission."; |
| + return 1; |
| + } |
| + |
| + // Attempting to duplicate the HANDLE with the same permissions should |
| + // succeed. |
| + success = ::DuplicateHandle(GetCurrentProcess(), received_handle.Get(), |
| + GetCurrentProcess(), &duped_handle, FILE_MAP_READ, |
| + FALSE, 0); |
| + if (!success) { |
| + LOG(ERROR) << "Failed to duplicate handle."; |
| + return 4; |
| + } |
| + ::CloseHandle(duped_handle); |
| + return 0; |
| +} |
| + |
| +TEST_F(SharedMemoryWinTest, LowerPermissions) { |
| + std::wstring communication_pipe_name = CreateCommunicationPipeName(); |
| + communication_pipe_name_ = SysWideToUTF8(communication_pipe_name); |
| + |
| + win::ScopedHandle communication_pipe = |
| + CreateCommunicationPipe(communication_pipe_name); |
| + ASSERT_TRUE(communication_pipe.Get()); |
| + |
| + win::ScopedHandle lowered_process_token = CreateLowIntegritySID(); |
| + ASSERT_TRUE(lowered_process_token.Get()); |
| + |
| + base::LaunchOptions options; |
| + options.as_user = lowered_process_token.Get(); |
| + base::Process process = SpawnChildWithOptions("LowerPermissions", options); |
| + ASSERT_TRUE(process.IsValid()); |
| + |
| + SharedMemory memory; |
| + memory.CreateAndMapAnonymous(1001); |
| + |
| + // Duplicate into child process, giving only FILE_MAP_READ permissions. |
| + HANDLE raw_handle = nullptr; |
| + ::DuplicateHandle(::GetCurrentProcess(), memory.handle().GetHandle(), |
| + process.Handle(), &raw_handle, |
| + FILE_MAP_READ | SECTION_QUERY, FALSE, 0); |
| + ASSERT_TRUE(raw_handle); |
| + |
| + WriteHandleToPipe(communication_pipe.Get(), raw_handle); |
| + |
| + int exit_code; |
| + EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(), |
| + &exit_code)); |
| + EXPECT_EQ(0, exit_code); |
| +} |
| + |
| +} // namespace |
| +} // namespace base |