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