|
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 |
|
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 |
|
61 bool FileSystemPolicy::GenerateRules(const wchar_t* name, |
|
62 TargetPolicy::Semantics semantics, |
|
63 LowLevelPolicy* policy) { |
|
64 std::wstring 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 if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { |
|
81 // TODO(nsylvain): Find a better way to do name resolution. Right now we |
|
82 // take the name and we expand it. |
|
83 mod_name.insert(0, L"\\/?/?\\"); |
|
84 name = mod_name.c_str(); |
|
85 } |
|
86 } |
|
87 |
|
88 EvalResult result = ASK_BROKER; |
|
89 |
|
90 // List of supported calls for the filesystem. |
|
91 const unsigned kCallNtCreateFile = 0x1; |
|
92 const unsigned kCallNtOpenFile = 0x2; |
|
93 const unsigned kCallNtQueryAttributesFile = 0x4; |
|
94 const unsigned kCallNtQueryFullAttributesFile = 0x8; |
|
95 const unsigned kCallNtSetInfoRename = 0x10; |
|
96 |
|
97 DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile | |
|
98 kCallNtQueryAttributesFile | |
|
99 kCallNtQueryFullAttributesFile | kCallNtSetInfoRename; |
|
100 |
|
101 PolicyRule create(result); |
|
102 PolicyRule open(result); |
|
103 PolicyRule query(result); |
|
104 PolicyRule query_full(result); |
|
105 PolicyRule rename(result); |
|
106 |
|
107 switch (semantics) { |
|
108 case TargetPolicy::FILES_ALLOW_DIR_ANY: { |
|
109 open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); |
|
110 create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); |
|
111 break; |
|
112 } |
|
113 case TargetPolicy::FILES_ALLOW_READONLY: { |
|
114 // We consider all flags that are not known to be readonly as potentially |
|
115 // used for write. |
|
116 DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES | |
|
117 FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE | |
|
118 GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; |
|
119 DWORD restricted_flags = ~allowed_flags; |
|
120 open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); |
|
121 create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); |
|
122 |
|
123 // Read only access don't work for rename. |
|
124 rule_to_add &= ~kCallNtSetInfoRename; |
|
125 break; |
|
126 } |
|
127 case TargetPolicy::FILES_ALLOW_QUERY: { |
|
128 // Here we don't want to add policy for the open or the create. |
|
129 rule_to_add &= ~(kCallNtOpenFile | kCallNtCreateFile | |
|
130 kCallNtSetInfoRename); |
|
131 break; |
|
132 } |
|
133 case TargetPolicy::FILES_ALLOW_ANY: { |
|
134 break; |
|
135 } |
|
136 default: { |
|
137 NOTREACHED(); |
|
138 return false; |
|
139 } |
|
140 } |
|
141 |
|
142 if ((rule_to_add & kCallNtCreateFile) && |
|
143 (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || |
|
144 !policy->AddRule(IPC_NTCREATEFILE_TAG, &create))) { |
|
145 return false; |
|
146 } |
|
147 |
|
148 if ((rule_to_add & kCallNtOpenFile) && |
|
149 (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || |
|
150 !policy->AddRule(IPC_NTOPENFILE_TAG, &open))) { |
|
151 return false; |
|
152 } |
|
153 |
|
154 if ((rule_to_add & kCallNtQueryAttributesFile) && |
|
155 (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || |
|
156 !policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &query))) { |
|
157 return false; |
|
158 } |
|
159 |
|
160 if ((rule_to_add & kCallNtQueryFullAttributesFile) && |
|
161 (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) |
|
162 || !policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, |
|
163 &query_full))) { |
|
164 return false; |
|
165 } |
|
166 |
|
167 if ((rule_to_add & kCallNtSetInfoRename) && |
|
168 (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || |
|
169 !policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &rename))) { |
|
170 return false; |
|
171 } |
|
172 |
|
173 return true; |
|
174 } |
|
175 |
|
176 // Right now we insert two rules, to be evaluated before any user supplied rule: |
|
177 // - go to the broker if the path doesn't look like the paths that we push on |
|
178 // the policy (namely \??\something). |
|
179 // - go to the broker if it looks like this is a short-name path. |
|
180 // |
|
181 // It is possible to add a rule to go to the broker in any case; it would look |
|
182 // something like: |
|
183 // rule = new PolicyRule(ASK_BROKER); |
|
184 // rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); |
|
185 // policy->AddRule(service, rule); |
|
186 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) { |
|
187 PolicyRule format(ASK_BROKER); |
|
188 PolicyRule short_name(ASK_BROKER); |
|
189 |
|
190 bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); |
|
191 rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*", |
|
192 CASE_SENSITIVE); |
|
193 |
|
194 rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); |
|
195 rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE); |
|
196 |
|
197 if (!rv || !policy->AddRule(IPC_NTCREATEFILE_TAG, &format)) |
|
198 return false; |
|
199 |
|
200 if (!policy->AddRule(IPC_NTCREATEFILE_TAG, &short_name)) |
|
201 return false; |
|
202 |
|
203 if (!policy->AddRule(IPC_NTOPENFILE_TAG, &format)) |
|
204 return false; |
|
205 |
|
206 if (!policy->AddRule(IPC_NTOPENFILE_TAG, &short_name)) |
|
207 return false; |
|
208 |
|
209 if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &format)) |
|
210 return false; |
|
211 |
|
212 if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &short_name)) |
|
213 return false; |
|
214 |
|
215 if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &format)) |
|
216 return false; |
|
217 |
|
218 if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &short_name)) |
|
219 return false; |
|
220 |
|
221 if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &format)) |
|
222 return false; |
|
223 |
|
224 if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &short_name)) |
|
225 return false; |
|
226 |
|
227 return true; |
|
228 } |
|
229 |
|
230 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, |
|
231 const ClientInfo& client_info, |
|
232 const std::wstring &file, |
|
233 uint32 attributes, |
|
234 uint32 desired_access, |
|
235 uint32 file_attributes, |
|
236 uint32 share_access, |
|
237 uint32 create_disposition, |
|
238 uint32 create_options, |
|
239 HANDLE *handle, |
|
240 NTSTATUS* nt_status, |
|
241 ULONG_PTR *io_information) { |
|
242 // The only action supported is ASK_BROKER which means create the requested |
|
243 // file as specified. |
|
244 if (ASK_BROKER != eval_result) { |
|
245 *nt_status = STATUS_ACCESS_DENIED; |
|
246 return false; |
|
247 } |
|
248 IO_STATUS_BLOCK io_block = {0}; |
|
249 UNICODE_STRING uni_name = {0}; |
|
250 OBJECT_ATTRIBUTES obj_attributes = {0}; |
|
251 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); |
|
252 *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, |
|
253 &io_block, file_attributes, share_access, |
|
254 create_disposition, create_options, NULL, |
|
255 0, client_info.process); |
|
256 |
|
257 *io_information = io_block.Information; |
|
258 return true; |
|
259 } |
|
260 |
|
261 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result, |
|
262 const ClientInfo& client_info, |
|
263 const std::wstring &file, |
|
264 uint32 attributes, |
|
265 uint32 desired_access, |
|
266 uint32 share_access, |
|
267 uint32 open_options, |
|
268 HANDLE *handle, |
|
269 NTSTATUS* nt_status, |
|
270 ULONG_PTR *io_information) { |
|
271 // The only action supported is ASK_BROKER which means open the requested |
|
272 // file as specified. |
|
273 if (ASK_BROKER != eval_result) { |
|
274 *nt_status = STATUS_ACCESS_DENIED; |
|
275 return true; |
|
276 } |
|
277 // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and |
|
278 // CreateDisposition = FILE_OPEN. |
|
279 IO_STATUS_BLOCK io_block = {0}; |
|
280 UNICODE_STRING uni_name = {0}; |
|
281 OBJECT_ATTRIBUTES obj_attributes = {0}; |
|
282 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); |
|
283 *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, |
|
284 &io_block, 0, share_access, FILE_OPEN, |
|
285 open_options, NULL, 0, |
|
286 client_info.process); |
|
287 |
|
288 *io_information = io_block.Information; |
|
289 return true; |
|
290 } |
|
291 |
|
292 bool FileSystemPolicy::QueryAttributesFileAction( |
|
293 EvalResult eval_result, |
|
294 const ClientInfo& client_info, |
|
295 const std::wstring &file, |
|
296 uint32 attributes, |
|
297 FILE_BASIC_INFORMATION* file_info, |
|
298 NTSTATUS* nt_status) { |
|
299 // The only action supported is ASK_BROKER which means query the requested |
|
300 // file as specified. |
|
301 if (ASK_BROKER != eval_result) { |
|
302 *nt_status = STATUS_ACCESS_DENIED; |
|
303 return true; |
|
304 } |
|
305 |
|
306 NtQueryAttributesFileFunction NtQueryAttributesFile = NULL; |
|
307 ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); |
|
308 |
|
309 UNICODE_STRING uni_name = {0}; |
|
310 OBJECT_ATTRIBUTES obj_attributes = {0}; |
|
311 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); |
|
312 *nt_status = NtQueryAttributesFile(&obj_attributes, file_info); |
|
313 |
|
314 return true; |
|
315 } |
|
316 |
|
317 bool FileSystemPolicy::QueryFullAttributesFileAction( |
|
318 EvalResult eval_result, |
|
319 const ClientInfo& client_info, |
|
320 const std::wstring &file, |
|
321 uint32 attributes, |
|
322 FILE_NETWORK_OPEN_INFORMATION* file_info, |
|
323 NTSTATUS* nt_status) { |
|
324 // The only action supported is ASK_BROKER which means query the requested |
|
325 // file as specified. |
|
326 if (ASK_BROKER != eval_result) { |
|
327 *nt_status = STATUS_ACCESS_DENIED; |
|
328 return true; |
|
329 } |
|
330 |
|
331 NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = NULL; |
|
332 ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile); |
|
333 |
|
334 UNICODE_STRING uni_name = {0}; |
|
335 OBJECT_ATTRIBUTES obj_attributes = {0}; |
|
336 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); |
|
337 *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info); |
|
338 |
|
339 return true; |
|
340 } |
|
341 |
|
342 bool FileSystemPolicy::SetInformationFileAction( |
|
343 EvalResult eval_result, const ClientInfo& client_info, |
|
344 HANDLE target_file_handle, void* file_info, uint32 length, |
|
345 uint32 info_class, IO_STATUS_BLOCK* io_block, |
|
346 NTSTATUS* nt_status) { |
|
347 // The only action supported is ASK_BROKER which means open the requested |
|
348 // file as specified. |
|
349 if (ASK_BROKER != eval_result) { |
|
350 *nt_status = STATUS_ACCESS_DENIED; |
|
351 return true; |
|
352 } |
|
353 |
|
354 NtSetInformationFileFunction NtSetInformationFile = NULL; |
|
355 ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile); |
|
356 |
|
357 HANDLE local_handle = NULL; |
|
358 if (!::DuplicateHandle(client_info.process, target_file_handle, |
|
359 ::GetCurrentProcess(), &local_handle, 0, FALSE, |
|
360 DUPLICATE_SAME_ACCESS)) { |
|
361 *nt_status = STATUS_ACCESS_DENIED; |
|
362 return true; |
|
363 } |
|
364 |
|
365 base::win::ScopedHandle handle(local_handle); |
|
366 |
|
367 FILE_INFORMATION_CLASS file_info_class = |
|
368 static_cast<FILE_INFORMATION_CLASS>(info_class); |
|
369 *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length, |
|
370 file_info_class); |
|
371 |
|
372 return true; |
|
373 } |
|
374 |
|
375 bool PreProcessName(const std::wstring& path, std::wstring* new_path) { |
|
376 ConvertToLongPath(path, new_path); |
|
377 |
|
378 bool reparsed = false; |
|
379 if (ERROR_SUCCESS != IsReparsePoint(*new_path, &reparsed)) |
|
380 return false; |
|
381 |
|
382 // We can't process reparsed file. |
|
383 return !reparsed; |
|
384 } |
|
385 |
|
386 } // namespace sandbox |