summaryrefslogtreecommitdiffstats
path: root/base/memory/shared_memory_win_unittest.cc
blob: 9dbefb5ec0ef84fda2f9690f3f33c0a726200a3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// 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 "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);

  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