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/i18n/case_conversion.h"
17 #include "base/logging.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string16.h"
20 #include "base/strings/string_split.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/sys_byteorder.h"
24 #include "base/values.h"
25 #include "components/policy/core/common/policy_load_status.h"
26 #include "components/policy/core/common/registry_dict_win.h"
29 namespace preg_parser
{
31 const char kPRegFileHeader
[8] =
32 { 'P', 'R', 'e', 'g', '\x01', '\x00', '\x00', '\x00' };
34 // Maximum PReg file size we're willing to accept.
35 const int64 kMaxPRegFileSize
= 1024 * 1024 * 16;
37 // Constants for PReg file delimiters.
38 const base::char16 kDelimBracketOpen
= L
'[';
39 const base::char16 kDelimBracketClose
= L
']';
40 const base::char16 kDelimSemicolon
= L
';';
42 // Registry path separator.
43 const base::char16 kRegistryPathSeparator
[] = L
"\\";
45 // Magic strings for the PReg value field to trigger special actions.
46 const char kActionTriggerPrefix
[] = "**";
47 const char kActionTriggerDeleteValues
[] = "deletevalues";
48 const char kActionTriggerDel
[] = "del.";
49 const char kActionTriggerDelVals
[] = "delvals";
50 const char kActionTriggerDeleteKeys
[] = "deletekeys";
51 const char kActionTriggerSecureKey
[] = "securekey";
52 const char kActionTriggerSoft
[] = "soft";
54 // Returns the character at |cursor| and increments it, unless the end is here
55 // in which case -1 is returned.
56 int NextChar(const uint8
** cursor
, const uint8
* end
) {
57 // Only read the character if a full base::char16 is available.
58 if (*cursor
+ sizeof(base::char16
) > end
)
61 int result
= **cursor
| (*(*cursor
+ 1) << 8);
62 *cursor
+= sizeof(base::char16
);
66 // Reads a fixed-size field from a PReg file.
67 bool ReadFieldBinary(const uint8
** cursor
,
74 const uint8
* field_end
= *cursor
+ size
;
75 if (field_end
<= *cursor
|| field_end
> end
)
77 std::copy(*cursor
, field_end
, data
);
82 bool ReadField32(const uint8
** cursor
, const uint8
* end
, uint32
* data
) {
84 if (!ReadFieldBinary(cursor
, end
, sizeof(uint32
),
85 reinterpret_cast<uint8
*>(&value
))) {
88 *data
= base::ByteSwapToLE32(value
);
92 // Reads a string field from a file.
93 bool ReadFieldString(const uint8
** cursor
,
95 base::string16
* str
) {
97 while ((current
= NextChar(cursor
, end
)) > 0x0000)
100 return current
== L
'\0';
103 std::string
DecodePRegStringValue(const std::vector
<uint8
>& data
) {
104 size_t len
= data
.size() / sizeof(base::char16
);
106 return std::string();
108 const base::char16
* chars
=
109 reinterpret_cast<const base::char16
*>(vector_as_array(&data
));
110 base::string16 result
;
111 std::transform(chars
, chars
+ len
- 1, std::back_inserter(result
),
112 std::ptr_fun(base::ByteSwapToLE16
));
113 return base::UTF16ToUTF8(result
);
116 // Decodes a value from a PReg file given as a uint8 vector.
117 bool DecodePRegValue(uint32 type
,
118 const std::vector
<uint8
>& data
,
119 scoped_ptr
<base::Value
>* value
) {
123 value
->reset(new base::StringValue(DecodePRegStringValue(data
)));
125 case REG_DWORD_LITTLE_ENDIAN
:
126 case REG_DWORD_BIG_ENDIAN
:
127 if (data
.size() == sizeof(uint32
)) {
128 uint32 val
= *reinterpret_cast<const uint32
*>(vector_as_array(&data
));
129 if (type
== REG_DWORD_BIG_ENDIAN
)
130 val
= base::NetToHost32(val
);
132 val
= base::ByteSwapToLE32(val
);
133 value
->reset(new base::FundamentalValue(static_cast<int>(val
)));
136 LOG(ERROR
) << "Bad data size " << data
.size();
142 case REG_RESOURCE_LIST
:
143 case REG_FULL_RESOURCE_DESCRIPTOR
:
144 case REG_RESOURCE_REQUIREMENTS_LIST
:
145 case REG_QWORD_LITTLE_ENDIAN
:
147 LOG(ERROR
) << "Unsupported registry data type " << type
;
153 // Adds the record data passed via parameters to |dict| in case the data is
154 // relevant policy for Chromium.
155 void HandleRecord(const base::string16
& key_name
,
156 const base::string16
& value
,
158 const std::vector
<uint8
>& data
,
159 RegistryDict
* dict
) {
160 // Locate/create the dictionary to place the value in.
161 std::vector
<base::string16
> path
;
163 for (const base::string16
& entry
:
164 base::SplitString(key_name
, kRegistryPathSeparator
,
165 base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
)) {
168 const std::string name
= base::UTF16ToUTF8(entry
);
169 RegistryDict
* subdict
= dict
->GetKey(name
);
171 subdict
= new RegistryDict();
172 dict
->SetKey(name
, make_scoped_ptr(subdict
));
180 std::string
value_name(base::UTF16ToUTF8(value
));
181 if (!base::StartsWith(value_name
, kActionTriggerPrefix
,
182 base::CompareCase::SENSITIVE
)) {
183 scoped_ptr
<base::Value
> value
;
184 if (DecodePRegValue(type
, data
, &value
))
185 dict
->SetValue(value_name
, value
.Pass());
189 std::string
action_trigger(base::ToLowerASCII(value_name
.substr(
190 arraysize(kActionTriggerPrefix
) - 1)));
191 if (action_trigger
== kActionTriggerDeleteValues
) {
192 for (const std::string
& value
:
193 base::SplitString(DecodePRegStringValue(data
), ";",
194 base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
))
195 dict
->RemoveValue(value
);
196 } else if (base::StartsWith(action_trigger
, kActionTriggerDeleteKeys
,
197 base::CompareCase::SENSITIVE
)) {
198 for (const std::string
& key
:
199 base::SplitString(DecodePRegStringValue(data
), ";",
200 base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
))
201 dict
->RemoveKey(key
);
202 } else if (base::StartsWith(action_trigger
, kActionTriggerDel
,
203 base::CompareCase::SENSITIVE
)) {
205 value_name
.substr(arraysize(kActionTriggerPrefix
) - 1 +
206 arraysize(kActionTriggerDel
) - 1));
207 } else if (base::StartsWith(action_trigger
, kActionTriggerDelVals
,
208 base::CompareCase::SENSITIVE
)) {
209 // Delete all values.
211 } else if (base::StartsWith(action_trigger
, kActionTriggerSecureKey
,
212 base::CompareCase::SENSITIVE
) ||
213 base::StartsWith(action_trigger
, kActionTriggerSoft
,
214 base::CompareCase::SENSITIVE
)) {
215 // Doesn't affect values.
217 LOG(ERROR
) << "Bad action trigger " << value_name
;
221 bool ReadFile(const base::FilePath
& file_path
,
222 const base::string16
& root
,
224 PolicyLoadStatusSample
* status
) {
225 base::MemoryMappedFile mapped_file
;
226 if (!mapped_file
.Initialize(file_path
) || !mapped_file
.IsValid()) {
227 PLOG(ERROR
) << "Failed to map " << file_path
.value();
228 status
->Add(POLICY_LOAD_STATUS_READ_ERROR
);
232 if (mapped_file
.length() > kMaxPRegFileSize
) {
233 LOG(ERROR
) << "PReg file " << file_path
.value() << " too large: "
234 << mapped_file
.length();
235 status
->Add(POLICY_LOAD_STATUS_TOO_BIG
);
240 const int kHeaderSize
= arraysize(kPRegFileHeader
);
241 if (mapped_file
.length() < kHeaderSize
||
242 memcmp(kPRegFileHeader
, mapped_file
.data(), kHeaderSize
) != 0) {
243 LOG(ERROR
) << "Bad policy file " << file_path
.value();
244 status
->Add(POLICY_LOAD_STATUS_PARSE_ERROR
);
248 // Parse file contents, which is UCS-2 and little-endian. The latter I
249 // couldn't find documentation on, but the example I saw were all
250 // little-endian. It'd be interesting to check on big-endian hardware.
251 const uint8
* cursor
= mapped_file
.data() + kHeaderSize
;
252 const uint8
* end
= mapped_file
.data() + mapped_file
.length();
257 if (NextChar(&cursor
, end
) != kDelimBracketOpen
)
260 // Read the record fields.
261 base::string16 key_name
;
262 base::string16 value
;
265 std::vector
<uint8
> data
;
267 if (!ReadFieldString(&cursor
, end
, &key_name
))
270 int current
= NextChar(&cursor
, end
);
271 if (current
== kDelimSemicolon
) {
272 if (!ReadFieldString(&cursor
, end
, &value
))
274 current
= NextChar(&cursor
, end
);
277 if (current
== kDelimSemicolon
) {
278 if (!ReadField32(&cursor
, end
, &type
))
280 current
= NextChar(&cursor
, end
);
283 if (current
== kDelimSemicolon
) {
284 if (!ReadField32(&cursor
, end
, &size
))
286 current
= NextChar(&cursor
, end
);
289 if (current
== kDelimSemicolon
) {
290 if (size
> kMaxPRegFileSize
)
293 if (!ReadFieldBinary(&cursor
, end
, size
, vector_as_array(&data
)))
295 current
= NextChar(&cursor
, end
);
298 if (current
!= kDelimBracketClose
)
301 // Process the record if it is within the |root| subtree.
302 if (base::StartsWith(base::i18n::ToLower(key_name
),
303 base::i18n::ToLower(root
),
304 base::CompareCase::SENSITIVE
))
305 HandleRecord(key_name
.substr(root
.size()), value
, type
, data
, dict
);
308 LOG(ERROR
) << "Error parsing " << file_path
.value() << " at offset "
309 << reinterpret_cast<const uint8
*>(cursor
- 1) - mapped_file
.data();
310 status
->Add(POLICY_LOAD_STATUS_PARSE_ERROR
);
314 } // namespace preg_parser
315 } // namespace policy