chromium/chrome/installer/util/registry_key_backup.cc

// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/installer/util/registry_key_backup.h"

#include <stdint.h>

#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/logging.h"
#include "base/win/registry.h"

using base::win::RegKey;

namespace {

const REGSAM kKeyReadNoNotify = (KEY_READ) & ~(KEY_NOTIFY);

// A container for a registry value.
class ValueData {
 public:
  ValueData();
  ~ValueData();

  // Initializes this object with a name (the first |name_size| characters in
  // |name_buffer|, |type|, and data (the first |data_size| bytes in |data|).
  void Initialize(const wchar_t* name_buffer,
                  DWORD name_size,
                  DWORD type,
                  const uint8_t* data,
                  DWORD data_size);

  // The possibly empty name of this value.
  const std::wstring& name_str() const { return name_; }

  // The name of this value, or nullptr for the default (unnamed) value.
  const wchar_t* name() const {
    return name_.empty() ? nullptr : name_.c_str();
  }

  // The type of this value.
  DWORD type() const { return type_; }

  // A pointer to a buffer of |data_len()| bytes containing the value's data,
  // or nullptr if the value has no data.
  const uint8_t* data() const { return data_.empty() ? nullptr : &data_[0]; }

  // The size, in bytes, of the value's data.
  DWORD data_len() const { return static_cast<DWORD>(data_.size()); }

 private:
  // This value's name, or the empty string if this is the default (unnamed)
  // value.
  std::wstring name_;
  // This value's data.
  std::vector<uint8_t> data_;
  // This value's type (e.g., REG_DWORD, REG_SZ, REG_QWORD, etc).
  DWORD type_;

  // Copy constructible and assignable for use in STL containers.
};

}  // namespace

// A container for a registry key, its values, and its subkeys.
class RegistryKeyBackup::KeyData {
 public:
  KeyData();
  ~KeyData();

  // Initializes this object by reading the values and subkeys of |key|.
  // Security descriptors are not backed up.  Returns true if the operation was
  // successful; false otherwise, in which case the state of this object is not
  // modified.
  bool Initialize(const RegKey& key);

  // Writes the contents of this object to |key|, which must have been opened
  // with at least REG_SET_VALUE and KEY_CREATE_SUB_KEY access rights.  Returns
  // true if the operation was successful; false otherwise, in which case the
  // contents of |key| may have been modified.
  bool WriteTo(RegKey* key) const;

 private:
  // The values of this key.
  std::vector<ValueData> values_;
  // Map of subkey names to the corresponding KeyData.
  std::map<std::wstring, KeyData> subkeys_;

  // Copy constructible and assignable for use in STL containers.
};

ValueData::ValueData() : type_(REG_NONE) {}

ValueData::~ValueData() {}

void ValueData::Initialize(const wchar_t* name_buffer,
                           DWORD name_size,
                           DWORD type,
                           const uint8_t* data,
                           DWORD data_size) {
  name_.assign(name_buffer, name_size);
  type_ = type;
  data_.assign(data, data + data_size);
}

RegistryKeyBackup::KeyData::KeyData() {}

RegistryKeyBackup::KeyData::~KeyData() {}

bool RegistryKeyBackup::KeyData::Initialize(const RegKey& key) {
  std::vector<ValueData> values;
  std::map<std::wstring, KeyData> subkeys;

  DWORD num_subkeys = 0;
  DWORD max_subkey_name_len = 0;
  DWORD num_values = 0;
  DWORD max_value_name_len = 0;
  DWORD max_value_len = 0;
  LONG result =
      RegQueryInfoKey(key.Handle(), nullptr, nullptr, nullptr, &num_subkeys,
                      &max_subkey_name_len, nullptr, &num_values,
                      &max_value_name_len, &max_value_len, nullptr, nullptr);
  if (result != ERROR_SUCCESS) {
    LOG(ERROR) << "Failed getting info of key to backup, result: " << result;
    return false;
  }
  DWORD max_name_len = std::max(max_subkey_name_len, max_value_name_len) + 1;
  std::vector<wchar_t> name_buffer(max_name_len);

  // Backup the values.
  if (num_values != 0) {
    values.reserve(num_values);
    std::vector<uint8_t> value_buffer(max_value_len != 0 ? max_value_len : 1);
    DWORD name_size = 0;
    DWORD value_type = REG_NONE;
    DWORD value_size = 0;

    for (DWORD i = 0; i < num_values;) {
      name_size = static_cast<DWORD>(name_buffer.size());
      value_size = static_cast<DWORD>(value_buffer.size());
      result =
          RegEnumValue(key.Handle(), i, &name_buffer[0], &name_size, nullptr,
                       &value_type, &value_buffer[0], &value_size);
      switch (result) {
        case ERROR_NO_MORE_ITEMS:
          num_values = i;
          break;
        case ERROR_SUCCESS:
          values.push_back(ValueData());
          values.back().Initialize(&name_buffer[0], name_size, value_type,
                                   &value_buffer[0], value_size);
          ++i;
          break;
        case ERROR_MORE_DATA:
          if (value_size > value_buffer.size())
            value_buffer.resize(value_size);
          // |name_size| does not include space for the string terminator.
          if (name_size + 1 > name_buffer.size())
            name_buffer.resize(name_size + 1);
          break;
        default:
          LOG(ERROR) << "Failed backing up value " << i
                     << ", result: " << result;
          return false;
      }
    }
    DLOG_IF(WARNING, RegEnumValue(key.Handle(), num_values, &name_buffer[0],
                                  &name_size, nullptr, &value_type, nullptr,
                                  nullptr) != ERROR_NO_MORE_ITEMS)
        << "Concurrent modifications to registry key during backup operation.";
  }

  // Backup the subkeys.
  if (num_subkeys != 0) {
    DWORD name_size = 0;

    // Get the names of them.
    for (DWORD i = 0; i < num_subkeys;) {
      name_size = static_cast<DWORD>(name_buffer.size());
      result = RegEnumKeyEx(key.Handle(), i, &name_buffer[0], &name_size,
                            nullptr, nullptr, nullptr, nullptr);
      switch (result) {
        case ERROR_NO_MORE_ITEMS:
          num_subkeys = i;
          break;
        case ERROR_SUCCESS:
          subkeys.insert(std::make_pair(&name_buffer[0], KeyData()));
          ++i;
          break;
        case ERROR_MORE_DATA:
          name_buffer.resize(name_size + 1);
          break;
        default:
          LOG(ERROR) << "Failed getting name of subkey " << i
                     << " for backup, result: " << result;
          return false;
      }
    }
    DLOG_IF(WARNING, RegEnumKeyEx(key.Handle(), num_subkeys, nullptr,
                                  &name_size, nullptr, nullptr, nullptr,
                                  nullptr) != ERROR_NO_MORE_ITEMS)
        << "Concurrent modifications to registry key during backup operation.";

    // Get their values.
    RegKey subkey;
    for (std::map<std::wstring, KeyData>::iterator it = subkeys.begin();
         it != subkeys.end(); ++it) {
      result = subkey.Open(key.Handle(), it->first.c_str(), kKeyReadNoNotify);
      if (result != ERROR_SUCCESS) {
        LOG(ERROR) << "Failed opening subkey \"" << it->first
                   << "\" for backup, result: " << result;
        return false;
      }
      if (!it->second.Initialize(subkey)) {
        LOG(ERROR) << "Failed backing up subkey \"" << it->first << "\"";
        return false;
      }
    }
  }

  values_.swap(values);
  subkeys_.swap(subkeys);

  return true;
}

