| // 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 "starboard/memory.h" |
| #include "starboard/types.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 |