blob: 0857284229598e7bfa0a2c944f5d894f7b07ea76 [file] [log] [blame]
// 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