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.
7 #include "sandbox/win/src/filesystem_policy.h"
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"
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
,
26 ULONG create_disposition
,
30 HANDLE target_process
) {
31 NtCreateFileFunction NtCreateFile
= NULL
;
32 ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile
);
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
)) {
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
;
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
;
54 return STATUS_SUCCESS
;
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()) {
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
75 if (!PreProcessName(mod_name
, &mod_name
)) {
76 // The path to be added might contain a reparse point.
81 mod_name
= FixNTPrefixForMatch(mod_name
);
82 name
= mod_name
.c_str();
85 EvalResult result
= ASK_BROKER
;
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;
94 DWORD rule_to_add
= kCallNtOpenFile
| kCallNtCreateFile
|
95 kCallNtQueryAttributesFile
|
96 kCallNtQueryFullAttributesFile
| kCallNtSetInfoRename
;
98 PolicyRule
create(result
);
99 PolicyRule
open(result
);
100 PolicyRule
query(result
);
101 PolicyRule
query_full(result
);
102 PolicyRule
rename(result
);
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
);
110 case TargetPolicy::FILES_ALLOW_READONLY
: {
111 // We consider all flags that are not known to be readonly as potentially
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
);
120 // Read only access don't work for rename.
121 rule_to_add
&= ~kCallNtSetInfoRename
;
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
);
130 case TargetPolicy::FILES_ALLOW_ANY
: {
139 if ((rule_to_add
& kCallNtCreateFile
) &&
140 (!create
.AddStringMatch(IF
, OpenFile::NAME
, name
, CASE_INSENSITIVE
) ||
141 !policy
->AddRule(IPC_NTCREATEFILE_TAG
, &create
))) {
145 if ((rule_to_add
& kCallNtOpenFile
) &&
146 (!open
.AddStringMatch(IF
, OpenFile::NAME
, name
, CASE_INSENSITIVE
) ||
147 !policy
->AddRule(IPC_NTOPENFILE_TAG
, &open
))) {
151 if ((rule_to_add
& kCallNtQueryAttributesFile
) &&
152 (!query
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
) ||
153 !policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &query
))) {
157 if ((rule_to_add
& kCallNtQueryFullAttributesFile
) &&
158 (!query_full
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
)
159 || !policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
,
164 if ((rule_to_add
& kCallNtSetInfoRename
) &&
165 (!rename
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
) ||
166 !policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &rename
))) {
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.
178 // It is possible to add a rule to go to the broker in any case; it would look
180 // rule = new PolicyRule(ASK_BROKER);
181 // rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
182 // policy->AddRule(service, rule);
183 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy
* policy
) {
184 PolicyRule
format(ASK_BROKER
);
185 PolicyRule
short_name(ASK_BROKER
);
187 bool rv
= format
.AddNumberMatch(IF_NOT
, FileName::BROKER
, TRUE
, AND
);
188 rv
&= format
.AddStringMatch(IF_NOT
, FileName::NAME
, L
"\\/?/?\\*",
191 rv
&= short_name
.AddNumberMatch(IF_NOT
, FileName::BROKER
, TRUE
, AND
);
192 rv
&= short_name
.AddStringMatch(IF
, FileName::NAME
, L
"*~*", CASE_SENSITIVE
);
194 if (!rv
|| !policy
->AddRule(IPC_NTCREATEFILE_TAG
, &format
))
197 if (!policy
->AddRule(IPC_NTCREATEFILE_TAG
, &short_name
))
200 if (!policy
->AddRule(IPC_NTOPENFILE_TAG
, &format
))
203 if (!policy
->AddRule(IPC_NTOPENFILE_TAG
, &short_name
))
206 if (!policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &format
))
209 if (!policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &short_name
))
212 if (!policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
, &format
))
215 if (!policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
, &short_name
))
218 if (!policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &format
))
221 if (!policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &short_name
))
227 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result
,
228 const ClientInfo
& client_info
,
229 const base::string16
&file
,
231 uint32 desired_access
,
232 uint32 file_attributes
,
234 uint32 create_disposition
,
235 uint32 create_options
,
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
;
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
);
254 *io_information
= io_block
.Information
;
258 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result
,
259 const ClientInfo
& client_info
,
260 const base::string16
&file
,
262 uint32 desired_access
,
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
;
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
);
285 *io_information
= io_block
.Information
;
289 bool FileSystemPolicy::QueryAttributesFileAction(
290 EvalResult eval_result
,
291 const ClientInfo
& client_info
,
292 const base::string16
&file
,
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
;
303 NtQueryAttributesFileFunction NtQueryAttributesFile
= NULL
;
304 ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile
);
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
);
314 bool FileSystemPolicy::QueryFullAttributesFileAction(
315 EvalResult eval_result
,
316 const ClientInfo
& client_info
,
317 const base::string16
&file
,
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
;
328 NtQueryFullAttributesFileFunction NtQueryFullAttributesFile
= NULL
;
329 ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile
);
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
);
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
;
351 NtSetInformationFileFunction NtSetInformationFile
= NULL
;
352 ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile
);
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
;
362 base::win::ScopedHandle
handle(local_handle
);
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
,
372 bool PreProcessName(const base::string16
& path
, base::string16
* new_path
) {
373 ConvertToLongPath(path
, new_path
);
375 bool reparsed
= false;
376 if (ERROR_SUCCESS
!= IsReparsePoint(*new_path
, &reparsed
))
379 // We can't process reparsed file.
383 base::string16
FixNTPrefixForMatch(const base::string16
& name
) {
384 base::string16 mod_name
= name
;
386 // NT prefix escaped for rule matcher
387 const wchar_t kNTPrefixEscaped
[] = L
"\\/?/?\\";
388 const int kNTPrefixEscapedLen
= arraysize(kNTPrefixEscaped
) - 1;
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
);
397 // Start of name matches NT prefix, replace with escaped format
399 mod_name
.replace(0, kNTPrefixLen
, kNTPrefixEscaped
);
405 } // namespace sandbox