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
18namespace {
19
20NTSTATUS 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
59namespace sandbox {
60
61bool 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);
183bool 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
227bool 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
258bool 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
289bool 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
314bool 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
339bool 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
372bool 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
383base::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