Home | History | Annotate | Download | only in common
      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 "components/policy/core/common/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 "components/policy/core/common/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 base::char16 kDelimBracketOpen = L'[';
     37 const base::char16 kDelimBracketClose = L']';
     38 const base::char16 kDelimSemicolon = L';';
     39 
     40 // Registry path separator.
     41 const base::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 base::char16 is available.
     56   if (*cursor + sizeof(base::char16) > end)
     57     return -1;
     58 
     59   int result = **cursor | (*(*cursor + 1) << 8);
     60   *cursor += sizeof(base::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,
     92                      const uint8* end,
     93                      base::string16* str) {
     94   int current = -1;
     95   while ((current = NextChar(cursor, end)) > 0x0000)
     96     *str += current;
     97 
     98   return current == L'\0';
     99 }
    100 
    101 std::string DecodePRegStringValue(const std::vector<uint8>& data) {
    102   size_t len = data.size() / sizeof(base::char16);
    103   if (len <= 0)
    104     return std::string();
    105 
    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);
    112 }
    113 
    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) {
    118   switch (type) {
    119     case REG_SZ:
    120     case REG_EXPAND_SZ:
    121       value->reset(new base::StringValue(DecodePRegStringValue(data)));
    122       return true;
    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);
    129         else
    130           val = base::ByteSwapToLE32(val);
    131         value->reset(new base::FundamentalValue(static_cast<int>(val)));
    132         return true;
    133       } else {
    134         LOG(ERROR) << "Bad data size " << data.size();
    135       }
    136       break;
    137     case REG_NONE:
    138     case REG_LINK:
    139     case REG_MULTI_SZ:
    140     case REG_RESOURCE_LIST:
    141     case REG_FULL_RESOURCE_DESCRIPTOR:
    142     case REG_RESOURCE_REQUIREMENTS_LIST:
    143     case REG_QWORD_LITTLE_ENDIAN:
    144     default:
    145       LOG(ERROR) << "Unsupported registry data type " << type;
    146   }
    147 
    148   return false;
    149 }
    150 
    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,
    155                   uint32 type,
    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;
    160 
    161   Tokenize(key_name, kRegistryPathSeparator, &path);
    162   for (std::vector<base::string16>::const_iterator entry(path.begin());
    163        entry != path.end(); ++entry) {
    164     if (entry->empty())
    165       continue;
    166     const std::string name = base::UTF16ToUTF8(*entry);
    167     RegistryDict* subdict = dict->GetKey(name);
    168     if (!subdict) {
    169       subdict = new RegistryDict();
    170       dict->SetKey(name, make_scoped_ptr(subdict));
    171     }
    172     dict = subdict;
    173   }
    174 
    175   if (value.empty())
    176     return;
    177 
    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());
    183     return;
    184   }
    185 
    186   std::string action_trigger(base::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);
    194     }
    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);
    201     }
    202   } else if (StartsWithASCII(action_trigger, kActionTriggerDel, true)) {
    203     dict->RemoveValue(
    204         value_name.substr(arraysize(kActionTriggerPrefix) - 1 +
    205                           arraysize(kActionTriggerDel) - 1));
    206   } else if (StartsWithASCII(action_trigger, kActionTriggerDelVals, true)) {
    207     // Delete all values.
    208     dict->ClearValues();
    209   } else if (StartsWithASCII(action_trigger, kActionTriggerSecureKey, true) ||
    210              StartsWithASCII(action_trigger, kActionTriggerSoft, true)) {
    211     // Doesn't affect values.
    212   } else {
    213     LOG(ERROR) << "Bad action trigger " << value_name;
    214   }
    215 }
    216 
    217 bool ReadFile(const base::FilePath& file_path,
    218               const base::string16& root,
    219               RegistryDict* dict,
    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);
    225     return false;
    226   }
    227 
    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);
    232     return false;
    233   }
    234 
    235   // Check the header.
    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);
    241     return false;
    242   }
    243 
    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();
    249   while (true) {
    250     if (cursor == end)
    251       return true;
    252 
    253     if (NextChar(&cursor, end) != kDelimBracketOpen)
    254       break;
    255 
    256     // Read the record fields.
    257     base::string16 key_name;
    258     base::string16 value;
    259     uint32 type = 0;
    260     uint32 size = 0;
    261     std::vector<uint8> data;
    262 
    263     if (!ReadFieldString(&cursor, end, &key_name))
    264       break;
    265 
    266     int current = NextChar(&cursor, end);
    267     if (current == kDelimSemicolon) {
    268       if (!ReadFieldString(&cursor, end, &value))
    269         break;
    270       current = NextChar(&cursor, end);
    271     }
    272 
    273     if (current == kDelimSemicolon) {
    274       if (!ReadField32(&cursor, end, &type))
    275         break;
    276       current = NextChar(&cursor, end);
    277     }
    278 
    279     if (current == kDelimSemicolon) {
    280       if (!ReadField32(&cursor, end, &size))
    281         break;
    282       current = NextChar(&cursor, end);
    283     }
    284 
    285     if (current == kDelimSemicolon) {
    286       if (size > kMaxPRegFileSize)
    287         break;
    288       data.resize(size);
    289       if (!ReadFieldBinary(&cursor, end, size, vector_as_array(&data)))
    290         break;
    291       current = NextChar(&cursor, end);
    292     }
    293 
    294     if (current != kDelimBracketClose)
    295       break;
    296 
    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);
    300   }
    301 
    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);
    305   return false;
    306 }
    307 
    308 }  // namespace preg_parser
    309 }  // namespace policy
    310