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
;
57 // Get an initialized anonymous level Security QOS.
58 SECURITY_QUALITY_OF_SERVICE
GetAnonymousQOS() {
59 SECURITY_QUALITY_OF_SERVICE security_qos
= {0};
60 security_qos
.Length
= sizeof(security_qos
);
61 security_qos
.ImpersonationLevel
= SecurityAnonymous
;
62 // Set dynamic tracking so that a pipe doesn't capture the broker's token
63 security_qos
.ContextTrackingMode
= SECURITY_DYNAMIC_TRACKING
;
64 security_qos
.EffectiveOnly
= TRUE
;
73 bool FileSystemPolicy::GenerateRules(const wchar_t* name
,
74 TargetPolicy::Semantics semantics
,
75 LowLevelPolicy
* policy
) {
76 base::string16
mod_name(name
);
77 if (mod_name
.empty()) {
81 // Don't do any pre-processing if the name starts like the the native
82 // object manager style.
83 if (0 != _wcsnicmp(mod_name
.c_str(), kNTObjManPrefix
, kNTObjManPrefixLen
)) {
84 // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
85 // infrastructure to normalize names. In any case we need to escape the
87 if (!PreProcessName(mod_name
, &mod_name
)) {
88 // The path to be added might contain a reparse point.
93 mod_name
= FixNTPrefixForMatch(mod_name
);
94 name
= mod_name
.c_str();
97 EvalResult result
= ASK_BROKER
;
99 // List of supported calls for the filesystem.
100 const unsigned kCallNtCreateFile
= 0x1;
101 const unsigned kCallNtOpenFile
= 0x2;
102 const unsigned kCallNtQueryAttributesFile
= 0x4;
103 const unsigned kCallNtQueryFullAttributesFile
= 0x8;
104 const unsigned kCallNtSetInfoRename
= 0x10;
106 DWORD rule_to_add
= kCallNtOpenFile
| kCallNtCreateFile
|
107 kCallNtQueryAttributesFile
|
108 kCallNtQueryFullAttributesFile
| kCallNtSetInfoRename
;
110 PolicyRule
create(result
);
111 PolicyRule
open(result
);
112 PolicyRule
query(result
);
113 PolicyRule
query_full(result
);
114 PolicyRule
rename(result
);
117 case TargetPolicy::FILES_ALLOW_DIR_ANY
: {
118 open
.AddNumberMatch(IF
, OpenFile::OPTIONS
, FILE_DIRECTORY_FILE
, AND
);
119 create
.AddNumberMatch(IF
, OpenFile::OPTIONS
, FILE_DIRECTORY_FILE
, AND
);
122 case TargetPolicy::FILES_ALLOW_READONLY
: {
123 // We consider all flags that are not known to be readonly as potentially
125 DWORD allowed_flags
= FILE_READ_DATA
| FILE_READ_ATTRIBUTES
|
126 FILE_READ_EA
| SYNCHRONIZE
| FILE_EXECUTE
|
127 GENERIC_READ
| GENERIC_EXECUTE
| READ_CONTROL
;
128 DWORD restricted_flags
= ~allowed_flags
;
129 open
.AddNumberMatch(IF_NOT
, OpenFile::ACCESS
, restricted_flags
, AND
);
130 open
.AddNumberMatch(IF
, OpenFile::DISPOSITION
, FILE_OPEN
, EQUAL
);
131 create
.AddNumberMatch(IF_NOT
, OpenFile::ACCESS
, restricted_flags
, AND
);
132 create
.AddNumberMatch(IF
, OpenFile::DISPOSITION
, FILE_OPEN
, EQUAL
);
134 // Read only access don't work for rename.
135 rule_to_add
&= ~kCallNtSetInfoRename
;
138 case TargetPolicy::FILES_ALLOW_QUERY
: {
139 // Here we don't want to add policy for the open or the create.
140 rule_to_add
&= ~(kCallNtOpenFile
| kCallNtCreateFile
|
141 kCallNtSetInfoRename
);
144 case TargetPolicy::FILES_ALLOW_ANY
: {
153 if ((rule_to_add
& kCallNtCreateFile
) &&
154 (!create
.AddStringMatch(IF
, OpenFile::NAME
, name
, CASE_INSENSITIVE
) ||
155 !policy
->AddRule(IPC_NTCREATEFILE_TAG
, &create
))) {
159 if ((rule_to_add
& kCallNtOpenFile
) &&
160 (!open
.AddStringMatch(IF
, OpenFile::NAME
, name
, CASE_INSENSITIVE
) ||
161 !policy
->AddRule(IPC_NTOPENFILE_TAG
, &open
))) {
165 if ((rule_to_add
& kCallNtQueryAttributesFile
) &&
166 (!query
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
) ||
167 !policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &query
))) {
171 if ((rule_to_add
& kCallNtQueryFullAttributesFile
) &&
172 (!query_full
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
)
173 || !policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
,
178 if ((rule_to_add
& kCallNtSetInfoRename
) &&
179 (!rename
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
) ||
180 !policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &rename
))) {
187 // Right now we insert two rules, to be evaluated before any user supplied rule:
188 // - go to the broker if the path doesn't look like the paths that we push on
189 // the policy (namely \??\something).
190 // - go to the broker if it looks like this is a short-name path.
192 // It is possible to add a rule to go to the broker in any case; it would look
194 // rule = new PolicyRule(ASK_BROKER);
195 // rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
196 // policy->AddRule(service, rule);
197 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy
* policy
) {
198 PolicyRule
format(ASK_BROKER
);
199 PolicyRule
short_name(ASK_BROKER
);
201 bool rv
= format
.AddNumberMatch(IF_NOT
, FileName::BROKER
, TRUE
, AND
);
202 rv
&= format
.AddStringMatch(IF_NOT
, FileName::NAME
, L
"\\/?/?\\*",
205 rv
&= short_name
.AddNumberMatch(IF_NOT
, FileName::BROKER
, TRUE
, AND
);
206 rv
&= short_name
.AddStringMatch(IF
, FileName::NAME
, L
"*~*", CASE_SENSITIVE
);
208 if (!rv
|| !policy
->AddRule(IPC_NTCREATEFILE_TAG
, &format
))
211 if (!policy
->AddRule(IPC_NTCREATEFILE_TAG
, &short_name
))
214 if (!policy
->AddRule(IPC_NTOPENFILE_TAG
, &format
))
217 if (!policy
->AddRule(IPC_NTOPENFILE_TAG
, &short_name
))
220 if (!policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &format
))
223 if (!policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &short_name
))
226 if (!policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
, &format
))
229 if (!policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
, &short_name
))
232 if (!policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &format
))
235 if (!policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &short_name
))
241 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result
,
242 const ClientInfo
& client_info
,
243 const base::string16
&file
,
245 uint32 desired_access
,
246 uint32 file_attributes
,
248 uint32 create_disposition
,
249 uint32 create_options
,
252 ULONG_PTR
*io_information
) {
253 // The only action supported is ASK_BROKER which means create the requested
254 // file as specified.
255 if (ASK_BROKER
!= eval_result
) {
256 *nt_status
= STATUS_ACCESS_DENIED
;
259 IO_STATUS_BLOCK io_block
= {0};
260 UNICODE_STRING uni_name
= {0};
261 OBJECT_ATTRIBUTES obj_attributes
= {0};
262 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
264 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
265 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
266 *nt_status
= NtCreateFileInTarget(handle
, desired_access
, &obj_attributes
,
267 &io_block
, file_attributes
, share_access
,
268 create_disposition
, create_options
, NULL
,
269 0, client_info
.process
);
271 *io_information
= io_block
.Information
;
275 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result
,
276 const ClientInfo
& client_info
,
277 const base::string16
&file
,
279 uint32 desired_access
,
284 ULONG_PTR
*io_information
) {
285 // The only action supported is ASK_BROKER which means open the requested
286 // file as specified.
287 if (ASK_BROKER
!= eval_result
) {
288 *nt_status
= STATUS_ACCESS_DENIED
;
291 // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and
292 // CreateDisposition = FILE_OPEN.
293 IO_STATUS_BLOCK io_block
= {0};
294 UNICODE_STRING uni_name
= {0};
295 OBJECT_ATTRIBUTES obj_attributes
= {0};
296 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
298 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
299 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
300 *nt_status
= NtCreateFileInTarget(handle
, desired_access
, &obj_attributes
,
301 &io_block
, 0, share_access
, FILE_OPEN
,
302 open_options
, NULL
, 0,
303 client_info
.process
);
305 *io_information
= io_block
.Information
;
309 bool FileSystemPolicy::QueryAttributesFileAction(
310 EvalResult eval_result
,
311 const ClientInfo
& client_info
,
312 const base::string16
&file
,
314 FILE_BASIC_INFORMATION
* file_info
,
315 NTSTATUS
* nt_status
) {
316 // The only action supported is ASK_BROKER which means query the requested
317 // file as specified.
318 if (ASK_BROKER
!= eval_result
) {
319 *nt_status
= STATUS_ACCESS_DENIED
;
323 NtQueryAttributesFileFunction NtQueryAttributesFile
= NULL
;
324 ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile
);
326 UNICODE_STRING uni_name
= {0};
327 OBJECT_ATTRIBUTES obj_attributes
= {0};
328 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
330 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
331 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
332 *nt_status
= NtQueryAttributesFile(&obj_attributes
, file_info
);
337 bool FileSystemPolicy::QueryFullAttributesFileAction(
338 EvalResult eval_result
,
339 const ClientInfo
& client_info
,
340 const base::string16
&file
,
342 FILE_NETWORK_OPEN_INFORMATION
* file_info
,
343 NTSTATUS
* nt_status
) {
344 // The only action supported is ASK_BROKER which means query the requested
345 // file as specified.
346 if (ASK_BROKER
!= eval_result
) {
347 *nt_status
= STATUS_ACCESS_DENIED
;
351 NtQueryFullAttributesFileFunction NtQueryFullAttributesFile
= NULL
;
352 ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile
);
354 UNICODE_STRING uni_name
= {0};
355 OBJECT_ATTRIBUTES obj_attributes
= {0};
356 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
358 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
359 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
360 *nt_status
= NtQueryFullAttributesFile(&obj_attributes
, file_info
);
365 bool FileSystemPolicy::SetInformationFileAction(
366 EvalResult eval_result
, const ClientInfo
& client_info
,
367 HANDLE target_file_handle
, void* file_info
, uint32 length
,
368 uint32 info_class
, IO_STATUS_BLOCK
* io_block
,
369 NTSTATUS
* nt_status
) {
370 // The only action supported is ASK_BROKER which means open the requested
371 // file as specified.
372 if (ASK_BROKER
!= eval_result
) {
373 *nt_status
= STATUS_ACCESS_DENIED
;
377 NtSetInformationFileFunction NtSetInformationFile
= NULL
;
378 ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile
);
380 HANDLE local_handle
= NULL
;
381 if (!::DuplicateHandle(client_info
.process
, target_file_handle
,
382 ::GetCurrentProcess(), &local_handle
, 0, FALSE
,
383 DUPLICATE_SAME_ACCESS
)) {
384 *nt_status
= STATUS_ACCESS_DENIED
;
388 base::win::ScopedHandle
handle(local_handle
);
390 FILE_INFORMATION_CLASS file_info_class
=
391 static_cast<FILE_INFORMATION_CLASS
>(info_class
);
392 *nt_status
= NtSetInformationFile(local_handle
, io_block
, file_info
, length
,
398 bool PreProcessName(const base::string16
& path
, base::string16
* new_path
) {
399 ConvertToLongPath(path
, new_path
);
401 bool reparsed
= false;
402 if (ERROR_SUCCESS
!= IsReparsePoint(*new_path
, &reparsed
))
405 // We can't process reparsed file.
409 base::string16
FixNTPrefixForMatch(const base::string16
& name
) {
410 base::string16 mod_name
= name
;
412 // NT prefix escaped for rule matcher
413 const wchar_t kNTPrefixEscaped
[] = L
"\\/?/?\\";
414 const int kNTPrefixEscapedLen
= arraysize(kNTPrefixEscaped
) - 1;
416 if (0 != mod_name
.compare(0, kNTPrefixLen
, kNTPrefix
)) {
417 if (0 != mod_name
.compare(0, kNTPrefixEscapedLen
, kNTPrefixEscaped
)) {
418 // TODO(nsylvain): Find a better way to do name resolution. Right now we
419 // take the name and we expand it.
420 mod_name
.insert(0, kNTPrefixEscaped
);
423 // Start of name matches NT prefix, replace with escaped format
425 mod_name
.replace(0, kNTPrefixLen
, kNTPrefixEscaped
);
431 } // namespace sandbox