1 // Copyright 2019 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.
4
5 #include "chrome/chrome_cleaner/engines/common/registry_util.h"
6
7 #include "base/logging.h"
8 #include "base/notreached.h"
9 #include "base/numerics/safe_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "chrome/chrome_cleaner/strings/wstring_embedded_nulls.h"
12 #include "sandbox/win/src/win_utils.h"
13
14 using chrome_cleaner::WStringEmbeddedNulls;
15
16 namespace chrome_cleaner_sandbox {
17
InitUnicodeString(UNICODE_STRING * unicode_string,std::vector<wchar_t> * data)18 void InitUnicodeString(UNICODE_STRING* unicode_string,
19 std::vector<wchar_t>* data) {
20 DCHECK(data && !data->empty() && data->back() == L'\0');
21 // As per
22 // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx
23 // Length is the length of the string in BYTES, NOT including the
24 // terminating NULL char, MaximumLength is the length of the string in BYTES
25 // INCLUDING the terminating NULL char.
26 unicode_string->Length =
27 static_cast<USHORT>((data->size() - 1) * sizeof(wchar_t));
28 unicode_string->MaximumLength =
29 static_cast<USHORT>(unicode_string->Length + sizeof(wchar_t));
30 unicode_string->Buffer = const_cast<wchar_t*>(data->data());
31 }
32
ValidateRegistryValueChange(const WStringEmbeddedNulls & old_value,const WStringEmbeddedNulls & new_value)33 bool ValidateRegistryValueChange(const WStringEmbeddedNulls& old_value,
34 const WStringEmbeddedNulls& new_value) {
35 size_t new_cursor = 0;
36 size_t old_cursor = 0;
37 while (new_cursor < new_value.size()) {
38 while (old_cursor < old_value.size() &&
39 new_value.data()[new_cursor] != old_value.data()[old_cursor]) {
40 old_cursor++;
41 }
42
43 if (old_cursor >= old_value.size())
44 return false;
45
46 old_cursor++;
47 new_cursor++;
48 }
49
50 return true;
51 }
52
ValidateNtRegistryValue(const WStringEmbeddedNulls & param)53 NtRegistryParamError ValidateNtRegistryValue(
54 const WStringEmbeddedNulls& param) {
55 if (param.size() >= kMaxRegistryParamLength)
56 return NtRegistryParamError::TooLong;
57 return NtRegistryParamError::None;
58 }
59
ValidateNtRegistryNullTerminatedParam(const WStringEmbeddedNulls & param)60 NtRegistryParamError ValidateNtRegistryNullTerminatedParam(
61 const WStringEmbeddedNulls& param) {
62 if (param.size() == 0)
63 return NtRegistryParamError::ZeroLength;
64 if (param.size() >= kMaxRegistryParamLength)
65 return NtRegistryParamError::TooLong;
66 if (param.data()[param.size() - 1] != L'\0')
67 return NtRegistryParamError::NotNullTerminated;
68 return NtRegistryParamError::None;
69 }
70
ValidateNtRegistryKey(const WStringEmbeddedNulls & key)71 NtRegistryParamError ValidateNtRegistryKey(const WStringEmbeddedNulls& key) {
72 NtRegistryParamError error = ValidateNtRegistryNullTerminatedParam(key);
73 if (error != NtRegistryParamError::None)
74 return error;
75
76 if (key.data()[0] == L'\\') {
77 if (!base::StartsWith(key.CastAsWStringPiece(), L"\\Registry\\",
78 base::CompareCase::INSENSITIVE_ASCII)) {
79 return NtRegistryParamError::PathOutsideRegistry;
80 }
81 }
82
83 // TODO(joenotcharles): Ban .. to prevent path traversal. This is not
84 // critical as all the native registry functions call NtOpenKey, which will
85 // return an error for keys outside \Registry, but it should be checked for
86 // defense in depth.
87 return NtRegistryParamError::None;
88 }
89
FormatNtRegistryMemberForLogging(const WStringEmbeddedNulls & key)90 std::wstring FormatNtRegistryMemberForLogging(const WStringEmbeddedNulls& key) {
91 switch (ValidateNtRegistryKey(key)) {
92 case NtRegistryParamError::NullParam:
93 return L"(null)";
94 case NtRegistryParamError::ZeroLength:
95 return L"(empty key)";
96 case NtRegistryParamError::TooLong:
97 return L"(excessively long key)";
98 default:
99 // Replace null chars with 0s for printing.
100 std::wstring str(key.CastAsWStringPiece());
101 base::ReplaceChars(str, base::WStringPiece(L"\0", 1), L"\\0", &str);
102 return str;
103 }
104 }
105
operator <<(std::ostream & os,NtRegistryParamError param_error)106 std::ostream& operator<<(std::ostream& os, NtRegistryParamError param_error) {
107 switch (param_error) {
108 case NtRegistryParamError::None:
109 os << "parameter is OK";
110 break;
111 case NtRegistryParamError::NullParam:
112 os << "parameter is NULL";
113 break;
114 case NtRegistryParamError::ZeroLength:
115 os << "parameter has length 0";
116 break;
117 case NtRegistryParamError::TooLong:
118 os << "parameter has length above the maximum";
119 break;
120 case NtRegistryParamError::NotNullTerminated:
121 os << "parameter is not NULL terminated";
122 break;
123 case NtRegistryParamError::PathOutsideRegistry:
124 os << "key path is not rooted under \\Registry";
125 break;
126 default:
127 NOTREACHED();
128 break;
129 }
130 return os;
131 }
132
NativeCreateKey(HANDLE parent_key,std::vector<wchar_t> * key_name,HANDLE * out_handle,ULONG * out_disposition)133 NTSTATUS NativeCreateKey(HANDLE parent_key,
134 std::vector<wchar_t>* key_name,
135 HANDLE* out_handle,
136 ULONG* out_disposition) {
137 DCHECK(key_name && !key_name->empty() && key_name->back() == L'\0');
138 DCHECK(out_handle);
139 *out_handle = INVALID_HANDLE_VALUE;
140
141 static NtCreateKeyFunction NtCreateKey = nullptr;
142 if (!NtCreateKey)
143 ResolveNTFunctionPtr("NtCreateKey", &NtCreateKey);
144
145 // Set up a key with an embedded null.
146 UNICODE_STRING uni_name = {0};
147 InitUnicodeString(&uni_name, key_name);
148
149 OBJECT_ATTRIBUTES obj_attr = {0};
150 InitializeObjectAttributes(&obj_attr, &uni_name, OBJ_CASE_INSENSITIVE,
151 parent_key, /*SecurityDescriptor=*/NULL);
152
153 return NtCreateKey(out_handle, KEY_ALL_ACCESS, &obj_attr, /*TitleIndex=*/0,
154 /*Class=*/nullptr, REG_OPTION_NON_VOLATILE,
155 out_disposition);
156 }
157
NativeOpenKey(HANDLE parent_key,const WStringEmbeddedNulls & key_name,uint32_t dw_access,HANDLE * out_handle)158 NTSTATUS NativeOpenKey(HANDLE parent_key,
159 const WStringEmbeddedNulls& key_name,
160 uint32_t dw_access,
161 HANDLE* out_handle) {
162 static NtOpenKeyFunction NtOpenKey = nullptr;
163 if (!NtOpenKey)
164 ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey);
165
166 // Set up a key with an embedded null.
167 std::vector<wchar_t> key_name_buffer(key_name.data());
168 UNICODE_STRING uni_name = {0};
169 InitUnicodeString(&uni_name, &key_name_buffer);
170
171 OBJECT_ATTRIBUTES obj_attr = {0};
172 InitializeObjectAttributes(&obj_attr, &uni_name, OBJ_CASE_INSENSITIVE,
173 parent_key, /*SecurityDescriptor=*/NULL);
174
175 return NtOpenKey(out_handle, dw_access, &obj_attr);
176 }
177
NativeSetValueKey(HANDLE key,const WStringEmbeddedNulls & value_name,ULONG type,const WStringEmbeddedNulls & value)178 NTSTATUS NativeSetValueKey(HANDLE key,
179 const WStringEmbeddedNulls& value_name,
180 ULONG type,
181 const WStringEmbeddedNulls& value) {
182 static NtSetValueKeyFunction NtSetValueKey = nullptr;
183 if (!NtSetValueKey)
184 ResolveNTFunctionPtr("NtSetValueKey", &NtSetValueKey);
185
186 UNICODE_STRING uni_name = {};
187 std::vector<wchar_t> value_name_buffer(value_name.data());
188 if (value_name.size()) {
189 InitUnicodeString(&uni_name, &value_name_buffer);
190 }
191
192 std::vector<wchar_t> value_buffer(value.data());
193
194 // The astute reader will notice here that we pass a zero'ed out
195 // UNICODE_STRING instead of a NULL pointer in the second parameter to set the
196 // default value, which is not consistent with the MSDN documentation for
197 // ZwSetValueKey. Afaict, the documentation is incorrect, calling this with a
198 // NULL pointer returns access denied.
199 return NtSetValueKey(
200 key, &uni_name, /*TitleIndex=*/0, type,
201 reinterpret_cast<void*>(value_buffer.data()),
202 base::checked_cast<ULONG>(value_buffer.size() * sizeof(wchar_t)));
203 }
204
NativeQueryValueKey(HANDLE key,const WStringEmbeddedNulls & value_name,ULONG * out_type,WStringEmbeddedNulls * out_value)205 NTSTATUS NativeQueryValueKey(HANDLE key,
206 const WStringEmbeddedNulls& value_name,
207 ULONG* out_type,
208 WStringEmbeddedNulls* out_value) {
209 // Figure out a) if the value exists and b) what the type of the value is.
210 static NtQueryValueKeyFunction NtQueryValueKey = nullptr;
211 if (!NtQueryValueKey)
212 ResolveNTFunctionPtr("NtQueryValueKey", &NtQueryValueKey);
213
214 UNICODE_STRING uni_name = {};
215 std::vector<wchar_t> value_name_buffer(value_name.data());
216 if (value_name.size()) {
217 InitUnicodeString(&uni_name, &value_name_buffer);
218 }
219
220 ULONG size_needed = 0;
221
222 // The astute reader will notice here that we pass a zero'ed out
223 // UNICODE_STRING instead of a NULL pointer in the second parameter to set the
224 // default value, which is not consistent with the MSDN documentation for
225 // ZwQueryValueKey. Afaict, the documentation is incorrect, calling this with
226 // a NULL pointer returns access denied.
227 NTSTATUS status = NtQueryValueKey(key, &uni_name, KeyValueFullInformation,
228 nullptr, 0, &size_needed);
229 if (status != STATUS_BUFFER_TOO_SMALL || size_needed == 0) {
230 LOG(ERROR) << "Call to NtQueryValueKey to query size failed. Returned: "
231 << std::hex << status;
232 return false;
233 }
234
235 std::vector<BYTE> buffer(size_needed);
236 ULONG bytes_read = 0;
237 status = NtQueryValueKey(key, &uni_name, KeyValueFullInformation,
238 reinterpret_cast<void*>(buffer.data()), size_needed,
239 &bytes_read);
240 if (status != STATUS_SUCCESS || bytes_read != size_needed) {
241 LOG(ERROR) << "Call to NtQueryValueKey failed. Returned: " << std::hex
242 << status;
243 return false;
244 }
245
246 KEY_VALUE_FULL_INFORMATION* full_information =
247 reinterpret_cast<KEY_VALUE_FULL_INFORMATION*>(buffer.data());
248
249 if (out_type)
250 *out_type = full_information->Type;
251 if (out_value) {
252 // Make sure that the length is a multiple of the width of a wchar_t to
253 // avoid alignment errors.
254 if (full_information->DataLength % sizeof(wchar_t) != 0) {
255 LOG(ERROR) << "Mis-aligned size reading registry value.";
256 return false;
257 }
258
259 // Explanation for fiery mess of casts: the value data from NtQueryValueKey
260 // is in a memory block allocated right at the end of the
261 // KEY_VALUE_FULL_INFORMATION structure. The DataOffset member is the offset
262 // in BYTES of the start of the value data.
263 // The inner reinterpret_cast ensures that the pointer arithmetic uses
264 // BYTE widths.
265 // The outer reinterpret_cast then treats that address as a wchar_t*,
266 // which is safe due to the alignment check above.
267 const wchar_t* data_start =
268 reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(full_information) +
269 full_information->DataOffset);
270 *out_value = WStringEmbeddedNulls(
271 data_start, full_information->DataLength / sizeof(wchar_t));
272 }
273
274 return true;
275 }
276
NativeDeleteKey(HANDLE handle)277 NTSTATUS NativeDeleteKey(HANDLE handle) {
278 static NtDeleteKeyFunction NtDeleteKey = nullptr;
279 if (!NtDeleteKey)
280 ResolveNTFunctionPtr("NtDeleteKey", &NtDeleteKey);
281 return NtDeleteKey(handle);
282 }
283
284 } // namespace chrome_cleaner_sandbox
285