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