blob: 5d58f73e683507224aa17cfd48ead7d86142b378 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/chrome_cleaner/engines/common/registry_util.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "chrome/chrome_cleaner/strings/wstring_embedded_nulls.h"
#include "sandbox/win/src/win_utils.h"
using chrome_cleaner::WStringEmbeddedNulls;
namespace chrome_cleaner_sandbox {
void InitUnicodeString(UNICODE_STRING* unicode_string,
std::vector<wchar_t>* data) {
DCHECK(data && !data->empty() && data->back() == L'\0');
// As per
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx
// Length is the length of the string in BYTES, NOT including the
// terminating NULL char, MaximumLength is the length of the string in BYTES
// INCLUDING the terminating NULL char.
unicode_string->Length =
static_cast<USHORT>((data->size() - 1) * sizeof(wchar_t));
unicode_string->MaximumLength =
static_cast<USHORT>(unicode_string->Length + sizeof(wchar_t));
unicode_string->Buffer = const_cast<wchar_t*>(data->data());
}
bool ValidateRegistryValueChange(const WStringEmbeddedNulls& old_value,
const WStringEmbeddedNulls& new_value) {
size_t new_cursor = 0;
size_t old_cursor = 0;
while (new_cursor < new_value.size()) {
while (old_cursor < old_value.size() &&
new_value.data()[new_cursor] != old_value.data()[old_cursor]) {
old_cursor++;
}
if (old_cursor >= old_value.size())
return false;
old_cursor++;
new_cursor++;
}
return true;
}
NtRegistryParamError ValidateNtRegistryValue(
const WStringEmbeddedNulls& param) {
if (param.size() >= kMaxRegistryParamLength)
return NtRegistryParamError::TooLong;
return NtRegistryParamError::None;
}
NtRegistryParamError ValidateNtRegistryNullTerminatedParam(
const WStringEmbeddedNulls& param) {
if (param.size() == 0)
return NtRegistryParamError::ZeroLength;
if (param.size() >= kMaxRegistryParamLength)
return NtRegistryParamError::TooLong;
if (param.data()[param.size() - 1] != L'\0')
return NtRegistryParamError::NotNullTerminated;
return NtRegistryParamError::None;
}
NtRegistryParamError ValidateNtRegistryKey(const WStringEmbeddedNulls& key) {
NtRegistryParamError error = ValidateNtRegistryNullTerminatedParam(key);
if (error != NtRegistryParamError::None)
return error;
if (key.data()[0] == L'\\') {
if (!base::StartsWith(key.CastAsWStringPiece(), L"\\Registry\\",
base::CompareCase::INSENSITIVE_ASCII)) {
return NtRegistryParamError::PathOutsideRegistry;
}
}
// TODO(joenotcharles): Ban .. to prevent path traversal. This is not
// critical as all the native registry functions call NtOpenKey, which will
// return an error for keys outside \Registry, but it should be checked for
// defense in depth.
return NtRegistryParamError::None;
}
std::wstring FormatNtRegistryMemberForLogging(const WStringEmbeddedNulls& key) {
switch (ValidateNtRegistryKey(key)) {
case NtRegistryParamError::NullParam:
return L"(null)";
case NtRegistryParamError::ZeroLength:
return L"(empty key)";
case NtRegistryParamError::TooLong:
return L"(excessively long key)";
default:
// Replace null chars with 0s for printing.
std::wstring str(key.CastAsWStringPiece());
base::ReplaceChars(str, base::WStringPiece(L"\0", 1), L"\\0", &str);
return str;
}
}
std::ostream& operator<<(std::ostream& os, NtRegistryParamError param_error) {
switch (param_error) {
case NtRegistryParamError::None:
os << "parameter is OK";
break;
case NtRegistryParamError::NullParam:
os << "parameter is NULL";
break;
case NtRegistryParamError::ZeroLength:
os << "parameter has length 0";
break;
case NtRegistryParamError::TooLong:
os << "parameter has length above the maximum";
break;
case NtRegistryParamError::NotNullTerminated:
os << "parameter is not NULL terminated";
break;
case NtRegistryParamError::PathOutsideRegistry:
os << "key path is not rooted under \\Registry";
break;
default:
NOTREACHED();
break;
}
return os;
}
NTSTATUS NativeCreateKey(HANDLE parent_key,
std::vector<wchar_t>* key_name,
HANDLE* out_handle,
ULONG* out_disposition) {
DCHECK(key_name && !key_name->empty() && key_name->back() == L'\0');
DCHECK(out_handle);
*out_handle = INVALID_HANDLE_VALUE;
static NtCreateKeyFunction NtCreateKey = nullptr;
if (!NtCreateKey)
ResolveNTFunctionPtr("NtCreateKey", &NtCreateKey);
// Set up a key with an embedded null.
UNICODE_STRING uni_name = {0};
InitUnicodeString(&uni_name, key_name);
OBJECT_ATTRIBUTES obj_attr = {0};
InitializeObjectAttributes(&obj_attr, &uni_name, OBJ_CASE_INSENSITIVE,
parent_key, /*SecurityDescriptor=*/NULL);
return NtCreateKey(out_handle, KEY_ALL_ACCESS, &obj_attr, /*TitleIndex=*/0,
/*Class=*/nullptr, REG_OPTION_NON_VOLATILE,
out_disposition);
}
NTSTATUS NativeOpenKey(HANDLE parent_key,
const WStringEmbeddedNulls& key_name,
uint32_t dw_access,
HANDLE* out_handle) {
static NtOpenKeyFunction NtOpenKey = nullptr;
if (!NtOpenKey)
ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey);
// Set up a key with an embedded null.
std::vector<wchar_t> key_name_buffer(key_name.data());
UNICODE_STRING uni_name = {0};
InitUnicodeString(&uni_name, &key_name_buffer);
OBJECT_ATTRIBUTES obj_attr = {0};
InitializeObjectAttributes(&obj_attr, &uni_name, OBJ_CASE_INSENSITIVE,
parent_key, /*SecurityDescriptor=*/NULL);
return NtOpenKey(out_handle, dw_access, &obj_attr);
}
NTSTATUS NativeSetValueKey(HANDLE key,
const WStringEmbeddedNulls& value_name,
ULONG type,
const WStringEmbeddedNulls& value) {
static NtSetValueKeyFunction NtSetValueKey = nullptr;
if (!NtSetValueKey)
ResolveNTFunctionPtr("NtSetValueKey", &NtSetValueKey);
UNICODE_STRING uni_name = {};
std::vector<wchar_t> value_name_buffer(value_name.data());
if (value_name.size()) {
InitUnicodeString(&uni_name, &value_name_buffer);
}
std::vector<wchar_t> value_buffer(value.data());
// The astute reader will notice here that we pass a zero'ed out
// UNICODE_STRING instead of a NULL pointer in the second parameter to set the
// default value, which is not consistent with the MSDN documentation for
// ZwSetValueKey. Afaict, the documentation is incorrect, calling this with a
// NULL pointer returns access denied.
return NtSetValueKey(
key, &uni_name, /*TitleIndex=*/0, type,
reinterpret_cast<void*>(value_buffer.data()),
base::checked_cast<ULONG>(value_buffer.size() * sizeof(wchar_t)));
}
NTSTATUS NativeQueryValueKey(HANDLE key,
const WStringEmbeddedNulls& value_name,
ULONG* out_type,
WStringEmbeddedNulls* out_value) {
// Figure out a) if the value exists and b) what the type of the value is.
static NtQueryValueKeyFunction NtQueryValueKey = nullptr;
if (!NtQueryValueKey)
ResolveNTFunctionPtr("NtQueryValueKey", &NtQueryValueKey);
UNICODE_STRING uni_name = {};
std::vector<wchar_t> value_name_buffer(value_name.data());
if (value_name.size()) {
InitUnicodeString(&uni_name, &value_name_buffer);
}
ULONG size_needed = 0;
// The astute reader will notice here that we pass a zero'ed out
// UNICODE_STRING instead of a NULL pointer in the second parameter to set the
// default value, which is not consistent with the MSDN documentation for
// ZwQueryValueKey. Afaict, the documentation is incorrect, calling this with
// a NULL pointer returns access denied.
NTSTATUS status = NtQueryValueKey(key, &uni_name, KeyValueFullInformation,
nullptr, 0, &size_needed);
if (status != STATUS_BUFFER_TOO_SMALL || size_needed == 0) {
LOG(ERROR) << "Call to NtQueryValueKey to query size failed. Returned: "
<< std::hex << status;
return false;
}
std::vector<BYTE> buffer(size_needed);
ULONG bytes_read = 0;
status = NtQueryValueKey(key, &uni_name, KeyValueFullInformation,
reinterpret_cast<void*>(buffer.data()), size_needed,
&bytes_read);
if (status != STATUS_SUCCESS || bytes_read != size_needed) {
LOG(ERROR) << "Call to NtQueryValueKey failed. Returned: " << std::hex
<< status;
return false;
}
KEY_VALUE_FULL_INFORMATION* full_information =
reinterpret_cast<KEY_VALUE_FULL_INFORMATION*>(buffer.data());
if (out_type)
*out_type = full_information->Type;
if (out_value) {
// Make sure that the length is a multiple of the width of a wchar_t to
// avoid alignment errors.
if (full_information->DataLength % sizeof(wchar_t) != 0) {
LOG(ERROR) << "Mis-aligned size reading registry value.";
return false;
}
// Explanation for fiery mess of casts: the value data from NtQueryValueKey
// is in a memory block allocated right at the end of the
// KEY_VALUE_FULL_INFORMATION structure. The DataOffset member is the offset
// in BYTES of the start of the value data.
// The inner reinterpret_cast ensures that the pointer arithmetic uses
// BYTE widths.
// The outer reinterpret_cast then treats that address as a wchar_t*,
// which is safe due to the alignment check above.
const wchar_t* data_start =
reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(full_information) +
full_information->DataOffset);
*out_value = WStringEmbeddedNulls(
data_start, full_information->DataLength / sizeof(wchar_t));
}
return true;
}
NTSTATUS NativeDeleteKey(HANDLE handle) {
static NtDeleteKeyFunction NtDeleteKey = nullptr;
if (!NtDeleteKey)
ResolveNTFunctionPtr("NtDeleteKey", &NtDeleteKey);
return NtDeleteKey(handle);
}
} // namespace chrome_cleaner_sandbox