OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 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/browser/policy/preg_parser_win.h" | |
6 | |
7 #include <windows.h> | |
8 | |
9 #include <algorithm> | |
10 #include <iterator> | |
11 #include <vector> | |
12 | |
13 #include "base/basictypes.h" | |
14 #include "base/files/file_path.h" | |
15 #include "base/files/memory_mapped_file.h" | |
16 #include "base/logging.h" | |
17 #include "base/stl_util.h" | |
18 #include "base/strings/string16.h" | |
19 #include "base/strings/string_util.h" | |
20 #include "base/strings/utf_string_conversions.h" | |
21 #include "base/sys_byteorder.h" | |
22 #include "base/values.h" | |
23 #include "chrome/browser/policy/policy_load_status.h" | |
24 #include "components/policy/core/common/registry_dict_win.h" | |
25 | |
26 namespace policy { | |
27 namespace preg_parser { | |
28 | |
29 const char kPRegFileHeader[8] = | |
30 { 'P', 'R', 'e', 'g', '\x01', '\x00', '\x00', '\x00' }; | |
31 | |
32 // Maximum PReg file size we're willing to accept. | |
33 const int64 kMaxPRegFileSize = 1024 * 1024 * 16; | |
34 | |
35 // Constants for PReg file delimiters. | |
36 const char16 kDelimBracketOpen = L'['; | |
37 const char16 kDelimBracketClose = L']'; | |
38 const char16 kDelimSemicolon = L';'; | |
39 | |
40 // Registry path separator. | |
41 const char16 kRegistryPathSeparator[] = L"\\"; | |
42 | |
43 // Magic strings for the PReg value field to trigger special actions. | |
44 const char kActionTriggerPrefix[] = "**"; | |
45 const char kActionTriggerDeleteValues[] = "deletevalues"; | |
46 const char kActionTriggerDel[] = "del."; | |
47 const char kActionTriggerDelVals[] = "delvals"; | |
48 const char kActionTriggerDeleteKeys[] = "deletekeys"; | |
49 const char kActionTriggerSecureKey[] = "securekey"; | |
50 const char kActionTriggerSoft[] = "soft"; | |
51 | |
52 // Returns the character at |cursor| and increments it, unless the end is here | |
53 // in which case -1 is returned. | |
54 int NextChar(const uint8** cursor, const uint8* end) { | |
55 // Only read the character if a full char16 is available. | |
56 if (*cursor + sizeof(char16) > end) | |
57 return -1; | |
58 | |
59 int result = **cursor | (*(*cursor + 1) << 8); | |
60 *cursor += sizeof(char16); | |
61 return result; | |
62 } | |
63 | |
64 // Reads a fixed-size field from a PReg file. | |
65 bool ReadFieldBinary(const uint8** cursor, | |
66 const uint8* end, | |
67 uint32 size, | |
68 uint8* data) { | |
69 if (size == 0) | |
70 return true; | |
71 | |
72 const uint8* field_end = *cursor + size; | |
73 if (field_end <= *cursor || field_end > end) | |
74 return false; | |
75 std::copy(*cursor, field_end, data); | |
76 *cursor = field_end; | |
77 return true; | |
78 } | |
79 | |
80 bool ReadField32(const uint8** cursor, const uint8* end, uint32* data) { | |
81 uint32 value = 0; | |
82 if (!ReadFieldBinary(cursor, end, sizeof(uint32), | |
83 reinterpret_cast<uint8*>(&value))) { | |
84 return false; | |
85 } | |
86 *data = base::ByteSwapToLE32(value); | |
87 return true; | |
88 } | |
89 | |
90 // Reads a string field from a file. | |
91 bool ReadFieldString(const uint8** cursor, const uint8* end, string16* str) { | |
92 int current = -1; | |
93 while ((current = NextChar(cursor, end)) > 0x0000) | |
94 *str += current; | |
95 | |
96 return current == L'\0'; | |
97 } | |
98 | |
99 std::string DecodePRegStringValue(const std::vector<uint8>& data) { | |
100 size_t len = data.size() / sizeof(char16); | |
101 if (len <= 0) | |
102 return std::string(); | |
103 | |
104 const char16* chars = reinterpret_cast<const char16*>(vector_as_array(&data)); | |
105 string16 result; | |
106 std::transform(chars, chars + len - 1, std::back_inserter(result), | |
107 std::ptr_fun(base::ByteSwapToLE16)); | |
108 return UTF16ToUTF8(result); | |
109 } | |
110 | |
111 // Decodes a value from a PReg file given as a uint8 vector. | |
112 bool DecodePRegValue(uint32 type, | |
113 const std::vector<uint8>& data, | |
114 scoped_ptr<base::Value>* value) { | |
115 switch (type) { | |
116 case REG_SZ: | |
117 case REG_EXPAND_SZ: | |
118 value->reset(base::Value::CreateStringValue(DecodePRegStringValue(data))); | |
119 return true; | |
120 case REG_DWORD_LITTLE_ENDIAN: | |
121 case REG_DWORD_BIG_ENDIAN: | |
122 if (data.size() == sizeof(uint32)) { | |
123 uint32 val = *reinterpret_cast<const uint32*>(vector_as_array(&data)); | |
124 if (type == REG_DWORD_BIG_ENDIAN) | |
125 val = base::NetToHost32(val); | |
126 else | |
127 val = base::ByteSwapToLE32(val); | |
128 value->reset(base::Value::CreateIntegerValue(static_cast<int>(val))); | |
129 return true; | |
130 } else { | |
131 LOG(ERROR) << "Bad data size " << data.size(); | |
132 } | |
133 break; | |
134 case REG_NONE: | |
135 case REG_LINK: | |
136 case REG_MULTI_SZ: | |
137 case REG_RESOURCE_LIST: | |
138 case REG_FULL_RESOURCE_DESCRIPTOR: | |
139 case REG_RESOURCE_REQUIREMENTS_LIST: | |
140 case REG_QWORD_LITTLE_ENDIAN: | |
141 default: | |
142 LOG(ERROR) << "Unsupported registry data type " << type; | |
143 } | |
144 | |
145 return false; | |
146 } | |
147 | |
148 // Adds the record data passed via parameters to |dict| in case the data is | |
149 // relevant policy for Chromium. | |
150 void HandleRecord(const string16& key_name, | |
151 const string16& value, | |
152 uint32 type, | |
153 const std::vector<uint8>& data, | |
154 RegistryDict* dict) { | |
155 // Locate/create the dictionary to place the value in. | |
156 std::vector<string16> path; | |
157 | |
158 Tokenize(key_name, kRegistryPathSeparator, &path); | |
159 for (std::vector<string16>::const_iterator entry(path.begin()); | |
160 entry != path.end(); ++entry) { | |
161 if (entry->empty()) | |
162 continue; | |
163 const std::string name = UTF16ToUTF8(*entry); | |
164 RegistryDict* subdict = dict->GetKey(name); | |
165 if (!subdict) { | |
166 subdict = new RegistryDict(); | |
167 dict->SetKey(name, make_scoped_ptr(subdict)); | |
168 } | |
169 dict = subdict; | |
170 } | |
171 | |
172 if (value.empty()) | |
173 return; | |
174 | |
175 std::string value_name(UTF16ToUTF8(value)); | |
176 if (!StartsWithASCII(value_name, kActionTriggerPrefix, true)) { | |
177 scoped_ptr<base::Value> value; | |
178 if (DecodePRegValue(type, data, &value)) | |
179 dict->SetValue(value_name, value.Pass()); | |
180 return; | |
181 } | |
182 | |
183 std::string action_trigger(StringToLowerASCII(value_name.substr( | |
184 arraysize(kActionTriggerPrefix) - 1))); | |
185 if (action_trigger == kActionTriggerDeleteValues) { | |
186 std::vector<std::string> values; | |
187 Tokenize(DecodePRegStringValue(data), ";", &values); | |
188 for (std::vector<std::string>::const_iterator value(values.begin()); | |
189 value != values.end(); ++value) { | |
190 dict->RemoveValue(*value); | |
191 } | |
192 } else if (StartsWithASCII(action_trigger, kActionTriggerDeleteKeys, true)) { | |
193 std::vector<std::string> keys; | |
194 Tokenize(DecodePRegStringValue(data), ";", &keys); | |
195 for (std::vector<std::string>::const_iterator key(keys.begin()); | |
196 key != keys.end(); ++key) { | |
197 dict->RemoveKey(*key); | |
198 } | |
199 } else if (StartsWithASCII(action_trigger, kActionTriggerDel, true)) { | |
200 dict->RemoveValue( | |
201 value_name.substr(arraysize(kActionTriggerPrefix) - 1 + | |
202 arraysize(kActionTriggerDel) - 1)); | |
203 } else if (StartsWithASCII(action_trigger, kActionTriggerDelVals, true)) { | |
204 // Delete all values. | |
205 dict->ClearValues(); | |
206 } else if (StartsWithASCII(action_trigger, kActionTriggerSecureKey, true) || | |
207 StartsWithASCII(action_trigger, kActionTriggerSoft, true)) { | |
208 // Doesn't affect values. | |
209 } else { | |
210 LOG(ERROR) << "Bad action trigger " << value_name; | |
211 } | |
212 } | |
213 | |
214 bool ReadFile(const base::FilePath& file_path, | |
215 const string16& root, | |
216 RegistryDict* dict, | |
217 PolicyLoadStatusSample* status) { | |
218 base::MemoryMappedFile mapped_file; | |
219 if (!mapped_file.Initialize(file_path) || !mapped_file.IsValid()) { | |
220 PLOG(ERROR) << "Failed to map " << file_path.value(); | |
221 status->Add(POLICY_LOAD_STATUS_READ_ERROR); | |
222 return false; | |
223 } | |
224 | |
225 if (mapped_file.length() > kMaxPRegFileSize) { | |
226 LOG(ERROR) << "PReg file " << file_path.value() << " too large: " | |
227 << mapped_file.length(); | |
228 status->Add(POLICY_LOAD_STATUS_TOO_BIG); | |
229 return false; | |
230 } | |
231 | |
232 // Check the header. | |
233 const int kHeaderSize = arraysize(kPRegFileHeader); | |
234 if (mapped_file.length() < kHeaderSize || | |
235 memcmp(kPRegFileHeader, mapped_file.data(), kHeaderSize) != 0) { | |
236 LOG(ERROR) << "Bad policy file " << file_path.value(); | |
237 status->Add(POLICY_LOAD_STATUS_PARSE_ERROR); | |
238 return false; | |
239 } | |
240 | |
241 // Parse file contents, which is UCS-2 and little-endian. The latter I | |
242 // couldn't find documentation on, but the example I saw were all | |
243 // little-endian. It'd be interesting to check on big-endian hardware. | |
244 const uint8* cursor = mapped_file.data() + kHeaderSize; | |
245 const uint8* end = mapped_file.data() + mapped_file.length(); | |
246 while (true) { | |
247 if (cursor == end) | |
248 return true; | |
249 | |
250 if (NextChar(&cursor, end) != kDelimBracketOpen) | |
251 break; | |
252 | |
253 // Read the record fields. | |
254 string16 key_name; | |
255 string16 value; | |
256 uint32 type = 0; | |
257 uint32 size = 0; | |
258 std::vector<uint8> data; | |
259 | |
260 if (!ReadFieldString(&cursor, end, &key_name)) | |
261 break; | |
262 | |
263 int current = NextChar(&cursor, end); | |
264 if (current == kDelimSemicolon) { | |
265 if (!ReadFieldString(&cursor, end, &value)) | |
266 break; | |
267 current = NextChar(&cursor, end); | |
268 } | |
269 | |
270 if (current == kDelimSemicolon) { | |
271 if (!ReadField32(&cursor, end, &type)) | |
272 break; | |
273 current = NextChar(&cursor, end); | |
274 } | |
275 | |
276 if (current == kDelimSemicolon) { | |
277 if (!ReadField32(&cursor, end, &size)) | |
278 break; | |
279 current = NextChar(&cursor, end); | |
280 } | |
281 | |
282 if (current == kDelimSemicolon) { | |
283 if (size > kMaxPRegFileSize) | |
284 break; | |
285 data.resize(size); | |
286 if (!ReadFieldBinary(&cursor, end, size, vector_as_array(&data))) | |
287 break; | |
288 current = NextChar(&cursor, end); | |
289 } | |
290 | |
291 if (current != kDelimBracketClose) | |
292 break; | |
293 | |
294 // Process the record if it is within the |root| subtree. | |
295 if (StartsWith(key_name, root, false)) | |
296 HandleRecord(key_name.substr(root.size()), value, type, data, dict); | |
297 } | |
298 | |
299 LOG(ERROR) << "Error parsing " << file_path.value() << " at offset " | |
300 << reinterpret_cast<const uint8*>(cursor - 1) - mapped_file.data(); | |
301 status->Add(POLICY_LOAD_STATUS_PARSE_ERROR); | |
302 return false; | |
303 } | |
304 | |
305 } // namespace preg_parser | |
306 } // namespace policy | |
OLD | NEW |