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