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 "base/win/windows_version.h"
12 #include "sandbox/win/src/ipc_tags.h"
13 #include "sandbox/win/src/policy_engine_opcodes.h"
14 #include "sandbox/win/src/policy_params.h"
15 #include "sandbox/win/src/sandbox_utils.h"
16 #include "sandbox/win/src/sandbox_types.h"
17 #include "sandbox/win/src/win_utils.h"
21 NTSTATUS
NtCreateFileInTarget(HANDLE
* target_file_handle
,
22 ACCESS_MASK desired_access
,
23 OBJECT_ATTRIBUTES
* obj_attributes
,
24 IO_STATUS_BLOCK
* io_status_block
,
25 ULONG file_attributes
,
27 ULONG create_disposition
,
31 HANDLE target_process
) {
32 NtCreateFileFunction NtCreateFile
= NULL
;
33 ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile
);
35 HANDLE local_handle
= INVALID_HANDLE_VALUE
;
36 NTSTATUS status
= NtCreateFile(&local_handle
, desired_access
, obj_attributes
,
37 io_status_block
, NULL
, file_attributes
,
38 share_access
, create_disposition
,
39 create_options
, ea_buffer
, ea_lenght
);
40 if (!NT_SUCCESS(status
)) {
44 if (!sandbox::SameObject(local_handle
, obj_attributes
->ObjectName
->Buffer
)) {
45 // The handle points somewhere else. Fail the operation.
46 ::CloseHandle(local_handle
);
47 return STATUS_ACCESS_DENIED
;
50 if (!::DuplicateHandle(::GetCurrentProcess(), local_handle
,
51 target_process
, target_file_handle
, 0, FALSE
,
52 DUPLICATE_CLOSE_SOURCE
| DUPLICATE_SAME_ACCESS
)) {
53 return STATUS_ACCESS_DENIED
;
55 return STATUS_SUCCESS
;
58 // Get an initialized anonymous level Security QOS.
59 SECURITY_QUALITY_OF_SERVICE
GetAnonymousQOS() {
60 SECURITY_QUALITY_OF_SERVICE security_qos
= {0};
61 security_qos
.Length
= sizeof(security_qos
);
62 security_qos
.ImpersonationLevel
= SecurityAnonymous
;
63 // Set dynamic tracking so that a pipe doesn't capture the broker's token
64 security_qos
.ContextTrackingMode
= SECURITY_DYNAMIC_TRACKING
;
65 security_qos
.EffectiveOnly
= TRUE
;
74 bool FileSystemPolicy::GenerateRules(const wchar_t* name
,
75 TargetPolicy::Semantics semantics
,
76 LowLevelPolicy
* policy
) {
77 base::string16
mod_name(name
);
78 if (mod_name
.empty()) {
82 if (!PreProcessName(&mod_name
)) {
83 // The path to be added might contain a reparse point.
88 // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
89 // infrastructure to normalize names. In any case we need to escape the
91 if (_wcsnicmp(mod_name
.c_str(), kNTDevicePrefix
, kNTDevicePrefixLen
)) {
92 mod_name
= FixNTPrefixForMatch(mod_name
);
93 name
= mod_name
.c_str();
96 EvalResult result
= ASK_BROKER
;
98 // List of supported calls for the filesystem.
99 const unsigned kCallNtCreateFile
= 0x1;
100 const unsigned kCallNtOpenFile
= 0x2;
101 const unsigned kCallNtQueryAttributesFile
= 0x4;
102 const unsigned kCallNtQueryFullAttributesFile
= 0x8;
103 const unsigned kCallNtSetInfoRename
= 0x10;
105 DWORD rule_to_add
= kCallNtOpenFile
| kCallNtCreateFile
|
106 kCallNtQueryAttributesFile
|
107 kCallNtQueryFullAttributesFile
| kCallNtSetInfoRename
;
109 PolicyRule
create(result
);
110 PolicyRule
open(result
);
111 PolicyRule
query(result
);
112 PolicyRule
query_full(result
);
113 PolicyRule
rename(result
);
116 case TargetPolicy::FILES_ALLOW_DIR_ANY
: {
117 open
.AddNumberMatch(IF
, OpenFile::OPTIONS
, FILE_DIRECTORY_FILE
, AND
);
118 create
.AddNumberMatch(IF
, OpenFile::OPTIONS
, FILE_DIRECTORY_FILE
, AND
);
121 case TargetPolicy::FILES_ALLOW_READONLY
: {
122 // We consider all flags that are not known to be readonly as potentially
124 DWORD allowed_flags
= FILE_READ_DATA
| FILE_READ_ATTRIBUTES
|
125 FILE_READ_EA
| SYNCHRONIZE
| FILE_EXECUTE
|
126 GENERIC_READ
| GENERIC_EXECUTE
| READ_CONTROL
;
127 DWORD restricted_flags
= ~allowed_flags
;
128 open
.AddNumberMatch(IF_NOT
, OpenFile::ACCESS
, restricted_flags
, AND
);
129 open
.AddNumberMatch(IF
, OpenFile::DISPOSITION
, FILE_OPEN
, EQUAL
);
130 create
.AddNumberMatch(IF_NOT
, OpenFile::ACCESS
, restricted_flags
, AND
);
131 create
.AddNumberMatch(IF
, OpenFile::DISPOSITION
, FILE_OPEN
, EQUAL
);
133 // Read only access don't work for rename.
134 rule_to_add
&= ~kCallNtSetInfoRename
;
137 case TargetPolicy::FILES_ALLOW_QUERY
: {
138 // Here we don't want to add policy for the open or the create.
139 rule_to_add
&= ~(kCallNtOpenFile
| kCallNtCreateFile
|
140 kCallNtSetInfoRename
);
143 case TargetPolicy::FILES_ALLOW_ANY
: {
152 if ((rule_to_add
& kCallNtCreateFile
) &&
153 (!create
.AddStringMatch(IF
, OpenFile::NAME
, name
, CASE_INSENSITIVE
) ||
154 !policy
->AddRule(IPC_NTCREATEFILE_TAG
, &create
))) {
158 if ((rule_to_add
& kCallNtOpenFile
) &&
159 (!open
.AddStringMatch(IF
, OpenFile::NAME
, name
, CASE_INSENSITIVE
) ||
160 !policy
->AddRule(IPC_NTOPENFILE_TAG
, &open
))) {
164 if ((rule_to_add
& kCallNtQueryAttributesFile
) &&
165 (!query
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
) ||
166 !policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &query
))) {
170 if ((rule_to_add
& kCallNtQueryFullAttributesFile
) &&
171 (!query_full
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
)
172 || !policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
,
177 if ((rule_to_add
& kCallNtSetInfoRename
) &&
178 (!rename
.AddStringMatch(IF
, FileName::NAME
, name
, CASE_INSENSITIVE
) ||
179 !policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &rename
))) {
186 // Right now we insert two rules, to be evaluated before any user supplied rule:
187 // - go to the broker if the path doesn't look like the paths that we push on
188 // the policy (namely \??\something).
189 // - go to the broker if it looks like this is a short-name path.
191 // It is possible to add a rule to go to the broker in any case; it would look
193 // rule = new PolicyRule(ASK_BROKER);
194 // rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
195 // policy->AddRule(service, rule);
196 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy
* policy
) {
197 PolicyRule
format(ASK_BROKER
);
198 PolicyRule
short_name(ASK_BROKER
);
200 bool rv
= format
.AddNumberMatch(IF_NOT
, FileName::BROKER
, TRUE
, AND
);
201 rv
&= format
.AddStringMatch(IF_NOT
, FileName::NAME
, L
"\\/?/?\\*",
204 rv
&= short_name
.AddNumberMatch(IF_NOT
, FileName::BROKER
, TRUE
, AND
);
205 rv
&= short_name
.AddStringMatch(IF
, FileName::NAME
, L
"*~*", CASE_SENSITIVE
);
207 if (!rv
|| !policy
->AddRule(IPC_NTCREATEFILE_TAG
, &format
))
210 if (!policy
->AddRule(IPC_NTCREATEFILE_TAG
, &short_name
))
213 if (!policy
->AddRule(IPC_NTOPENFILE_TAG
, &format
))
216 if (!policy
->AddRule(IPC_NTOPENFILE_TAG
, &short_name
))
219 if (!policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &format
))
222 if (!policy
->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG
, &short_name
))
225 if (!policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
, &format
))
228 if (!policy
->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG
, &short_name
))
231 if (!policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &format
))
234 if (!policy
->AddRule(IPC_NTSETINFO_RENAME_TAG
, &short_name
))
240 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result
,
241 const ClientInfo
& client_info
,
242 const base::string16
&file
,
244 uint32 desired_access
,
245 uint32 file_attributes
,
247 uint32 create_disposition
,
248 uint32 create_options
,
251 ULONG_PTR
*io_information
) {
252 // The only action supported is ASK_BROKER which means create the requested
253 // file as specified.
254 if (ASK_BROKER
!= eval_result
) {
255 *nt_status
= STATUS_ACCESS_DENIED
;
258 IO_STATUS_BLOCK io_block
= {};
259 UNICODE_STRING uni_name
= {};
260 OBJECT_ATTRIBUTES obj_attributes
= {};
261 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
263 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
264 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
265 *nt_status
= NtCreateFileInTarget(handle
, desired_access
, &obj_attributes
,
266 &io_block
, file_attributes
, share_access
,
267 create_disposition
, create_options
, NULL
,
268 0, client_info
.process
);
270 *io_information
= io_block
.Information
;
274 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result
,
275 const ClientInfo
& client_info
,
276 const base::string16
&file
,
278 uint32 desired_access
,
283 ULONG_PTR
*io_information
) {
284 // The only action supported is ASK_BROKER which means open the requested
285 // file as specified.
286 if (ASK_BROKER
!= eval_result
) {
287 *nt_status
= STATUS_ACCESS_DENIED
;
290 // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and
291 // CreateDisposition = FILE_OPEN.
292 IO_STATUS_BLOCK io_block
= {};
293 UNICODE_STRING uni_name
= {};
294 OBJECT_ATTRIBUTES obj_attributes
= {};
295 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
297 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
298 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
299 *nt_status
= NtCreateFileInTarget(handle
, desired_access
, &obj_attributes
,
300 &io_block
, 0, share_access
, FILE_OPEN
,
301 open_options
, NULL
, 0,
302 client_info
.process
);
304 *io_information
= io_block
.Information
;
308 bool FileSystemPolicy::QueryAttributesFileAction(
309 EvalResult eval_result
,
310 const ClientInfo
& client_info
,
311 const base::string16
&file
,
313 FILE_BASIC_INFORMATION
* file_info
,
314 NTSTATUS
* nt_status
) {
315 // The only action supported is ASK_BROKER which means query the requested
316 // file as specified.
317 if (ASK_BROKER
!= eval_result
) {
318 *nt_status
= STATUS_ACCESS_DENIED
;
322 NtQueryAttributesFileFunction NtQueryAttributesFile
= NULL
;
323 ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile
);
325 UNICODE_STRING uni_name
= {0};
326 OBJECT_ATTRIBUTES obj_attributes
= {0};
327 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
329 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
330 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
331 *nt_status
= NtQueryAttributesFile(&obj_attributes
, file_info
);
336 bool FileSystemPolicy::QueryFullAttributesFileAction(
337 EvalResult eval_result
,
338 const ClientInfo
& client_info
,
339 const base::string16
&file
,
341 FILE_NETWORK_OPEN_INFORMATION
* file_info
,
342 NTSTATUS
* nt_status
) {
343 // The only action supported is ASK_BROKER which means query the requested
344 // file as specified.
345 if (ASK_BROKER
!= eval_result
) {
346 *nt_status
= STATUS_ACCESS_DENIED
;
350 NtQueryFullAttributesFileFunction NtQueryFullAttributesFile
= NULL
;
351 ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile
);
353 UNICODE_STRING uni_name
= {0};
354 OBJECT_ATTRIBUTES obj_attributes
= {0};
355 SECURITY_QUALITY_OF_SERVICE security_qos
= GetAnonymousQOS();
357 InitObjectAttribs(file
, attributes
, NULL
, &obj_attributes
,
358 &uni_name
, IsPipe(file
) ? &security_qos
: NULL
);
359 *nt_status
= NtQueryFullAttributesFile(&obj_attributes
, file_info
);
364 bool FileSystemPolicy::SetInformationFileAction(
365 EvalResult eval_result
, const ClientInfo
& client_info
,
366 HANDLE target_file_handle
, void* file_info
, uint32 length
,
367 uint32 info_class
, IO_STATUS_BLOCK
* io_block
,
368 NTSTATUS
* nt_status
) {
369 // The only action supported is ASK_BROKER which means open the requested
370 // file as specified.
371 if (ASK_BROKER
!= eval_result
) {
372 *nt_status
= STATUS_ACCESS_DENIED
;
376 NtSetInformationFileFunction NtSetInformationFile
= NULL
;
377 ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile
);
379 HANDLE local_handle
= NULL
;
380 if (!::DuplicateHandle(client_info
.process
, target_file_handle
,
381 ::GetCurrentProcess(), &local_handle
, 0, FALSE
,
382 DUPLICATE_SAME_ACCESS
)) {
383 *nt_status
= STATUS_ACCESS_DENIED
;
387 base::win::ScopedHandle
handle(local_handle
);
389 FILE_INFORMATION_CLASS file_info_class
=
390 static_cast<FILE_INFORMATION_CLASS
>(info_class
);
391 *nt_status
= NtSetInformationFile(local_handle
, io_block
, file_info
, length
,
397 bool PreProcessName(base::string16
* path
) {
398 ConvertToLongPath(path
);
400 if (ERROR_NOT_A_REPARSE_POINT
== IsReparsePoint(*path
))
403 // We can't process a reparsed file.
407 base::string16
FixNTPrefixForMatch(const base::string16
& name
) {
408 base::string16 mod_name
= name
;
410 // NT prefix escaped for rule matcher
411 const wchar_t kNTPrefixEscaped
[] = L
"\\/?/?\\";
412 const int kNTPrefixEscapedLen
= arraysize(kNTPrefixEscaped
) - 1;
414 if (0 != mod_name
.compare(0, kNTPrefixLen
, kNTPrefix
)) {
415 if (0 != mod_name
.compare(0, kNTPrefixEscapedLen
, kNTPrefixEscaped
)) {
416 // TODO(nsylvain): Find a better way to do name resolution. Right now we
417 // take the name and we expand it.
418 mod_name
.insert(0, kNTPrefixEscaped
);
421 // Start of name matches NT prefix, replace with escaped format
423 mod_name
.replace(0, kNTPrefixLen
, kNTPrefixEscaped
);
429 } // namespace sandbox