Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / sandbox / win / src / filesystem_policy.cc
blob2c3f703ef754ad804fb817871cfbca9943bb2ce7
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <string>
7 #include "sandbox/win/src/filesystem_policy.h"
9 #include "base/logging.h"
10 #include "base/win/scoped_handle.h"
11 #include "base/win/windows_version.h"
12 #include "sandbox/win/src/ipc_tags.h"
13 #include "sandbox/win/src/policy_engine_opcodes.h"
14 #include "sandbox/win/src/policy_params.h"
15 #include "sandbox/win/src/sandbox_utils.h"
16 #include "sandbox/win/src/sandbox_types.h"
17 #include "sandbox/win/src/win_utils.h"
19 namespace {
21 NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle,
22 ACCESS_MASK desired_access,
23 OBJECT_ATTRIBUTES* obj_attributes,
24 IO_STATUS_BLOCK* io_status_block,
25 ULONG file_attributes,
26 ULONG share_access,
27 ULONG create_disposition,
28 ULONG create_options,
29 PVOID ea_buffer,
30 ULONG ea_lenght,
31 HANDLE target_process) {
32 NtCreateFileFunction NtCreateFile = NULL;
33 ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile);
35 HANDLE local_handle = INVALID_HANDLE_VALUE;
36 NTSTATUS status = NtCreateFile(&local_handle, desired_access, obj_attributes,
37 io_status_block, NULL, file_attributes,
38 share_access, create_disposition,
39 create_options, ea_buffer, ea_lenght);
40 if (!NT_SUCCESS(status)) {
41 return status;
44 if (!sandbox::SameObject(local_handle, obj_attributes->ObjectName->Buffer)) {
45 // The handle points somewhere else. Fail the operation.
46 ::CloseHandle(local_handle);
47 return STATUS_ACCESS_DENIED;
50 if (!::DuplicateHandle(::GetCurrentProcess(), local_handle,
51 target_process, target_file_handle, 0, FALSE,
52 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
53 return STATUS_ACCESS_DENIED;
55 return STATUS_SUCCESS;
58 // Get an initialized anonymous level Security QOS.
59 SECURITY_QUALITY_OF_SERVICE GetAnonymousQOS() {
60 SECURITY_QUALITY_OF_SERVICE security_qos = {0};
61 security_qos.Length = sizeof(security_qos);
62 security_qos.ImpersonationLevel = SecurityAnonymous;
63 // Set dynamic tracking so that a pipe doesn't capture the broker's token
64 security_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
65 security_qos.EffectiveOnly = TRUE;
67 return security_qos;
70 } // namespace.
72 namespace sandbox {
74 bool FileSystemPolicy::GenerateRules(const wchar_t* name,
75 TargetPolicy::Semantics semantics,
76 LowLevelPolicy* policy) {
77 base::string16 mod_name(name);
78 if (mod_name.empty()) {
79 return false;
82 if (!PreProcessName(mod_name, &mod_name)) {
83 // The path to be added might contain a reparse point.
84 NOTREACHED();
85 return false;
88 // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
89 // infrastructure to normalize names. In any case we need to escape the
90 // question marks.
91 if (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) {
92 mod_name = FixNTPrefixForMatch(mod_name);
93 name = mod_name.c_str();
96 EvalResult result = ASK_BROKER;
98 // List of supported calls for the filesystem.
99 const unsigned kCallNtCreateFile = 0x1;
100 const unsigned kCallNtOpenFile = 0x2;
101 const unsigned kCallNtQueryAttributesFile = 0x4;
102 const unsigned kCallNtQueryFullAttributesFile = 0x8;
103 const unsigned kCallNtSetInfoRename = 0x10;
105 DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile |
106 kCallNtQueryAttributesFile |
107 kCallNtQueryFullAttributesFile | kCallNtSetInfoRename;
109 PolicyRule create(result);
110 PolicyRule open(result);
111 PolicyRule query(result);
112 PolicyRule query_full(result);
113 PolicyRule rename(result);
115 switch (semantics) {
116 case TargetPolicy::FILES_ALLOW_DIR_ANY: {
117 open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND);
118 create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND);
119 break;
121 case TargetPolicy::FILES_ALLOW_READONLY: {
122 // We consider all flags that are not known to be readonly as potentially
123 // used for write.
124 DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES |
125 FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE |
126 GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL;
127 DWORD restricted_flags = ~allowed_flags;
128 open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND);
129 open.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL);
130 create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND);
131 create.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL);
133 // Read only access don't work for rename.
134 rule_to_add &= ~kCallNtSetInfoRename;
135 break;
137 case TargetPolicy::FILES_ALLOW_QUERY: {
138 // Here we don't want to add policy for the open or the create.
139 rule_to_add &= ~(kCallNtOpenFile | kCallNtCreateFile |
140 kCallNtSetInfoRename);
141 break;
143 case TargetPolicy::FILES_ALLOW_ANY: {
144 break;
146 default: {
147 NOTREACHED();
148 return false;
152 if ((rule_to_add & kCallNtCreateFile) &&
153 (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) ||
154 !policy->AddRule(IPC_NTCREATEFILE_TAG, &create))) {
155 return false;
158 if ((rule_to_add & kCallNtOpenFile) &&
159 (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) ||
160 !policy->AddRule(IPC_NTOPENFILE_TAG, &open))) {
161 return false;
164 if ((rule_to_add & kCallNtQueryAttributesFile) &&
165 (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
166 !policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &query))) {
167 return false;
170 if ((rule_to_add & kCallNtQueryFullAttributesFile) &&
171 (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE)
172 || !policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG,
173 &query_full))) {
174 return false;
177 if ((rule_to_add & kCallNtSetInfoRename) &&
178 (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
179 !policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &rename))) {
180 return false;
183 return true;
186 // Right now we insert two rules, to be evaluated before any user supplied rule:
187 // - go to the broker if the path doesn't look like the paths that we push on
188 // the policy (namely \??\something).
189 // - go to the broker if it looks like this is a short-name path.
191 // It is possible to add a rule to go to the broker in any case; it would look
192 // something like:
193 // rule = new PolicyRule(ASK_BROKER);
194 // rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
195 // policy->AddRule(service, rule);
196 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) {
197 PolicyRule format(ASK_BROKER);
198 PolicyRule short_name(ASK_BROKER);
200 bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
201 rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*",
202 CASE_SENSITIVE);
204 rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
205 rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE);
207 if (!rv || !policy->AddRule(IPC_NTCREATEFILE_TAG, &format))
208 return false;
210 if (!policy->AddRule(IPC_NTCREATEFILE_TAG, &short_name))
211 return false;
213 if (!policy->AddRule(IPC_NTOPENFILE_TAG, &format))
214 return false;
216 if (!policy->AddRule(IPC_NTOPENFILE_TAG, &short_name))
217 return false;
219 if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &format))
220 return false;
222 if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &short_name))
223 return false;
225 if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &format))
226 return false;
228 if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &short_name))
229 return false;
231 if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &format))
232 return false;
234 if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &short_name))
235 return false;
237 return true;
240 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result,
241 const ClientInfo& client_info,
242 const base::string16 &file,
243 uint32 attributes,
244 uint32 desired_access,
245 uint32 file_attributes,
246 uint32 share_access,
247 uint32 create_disposition,
248 uint32 create_options,
249 HANDLE *handle,
250 NTSTATUS* nt_status,
251 ULONG_PTR *io_information) {
252 // The only action supported is ASK_BROKER which means create the requested
253 // file as specified.
254 if (ASK_BROKER != eval_result) {
255 *nt_status = STATUS_ACCESS_DENIED;
256 return false;
258 IO_STATUS_BLOCK io_block = {};
259 UNICODE_STRING uni_name = {};
260 OBJECT_ATTRIBUTES obj_attributes = {};
261 SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS();
263 InitObjectAttribs(file, attributes, NULL, &obj_attributes,
264 &uni_name, IsPipe(file) ? &security_qos : NULL);
265 *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes,
266 &io_block, file_attributes, share_access,
267 create_disposition, create_options, NULL,
268 0, client_info.process);
270 *io_information = io_block.Information;
271 return true;
274 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result,
275 const ClientInfo& client_info,
276 const base::string16 &file,
277 uint32 attributes,
278 uint32 desired_access,
279 uint32 share_access,
280 uint32 open_options,
281 HANDLE *handle,
282 NTSTATUS* nt_status,
283 ULONG_PTR *io_information) {
284 // The only action supported is ASK_BROKER which means open the requested
285 // file as specified.
286 if (ASK_BROKER != eval_result) {
287 *nt_status = STATUS_ACCESS_DENIED;
288 return true;
290 // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and
291 // CreateDisposition = FILE_OPEN.
292 IO_STATUS_BLOCK io_block = {};
293 UNICODE_STRING uni_name = {};
294 OBJECT_ATTRIBUTES obj_attributes = {};
295 SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS();
297 InitObjectAttribs(file, attributes, NULL, &obj_attributes,
298 &uni_name, IsPipe(file) ? &security_qos : NULL);
299 *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes,
300 &io_block, 0, share_access, FILE_OPEN,
301 open_options, NULL, 0,
302 client_info.process);
304 *io_information = io_block.Information;
305 return true;
308 bool FileSystemPolicy::QueryAttributesFileAction(
309 EvalResult eval_result,
310 const ClientInfo& client_info,
311 const base::string16 &file,
312 uint32 attributes,
313 FILE_BASIC_INFORMATION* file_info,
314 NTSTATUS* nt_status) {
315 // The only action supported is ASK_BROKER which means query the requested
316 // file as specified.
317 if (ASK_BROKER != eval_result) {
318 *nt_status = STATUS_ACCESS_DENIED;
319 return true;
322 NtQueryAttributesFileFunction NtQueryAttributesFile = NULL;
323 ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile);
325 UNICODE_STRING uni_name = {0};
326 OBJECT_ATTRIBUTES obj_attributes = {0};
327 SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS();
329 InitObjectAttribs(file, attributes, NULL, &obj_attributes,
330 &uni_name, IsPipe(file) ? &security_qos : NULL);
331 *nt_status = NtQueryAttributesFile(&obj_attributes, file_info);
333 return true;
336 bool FileSystemPolicy::QueryFullAttributesFileAction(
337 EvalResult eval_result,
338 const ClientInfo& client_info,
339 const base::string16 &file,
340 uint32 attributes,
341 FILE_NETWORK_OPEN_INFORMATION* file_info,
342 NTSTATUS* nt_status) {
343 // The only action supported is ASK_BROKER which means query the requested
344 // file as specified.
345 if (ASK_BROKER != eval_result) {
346 *nt_status = STATUS_ACCESS_DENIED;
347 return true;
350 NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = NULL;
351 ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile);
353 UNICODE_STRING uni_name = {0};
354 OBJECT_ATTRIBUTES obj_attributes = {0};
355 SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS();
357 InitObjectAttribs(file, attributes, NULL, &obj_attributes,
358 &uni_name, IsPipe(file) ? &security_qos : NULL);
359 *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info);
361 return true;
364 bool FileSystemPolicy::SetInformationFileAction(
365 EvalResult eval_result, const ClientInfo& client_info,
366 HANDLE target_file_handle, void* file_info, uint32 length,
367 uint32 info_class, IO_STATUS_BLOCK* io_block,
368 NTSTATUS* nt_status) {
369 // The only action supported is ASK_BROKER which means open the requested
370 // file as specified.
371 if (ASK_BROKER != eval_result) {
372 *nt_status = STATUS_ACCESS_DENIED;
373 return true;
376 NtSetInformationFileFunction NtSetInformationFile = NULL;
377 ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile);
379 HANDLE local_handle = NULL;
380 if (!::DuplicateHandle(client_info.process, target_file_handle,
381 ::GetCurrentProcess(), &local_handle, 0, FALSE,
382 DUPLICATE_SAME_ACCESS)) {
383 *nt_status = STATUS_ACCESS_DENIED;
384 return true;
387 base::win::ScopedHandle handle(local_handle);
389 FILE_INFORMATION_CLASS file_info_class =
390 static_cast<FILE_INFORMATION_CLASS>(info_class);
391 *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length,
392 file_info_class);
394 return true;
397 bool PreProcessName(const base::string16& path, base::string16* new_path) {
398 ConvertToLongPath(path, new_path);
400 bool reparsed = false;
401 if (ERROR_SUCCESS != IsReparsePoint(*new_path, &reparsed))
402 return false;
404 // We can't process reparsed file.
405 return !reparsed;
408 base::string16 FixNTPrefixForMatch(const base::string16& name) {
409 base::string16 mod_name = name;
411 // NT prefix escaped for rule matcher
412 const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\";
413 const int kNTPrefixEscapedLen = arraysize(kNTPrefixEscaped) - 1;
415 if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) {
416 if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) {
417 // TODO(nsylvain): Find a better way to do name resolution. Right now we
418 // take the name and we expand it.
419 mod_name.insert(0, kNTPrefixEscaped);
421 } else {
422 // Start of name matches NT prefix, replace with escaped format
423 // Fixes bug: 334882
424 mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped);
427 return mod_name;
430 } // namespace sandbox