bool RegistryKeyBackup::KeyData::WriteTo(RegKey* key) const {
  DCHECK(key);

  LONG result = ERROR_SUCCESS;

  // Write the values.
  for (std::vector<ValueData>::const_iterator it = values_.begin();
       it != values_.end(); ++it) {
    const ValueData& value = *it;
    result = RegSetValueEx(key->Handle(), value.name(), 0, value.type(),
                           value.data(), value.data_len());
    if (result != ERROR_SUCCESS) {
      LOG(ERROR) << "Failed writing value \"" << value.name_str()
                 << "\", result: " << result;
      return false;
    }
  }

  // Write the subkeys.
  RegKey subkey;
  for (std::map<std::wstring, KeyData>::const_iterator it = subkeys_.begin();
       it != subkeys_.end(); ++it) {
    const std::wstring& name = it->first;

    result = subkey.Create(key->Handle(), name.c_str(), KEY_WRITE);
    if (result != ERROR_SUCCESS) {
      LOG(ERROR) << "Failed creating subkey \"" << name
                 << "\", result: " << result;
      return false;
    }
    if (!it->second.WriteTo(&subkey)) {
      LOG(ERROR) << "Failed writing subkey \"" << name
                 << "\", result: " << result;
      return false;
    }
  }

  return true;
}

RegistryKeyBackup::RegistryKeyBackup() {}

RegistryKeyBackup::~RegistryKeyBackup() {}

bool RegistryKeyBackup::Initialize(HKEY root,
                                   const wchar_t* key_path,
                                   REGSAM wow64_access) {
  DCHECK(key_path);
  DCHECK(wow64_access == 0 || wow64_access == KEY_WOW64_32KEY ||
         wow64_access == KEY_WOW64_64KEY);

  RegKey key;
  std::unique_ptr<KeyData> key_data;

  // Does the key exist?
  LONG result = key.Open(root, key_path, kKeyReadNoNotify | wow64_access);
  if (result == ERROR_SUCCESS) {
    key_data = std::make_unique<KeyData>();
    if (!key_data->Initialize(key)) {
      LOG(ERROR) << "Failed to backup key at " << key_path;
      return false;
    }
  } else if (result != ERROR_FILE_NOT_FOUND) {
    LOG(ERROR) << "Failed to open key at " << key_path
               << " to create backup, result: " << result;
    return false;
  }

  key_data_.swap(key_data);
  return true;
}

bool RegistryKeyBackup::WriteTo(HKEY root,
                                const wchar_t* key_path,
                                REGSAM wow64_access) const {
  DCHECK(key_path);
  DCHECK(wow64_access == 0 || wow64_access == KEY_WOW64_32KEY ||
         wow64_access == KEY_WOW64_64KEY);

  bool success = false;

  if (key_data_) {
    RegKey dest_key;
    LONG result = dest_key.Create(root, key_path, KEY_WRITE | wow64_access);
    if (result != ERROR_SUCCESS) {
      LOG(ERROR) << "Failed to create destination key at " << key_path
                 << " to write backup, result: " << result;
    } else {
      success = key_data_->WriteTo(&dest_key);
      LOG_IF(ERROR, !success) << "Failed to write key data.";
    }
  } else {
    success = true;
  }

  return success;
}