• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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