Bug 1173371 Part 1: Take Chromium commit 0e49d029d5a1a25d971880b9e44d67ac70b31a80 for sandbox code. r=aklotz

From Chromium commit comment:
Sandbox: Add support for file system policies that use implied device paths.

A policy rule of the form \HarddiskVolume0\Foo\bar allows sandboxed code
to use \\.\HarddiskVolume0\Foo\bar directly.
This commit is contained in:
Bob Owen 2016-02-01 08:59:00 +00:00
parent 7317711fff
commit 09e1c980e1
5 changed files with 229 additions and 63 deletions

View File

@ -9,6 +9,7 @@
#include <winioctl.h>
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "sandbox/win/src/filesystem_policy.h"
#include "sandbox/win/src/nt_internals.h"
#include "sandbox/win/src/sandbox.h"
@ -28,34 +29,47 @@ namespace sandbox {
const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
// Creates a file using different desired access. Returns if the call succeeded
// or not. The first argument in argv is the filename. If the second argument
// is "read", we try read only access. Otherwise we try read-write access.
// or not. The first argument in argv is the filename. The second argument
// determines the type of access and the dispositino of the file.
SBOX_TESTS_COMMAND int File_Create(int argc, wchar_t **argv) {
if (argc != 2)
return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
bool read = (_wcsicmp(argv[0], L"Read") == 0);
std::wstring operation(argv[0]);
if (read) {
if (operation == L"Read") {
base::win::ScopedHandle file1(CreateFile(
argv[1], GENERIC_READ, kSharing, NULL, OPEN_EXISTING, 0, NULL));
base::win::ScopedHandle file2(CreateFile(
argv[1], FILE_EXECUTE, kSharing, NULL, OPEN_EXISTING, 0, NULL));
if (file1.Get() && file2.Get())
return SBOX_TEST_SUCCEEDED;
return SBOX_TEST_DENIED;
} else {
if (file1.IsValid() == file2.IsValid())
return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED;
return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR;
} else if (operation == L"Write") {
base::win::ScopedHandle file1(CreateFile(
argv[1], GENERIC_ALL, kSharing, NULL, OPEN_EXISTING, 0, NULL));
base::win::ScopedHandle file2(CreateFile(
argv[1], GENERIC_READ | FILE_WRITE_DATA, kSharing, NULL, OPEN_EXISTING,
0, NULL));
if (file1.Get() && file2.Get())
return SBOX_TEST_SUCCEEDED;
return SBOX_TEST_DENIED;
if (file1.IsValid() == file2.IsValid())
return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED;
return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR;
} else if (operation == L"ReadCreate") {
base::win::ScopedHandle file2(CreateFile(
argv[1], GENERIC_READ, kSharing, NULL, CREATE_NEW, 0, NULL));
base::win::ScopedHandle file1(CreateFile(
argv[1], GENERIC_READ, kSharing, NULL, CREATE_ALWAYS, 0, NULL));
if (file1.IsValid() == file2.IsValid())
return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED;
return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR;
}
return SBOX_TEST_INVALID_PARAMETER;
}
SBOX_TESTS_COMMAND int File_Win32Create(int argc, wchar_t **argv) {
@ -96,7 +110,7 @@ SBOX_TESTS_COMMAND int File_CreateSys32(int argc, wchar_t **argv) {
return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
base::string16 file(argv[0]);
if (0 != _wcsnicmp(file.c_str(), kNTObjManPrefix, kNTObjManPrefixLen))
if (0 != _wcsnicmp(file.c_str(), kNTDevicePrefix, kNTDevicePrefixLen))
file = MakePathToSys(argv[0], true);
UNICODE_STRING object_name;
@ -266,6 +280,9 @@ TEST(FilePolicyTest, AllowNtCreateCalc) {
}
TEST(FilePolicyTest, AllowNtCreateWithNativePath) {
if (base::win::GetVersion() < base::win::VERSION_WIN7)
return;
base::string16 calc = MakePathToSys(L"calc.exe", false);
base::string16 nt_path;
ASSERT_TRUE(GetNtPathFromWin32Path(calc, &nt_path));
@ -295,15 +312,21 @@ TEST(FilePolicyTest, AllowReadOnly) {
wchar_t command_read[MAX_PATH + 20] = {0};
wsprintf(command_read, L"File_Create Read \"%ls\"", temp_file_name);
wchar_t command_read_create[MAX_PATH + 20] = {0};
wsprintf(command_read_create, L"File_Create ReadCreate \"%ls\"",
temp_file_name);
wchar_t command_write[MAX_PATH + 20] = {0};
wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name);
// Verify that we have read access after revert.
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_read));
// Verify that we cannot create the file after revert.
EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_read_create));
// Verify that we don't have write access after revert.
EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write));
// Verify that we have read access after revert.
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_read));
// Verify that we really have write access to the file.
runner.SetTestState(BEFORE_REVERT);
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write));
@ -311,6 +334,34 @@ TEST(FilePolicyTest, AllowReadOnly) {
DeleteFile(temp_file_name);
}
// Tests support of "\\\\.\\DeviceName" kind of paths.
TEST(FilePolicyTest, AllowImplicitDeviceName) {
if (base::win::GetVersion() < base::win::VERSION_WIN7)
return;
TestRunner runner;
wchar_t temp_directory[MAX_PATH];
wchar_t temp_file_name[MAX_PATH];
ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u);
ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u);
std::wstring path;
EXPECT_TRUE(ConvertToLongPath(temp_file_name, &path));
EXPECT_TRUE(GetNtPathFromWin32Path(path, &path));
path = path.substr(sandbox::kNTDevicePrefixLen);
wchar_t command[MAX_PATH + 20] = {0};
wsprintf(command, L"File_Create Read \"\\\\.\\%ls\"", path.c_str());
path = std::wstring(kNTPrefix) + path;
EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command));
EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, path.c_str()));
EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command));
DeleteFile(temp_file_name);
}
TEST(FilePolicyTest, AllowWildcard) {
TestRunner runner;
@ -519,8 +570,7 @@ TEST(FilePolicyTest, FileGetDiskSpace) {
EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe"));
}
// http://crbug.com/146944
TEST(FilePolicyTest, DISABLED_TestReparsePoint) {
TEST(FilePolicyTest, TestReparsePoint) {
TestRunner runner;
// Create a temp file because we need write access to it.

View File

@ -8,6 +8,7 @@
#include "base/logging.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "sandbox/win/src/ipc_tags.h"
#include "sandbox/win/src/policy_engine_opcodes.h"
#include "sandbox/win/src/policy_params.h"
@ -66,18 +67,16 @@ bool FileSystemPolicy::GenerateRules(const wchar_t* name,
return false;
}
// Don't do any pre-processing if the name starts like the the native
// object manager style.
if (0 != _wcsnicmp(mod_name.c_str(), kNTObjManPrefix, kNTObjManPrefixLen)) {
// TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
// infrastructure to normalize names. In any case we need to escape the
// question marks.
if (!PreProcessName(mod_name, &mod_name)) {
// The path to be added might contain a reparse point.
NOTREACHED();
return false;
}
if (!PreProcessName(mod_name, &mod_name)) {
// The path to be added might contain a reparse point.
NOTREACHED();
return false;
}
// TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
// infrastructure to normalize names. In any case we need to escape the
// question marks.
if (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) {
mod_name = FixNTPrefixForMatch(mod_name);
name = mod_name.c_str();
}

View File

@ -7,6 +7,7 @@
#include <map>
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "base/win/pe_image.h"
#include "sandbox/win/src/internal_types.h"
#include "sandbox/win/src/nt_internals.h"
@ -33,6 +34,78 @@ const KnownReservedKey kKnownKey[] = {
{ L"HKEY_DYN_DATA", HKEY_DYN_DATA}
};
// These functions perform case independent path comparisons.
bool EqualPath(const base::string16& first, const base::string16& second) {
return _wcsicmp(first.c_str(), second.c_str()) == 0;
}
bool EqualPath(const base::string16& first, size_t first_offset,
const base::string16& second, size_t second_offset) {
return _wcsicmp(first.c_str() + first_offset,
second.c_str() + second_offset) == 0;
}
bool EqualPath(const base::string16& first,
const wchar_t* second, size_t second_len) {
return _wcsnicmp(first.c_str(), second, second_len) == 0;
}
bool EqualPath(const base::string16& first, size_t first_offset,
const wchar_t* second, size_t second_len) {
return _wcsnicmp(first.c_str() + first_offset, second, second_len) == 0;
}
// Returns true if |path| starts with "\??\" and returns a path without that
// component.
bool IsNTPath(const base::string16& path, base::string16* trimmed_path ) {
if ((path.size() < sandbox::kNTPrefixLen) ||
(0 != path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))) {
*trimmed_path = path;
return false;
}
*trimmed_path = path.substr(sandbox::kNTPrefixLen);
return true;
}
// Returns true if |path| starts with "\Device\" and returns a path without that
// component.
bool IsDevicePath(const base::string16& path, base::string16* trimmed_path ) {
if ((path.size() < sandbox::kNTDevicePrefixLen) ||
(!EqualPath(path, sandbox::kNTDevicePrefix,
sandbox::kNTDevicePrefixLen))) {
*trimmed_path = path;
return false;
}
*trimmed_path = path.substr(sandbox::kNTDevicePrefixLen);
return true;
}
bool StartsWithDriveLetter(const base::string16& path) {
if (path.size() < 3)
return false;
if (path[1] != L':' || path[2] != L'\\')
return false;
return (path[0] >= 'a' && path[0] <= 'z') ||
(path[0] >= 'A' && path[0] <= 'Z');
}
const wchar_t kNTDotPrefix[] = L"\\\\.\\";
const size_t kNTDotPrefixLen = arraysize(kNTDotPrefix) - 1;
// Removes "\\\\.\\" from the path.
void RemoveImpliedDevice(base::string16* path) {
if (0 == path->compare(0, kNTDotPrefixLen, kNTDotPrefix))
*path = path->substr(kNTDotPrefixLen);
}
} // namespace
namespace sandbox {
// Returns true if the provided path points to a pipe.
bool IsPipe(const base::string16& path) {
size_t start = 0;
@ -40,13 +113,12 @@ bool IsPipe(const base::string16& path) {
start = sandbox::kNTPrefixLen;
const wchar_t kPipe[] = L"pipe\\";
return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe));
if (path.size() < start + arraysize(kPipe) - 1)
return false;
return EqualPath(path, start, kPipe, arraysize(kPipe) - 1);
}
} // namespace
namespace sandbox {
HKEY GetReservedKeyFromName(const base::string16& name) {
for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
if (name == kKnownKey[i].name)
@ -80,20 +152,33 @@ bool ResolveRegistryName(base::string16 name, base::string16* resolved_name) {
return false;
}
// |full_path| can have any of the following forms:
// \??\c:\some\foo\bar
// \Device\HarddiskVolume0\some\foo\bar
// \??\HarddiskVolume0\some\foo\bar
DWORD IsReparsePoint(const base::string16& full_path, bool* result) {
base::string16 path = full_path;
// Remove the nt prefix.
if (0 == path.compare(0, kNTPrefixLen, kNTPrefix))
path = path.substr(kNTPrefixLen);
// Check if it's a pipe. We can't query the attributes of a pipe.
if (IsPipe(path)) {
if (IsPipe(full_path)) {
*result = FALSE;
return ERROR_SUCCESS;
}
base::string16 path;
bool nt_path = IsNTPath(full_path, &path);
bool has_drive = StartsWithDriveLetter(path);
bool is_device_path = IsDevicePath(path, &path);
if (!has_drive && !is_device_path && !nt_path)
return ERROR_INVALID_NAME;
bool added_implied_device = false;
if (!has_drive) {
path = base::string16(kNTDotPrefix) + path;
added_implied_device = true;
}
base::string16::size_type last_pos = base::string16::npos;
bool passed_once = false;
do {
path = path.substr(0, last_pos);
@ -105,6 +190,10 @@ DWORD IsReparsePoint(const base::string16& full_path, bool* result) {
error != ERROR_PATH_NOT_FOUND &&
error != ERROR_INVALID_NAME) {
// Unexpected error.
if (passed_once && added_implied_device &&
(path.rfind(L'\\') == kNTDotPrefixLen - 1)) {
break;
}
NOTREACHED_NT();
return error;
}
@ -114,6 +203,7 @@ DWORD IsReparsePoint(const base::string16& full_path, bool* result) {
return ERROR_SUCCESS;
}
passed_once = true;
last_pos = path.rfind(L'\\');
} while (last_pos > 2); // Skip root dir.
@ -121,42 +211,48 @@ DWORD IsReparsePoint(const base::string16& full_path, bool* result) {
return ERROR_SUCCESS;
}
// We get a |full_path| of the form \??\c:\some\foo\bar, and the name that
// We get a |full_path| of the forms accepted by IsReparsePoint(), and the name
// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
bool SameObject(HANDLE handle, const wchar_t* full_path) {
base::string16 path(full_path);
DCHECK_NT(!path.empty());
// Check if it's a pipe.
if (IsPipe(path))
if (IsPipe(full_path))
return true;
base::string16 actual_path;
if (!GetPathFromHandle(handle, &actual_path))
return false;
base::string16 path(full_path);
DCHECK_NT(!path.empty());
// This may end with a backslash.
const wchar_t kBackslash = '\\';
if (path[path.length() - 1] == kBackslash)
path = path.substr(0, path.length() - 1);
// Perfect match (case-insesitive check).
if (0 == _wcsicmp(actual_path.c_str(), path.c_str()))
if (EqualPath(actual_path, path))
return true;
// Look for the drive letter.
size_t colon_pos = path.find(L':');
if (colon_pos == 0 || colon_pos == base::string16::npos)
return false;
bool nt_path = IsNTPath(path, &path);
bool has_drive = StartsWithDriveLetter(path);
// Only one character for the drive.
if (colon_pos > 1 && path[colon_pos - 2] != kBackslash)
if (!has_drive && nt_path) {
base::string16 simple_actual_path;
if (!IsDevicePath(actual_path, &simple_actual_path))
return false;
// Perfect match (case-insesitive check).
return (EqualPath(simple_actual_path, path));
}
if (!has_drive)
return false;
// We only need 3 chars, but let's alloc a buffer for four.
wchar_t drive[4] = {0};
wchar_t vol_name[MAX_PATH];
memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive));
memcpy(drive, &path[0], 2 * sizeof(*drive));
// We'll get a double null terminated string.
DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH);
@ -167,28 +263,39 @@ bool SameObject(HANDLE handle, const wchar_t* full_path) {
vol_length = static_cast<DWORD>(wcslen(vol_name));
// The two paths should be the same length.
if (vol_length + path.size() - (colon_pos + 1) != actual_path.size())
if (vol_length + path.size() - 2 != actual_path.size())
return false;
// Check up to the drive letter.
if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length))
if (!EqualPath(actual_path, vol_name, vol_length))
return false;
// Check the path after the drive letter.
if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1]))
if (!EqualPath(actual_path, vol_length, path, 2))
return false;
return true;
}
// Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already
// expanded.
bool ConvertToLongPath(const base::string16& short_path,
base::string16* long_path) {
// Check if the path is a NT path.
bool is_nt_path = false;
base::string16 path = short_path;
if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) {
path = path.substr(kNTPrefixLen);
is_nt_path = true;
if (IsPipe(short_path)) {
// TODO(rvargas): Change the signature to use a single argument.
long_path->assign(short_path);
return true;
}
base::string16 path;
if (IsDevicePath(short_path, &path))
return false;
bool is_nt_path = IsNTPath(path, &path);
bool added_implied_device = false;
if (!StartsWithDriveLetter(path) && is_nt_path) {
path = base::string16(kNTDotPrefix) + path;
added_implied_device = true;
}
DWORD size = MAX_PATH;
@ -224,6 +331,9 @@ bool ConvertToLongPath(const base::string16& short_path,
}
if (return_value != 0) {
if (added_implied_device)
RemoveImpliedDevice(&path);
if (is_nt_path) {
*long_path = kNTPrefix;
*long_path += path;

View File

@ -17,8 +17,8 @@ namespace sandbox {
const wchar_t kNTPrefix[] = L"\\??\\";
const size_t kNTPrefixLen = arraysize(kNTPrefix) - 1;
const wchar_t kNTObjManPrefix[] = L"\\Device\\";
const size_t kNTObjManPrefixLen = arraysize(kNTObjManPrefix) - 1;
const wchar_t kNTDevicePrefix[] = L"\\Device\\";
const size_t kNTDevicePrefixLen = arraysize(kNTDevicePrefix) - 1;
// Automatically acquires and releases a lock when the object is
// is destroyed.
@ -105,6 +105,9 @@ bool ResolveRegistryName(base::string16 name, base::string16* resolved_name);
bool WriteProtectedChildMemory(HANDLE child_process, void* address,
const void* buffer, size_t length);
// Returns true if the provided path points to a pipe.
bool IsPipe(const base::string16& path);
} // namespace sandbox
// Resolves a function name in NTDLL to a function pointer. The second parameter

View File

@ -5,3 +5,7 @@ b533d6533585377edd63ec6500469f6c4fba602a chromium/sandbox/win/src/sharedmem_ip
034bd64db1806d85b2ceacc736074ac07722af4a chromium/sandbox/win/src/service_resolver_64.cc
de2078cfbbb6770791d32575a1a72a288e6d66a6 chromium/sandbox/win/src/target_services.cc
de2078cfbbb6770791d32575a1a72a288e6d66a6 chromium/sandbox/win/src/target_services.h
0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/file_policy_test.cc
0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/filesystem_policy.cc
0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/win_utils.cc
0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/win_utils.h