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.
5 #include "components/policy/core/common/preg_parser_win.h"
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 "components/policy/core/common/policy_load_status.h"
24 #include "components/policy/core/common/registry_dict_win.h"
27 namespace preg_parser
{
29 const char kPRegFileHeader
[8] =
30 { 'P', 'R', 'e', 'g', '\x01', '\x00', '\x00', '\x00' };
32 // Maximum PReg file size we're willing to accept.
33 const int64 kMaxPRegFileSize
= 1024 * 1024 * 16;
35 // Constants for PReg file delimiters.
36 const base::char16 kDelimBracketOpen
= L
'[';
37 const base::char16 kDelimBracketClose
= L
']';
38 const base::char16 kDelimSemicolon
= L
';';
40 // Registry path separator.
41 const base::char16 kRegistryPathSeparator
[] = L
"\\";
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";
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 base::char16 is available.
56 if (*cursor
+ sizeof(base::char16
) > end
)
59 int result
= **cursor
| (*(*cursor
+ 1) << 8);
60 *cursor
+= sizeof(base::char16
);
64 // Reads a fixed-size field from a PReg file.
65 bool ReadFieldBinary(const uint8
** cursor
,
72 const uint8
* field_end
= *cursor
+ size
;
73 if (field_end
<= *cursor
|| field_end
> end
)
75 std::copy(*cursor
, field_end
, data
);
80 bool ReadField32(const uint8
** cursor
, const uint8
* end
, uint32
* data
) {
82 if (!ReadFieldBinary(cursor
, end
, sizeof(uint32
),
83 reinterpret_cast<uint8
*>(&value
))) {
86 *data
= base::ByteSwapToLE32(value
);
90 // Reads a string field from a file.
91 bool ReadFieldString(const uint8
** cursor
,
93 base::string16
* str
) {
95 while ((current
= NextChar(cursor
, end
)) > 0x0000)
98 return current
== L
'\0';
101 std::string
DecodePRegStringValue(const std::vector
<uint8
>& data
) {
102 size_t len
= data
.size() / sizeof(base::char16
);
104 return std::string();
106 const base::char16
* chars
=
107 reinterpret_cast<const base::char16
*>(vector_as_array(&data
));
108 base::string16 result
;
109 std::transform(chars
, chars
+ len
- 1, std::back_inserter(result
),
110 std::ptr_fun(base::ByteSwapToLE16
));
111 return base::UTF16ToUTF8(result
);
114 // Decodes a value from a PReg file given as a uint8 vector.
115 bool DecodePRegValue(uint32 type
,
116 const std::vector
<uint8
>& data
,
117 scoped_ptr
<base::Value
>* value
) {
121 value
->reset(base::Value::CreateStringValue(DecodePRegStringValue(data
)));
123 case REG_DWORD_LITTLE_ENDIAN
:
124 case REG_DWORD_BIG_ENDIAN
:
125 if (data
.size() == sizeof(uint32
)) {
126 uint32 val
= *reinterpret_cast<const uint32
*>(vector_as_array(&data
));
127 if (type
== REG_DWORD_BIG_ENDIAN
)
128 val
= base::NetToHost32(val
);
130 val
= base::ByteSwapToLE32(val
);
131 value
->reset(base::Value::CreateIntegerValue(static_cast<int>(val
)));
134 LOG(ERROR
) << "Bad data size " << data
.size();
140 case REG_RESOURCE_LIST
:
141 case REG_FULL_RESOURCE_DESCRIPTOR
:
142 case REG_RESOURCE_REQUIREMENTS_LIST
:
143 case REG_QWORD_LITTLE_ENDIAN
:
145 LOG(ERROR
) << "Unsupported registry data type " << type
;
151 // Adds the record data passed via parameters to |dict| in case the data is
152 // relevant policy for Chromium.
153 void HandleRecord(const base::string16
& key_name
,
154 const base::string16
& value
,
156 const std::vector
<uint8
>& data
,
157 RegistryDict
* dict
) {
158 // Locate/create the dictionary to place the value in.
159 std::vector
<base::string16
> path
;
161 Tokenize(key_name
, kRegistryPathSeparator
, &path
);
162 for (std::vector
<base::string16
>::const_iterator
entry(path
.begin());
163 entry
!= path
.end(); ++entry
) {
166 const std::string name
= base::UTF16ToUTF8(*entry
);
167 RegistryDict
* subdict
= dict
->GetKey(name
);
169 subdict
= new RegistryDict();
170 dict
->SetKey(name
, make_scoped_ptr(subdict
));
178 std::string
value_name(base::UTF16ToUTF8(value
));
179 if (!StartsWithASCII(value_name
, kActionTriggerPrefix
, true)) {
180 scoped_ptr
<base::Value
> value
;
181 if (DecodePRegValue(type
, data
, &value
))
182 dict
->SetValue(value_name
, value
.Pass());
186 std::string
action_trigger(StringToLowerASCII(value_name
.substr(
187 arraysize(kActionTriggerPrefix
) - 1)));
188 if (action_trigger
== kActionTriggerDeleteValues
) {
189 std::vector
<std::string
> values
;
190 Tokenize(DecodePRegStringValue(data
), ";", &values
);
191 for (std::vector
<std::string
>::const_iterator
value(values
.begin());
192 value
!= values
.end(); ++value
) {
193 dict
->RemoveValue(*value
);
195 } else if (StartsWithASCII(action_trigger
, kActionTriggerDeleteKeys
, true)) {
196 std::vector
<std::string
> keys
;
197 Tokenize(DecodePRegStringValue(data
), ";", &keys
);
198 for (std::vector
<std::string
>::const_iterator
key(keys
.begin());
199 key
!= keys
.end(); ++key
) {
200 dict
->RemoveKey(*key
);
202 } else if (StartsWithASCII(action_trigger
, kActionTriggerDel
, true)) {
204 value_name
.substr(arraysize(kActionTriggerPrefix
) - 1 +
205 arraysize(kActionTriggerDel
) - 1));
206 } else if (StartsWithASCII(action_trigger
, kActionTriggerDelVals
, true)) {
207 // Delete all values.
209 } else if (StartsWithASCII(action_trigger
, kActionTriggerSecureKey
, true) ||
210 StartsWithASCII(action_trigger
, kActionTriggerSoft
, true)) {
211 // Doesn't affect values.
213 LOG(ERROR
) << "Bad action trigger " << value_name
;
217 bool ReadFile(const base::FilePath
& file_path
,
218 const base::string16
& root
,
220 PolicyLoadStatusSample
* status
) {
221 base::MemoryMappedFile mapped_file
;
222 if (!mapped_file
.Initialize(file_path
) || !mapped_file
.IsValid()) {
223 PLOG(ERROR
) << "Failed to map " << file_path
.value();
224 status
->Add(POLICY_LOAD_STATUS_READ_ERROR
);
228 if (mapped_file
.length() > kMaxPRegFileSize
) {
229 LOG(ERROR
) << "PReg file " << file_path
.value() << " too large: "
230 << mapped_file
.length();
231 status
->Add(POLICY_LOAD_STATUS_TOO_BIG
);
236 const int kHeaderSize
= arraysize(kPRegFileHeader
);
237 if (mapped_file
.length() < kHeaderSize
||
238 memcmp(kPRegFileHeader
, mapped_file
.data(), kHeaderSize
) != 0) {
239 LOG(ERROR
) << "Bad policy file " << file_path
.value();
240 status
->Add(POLICY_LOAD_STATUS_PARSE_ERROR
);
244 // Parse file contents, which is UCS-2 and little-endian. The latter I
245 // couldn't find documentation on, but the example I saw were all
246 // little-endian. It'd be interesting to check on big-endian hardware.
247 const uint8
* cursor
= mapped_file
.data() + kHeaderSize
;
248 const uint8
* end
= mapped_file
.data() + mapped_file
.length();
253 if (NextChar(&cursor
, end
) != kDelimBracketOpen
)
256 // Read the record fields.
257 base::string16 key_name
;
258 base::string16 value
;
261 std::vector
<uint8
> data
;
263 if (!ReadFieldString(&cursor
, end
, &key_name
))
266 int current
= NextChar(&cursor
, end
);
267 if (current
== kDelimSemicolon
) {
268 if (!ReadFieldString(&cursor
, end
, &value
))
270 current
= NextChar(&cursor
, end
);
273 if (current
== kDelimSemicolon
) {
274 if (!ReadField32(&cursor
, end
, &type
))
276 current
= NextChar(&cursor
, end
);
279 if (current
== kDelimSemicolon
) {
280 if (!ReadField32(&cursor
, end
, &size
))
282 current
= NextChar(&cursor
, end
);
285 if (current
== kDelimSemicolon
) {
286 if (size
> kMaxPRegFileSize
)
289 if (!ReadFieldBinary(&cursor
, end
, size
, vector_as_array(&data
)))
291 current
= NextChar(&cursor
, end
);
294 if (current
!= kDelimBracketClose
)
297 // Process the record if it is within the |root| subtree.
298 if (StartsWith(key_name
, root
, false))
299 HandleRecord(key_name
.substr(root
.size()), value
, type
, data
, dict
);
302 LOG(ERROR
) << "Error parsing " << file_path
.value() << " at offset "
303 << reinterpret_cast<const uint8
*>(cursor
- 1) - mapped_file
.data();
304 status
->Add(POLICY_LOAD_STATUS_PARSE_ERROR
);
308 } // namespace preg_parser
309 } // namespace policy