|  | // 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 <memory> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/memory/free_deleter.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 "base/win/win_util.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 = {}; | 
|  | 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 = base::win::HandleToUint32(handle); | 
|  |  | 
|  | std::unique_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 |