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