blob: 39c329e16db5abfa681c1b79cb5bae22c17d3225 [file] [log] [blame]
// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "settingsd/settings_document_manager.h"
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "settingsd/identifier_utils.h"
#include "settingsd/locked_settings.h"
#include "settingsd/settings_document.h"
#include "settingsd/settings_keys.h"
#include "settingsd/settings_map.h"
namespace settingsd {
namespace {
// Determines which sources changed their configuration according to
// |changed_keys|. The corresponding source IDs are added to
// |sources_to_revalidate|.
void UpdateSourceValidationQueue(
const std::set<Key>& changed_keys,
std::priority_queue<std::string>* sources_to_revalidate) {
const Key source_prefix(Key({keys::kSettingsdPrefix, keys::kSources}));
std::string last_source_id;
for (Key source_key : utils::GetRange(source_prefix, changed_keys)) {
// TODO(mnissler): handle nested sources properly.
Key source_suffix;
if (!source_key.Suffix(source_prefix, &source_suffix)) {
NOTREACHED() << "Bad source key " << source_key.ToString();
continue;
}
const std::string source_id = source_suffix.Split(nullptr).ToString();
if (source_id != last_source_id)
sources_to_revalidate->push(source_id);
last_source_id = source_id;
}
}
// A comparator for std::vector<std::unique_ptr<SettingsDocument>> that is
// sorted by the version stamp component corresponding to a specified source.
class DocumentVersionStampComparator {
public:
explicit DocumentVersionStampComparator(const std::string& source_id)
: source_id_(source_id) {}
bool operator()(const std::unique_ptr<const SettingsDocument>& doc_a,
const std::unique_ptr<const SettingsDocument>& doc_b) {
return doc_a->GetVersionStamp().Get(source_id_) <
doc_b->GetVersionStamp().Get(source_id_);
}
private:
std::string source_id_;
};
} // namespace
SettingsDocumentManager::SourceMapEntry::SourceMapEntry(
const std::string& source_id)
: source_(source_id) {}
SettingsDocumentManager::SourceMapEntry::~SourceMapEntry() {}
SettingsDocumentManager::SettingsDocumentManager(
const SettingsBlobParserFunction& settings_blob_parser_function,
const SourceDelegateFactoryFunction& source_delegate_factory_function,
const std::string& storage_path,
std::unique_ptr<SettingsMap> settings_map,
std::unique_ptr<const SettingsDocument> trusted_document)
: settings_blob_parser_function_(settings_blob_parser_function),
source_delegate_factory_function_(source_delegate_factory_function),
trusted_document_(std::move(trusted_document)),
blob_store_(storage_path),
settings_map_(std::move(settings_map)) {
// The trusted document should have an empty version stamp.
CHECK(!VersionStamp().IsBefore(trusted_document_->GetVersionStamp()));
}
SettingsDocumentManager::~SettingsDocumentManager() {}
void SettingsDocumentManager::Init() {
settings_map_->Clear();
// Insert the trusted document.
std::set<Key> changed_keys;
std::vector<const SettingsDocument*> unreferenced_documents;
CHECK(settings_map_->InsertDocument(trusted_document_.get(), &changed_keys,
&unreferenced_documents));
if (!unreferenced_documents.empty())
LOG(ERROR) << "Initial SettingsDocument is empty.";
UpdateTrustConfiguration(&changed_keys);
// |trusted_document| should have installed at least one source.
if (sources_.empty())
LOG(WARNING) << "Initial settings document has not added any sources.";
// Load settings blobs from disk for known sources in the order of the source
// hierarchy.
for (auto source_iter = sources_.begin(); source_iter != sources_.end();
++source_iter) {
const std::string current_source_id = source_iter->first;
// Find all documents for the source and InsertBlob() 'em.
for (const auto& handle : blob_store_.List(current_source_id)) {
const std::vector<uint8_t> blob = blob_store_.Load(handle);
InsertionStatus status = InsertBlob(current_source_id, BlobRef(&blob));
if (status != kInsertionStatusSuccess) {
LOG(ERROR) << "Failed to load settings blob for source "
<< current_source_id;
}
}
// Sources cannot remove themselves.
CHECK(sources_.end() != sources_.find(current_source_id));
}
}
BlobRef SettingsDocumentManager::GetValue(const Key& key) const {
return settings_map_->GetValue(key);
}
const std::set<Key> SettingsDocumentManager::GetKeys(const Key& prefix) const {
return settings_map_->GetKeys(prefix);
}
void SettingsDocumentManager::AddSettingsObserver(SettingsObserver* observer) {
observers_.AddObserver(observer);
}
void SettingsDocumentManager::RemoveSettingsObserver(
SettingsObserver* observer) {
observers_.RemoveObserver(observer);
}
SettingsDocumentManager::InsertionStatus SettingsDocumentManager::InsertBlob(
const std::string& source_id,
BlobRef blob) {
const Source* source = FindSource(source_id);
if (!source)
return kInsertionStatusUnknownSource;
// Parse and validate the blob.
std::unique_ptr<LockedSettingsContainer> container;
SettingsDocumentManager::InsertionStatus status =
ParseAndValidateBlob(source, blob, &container);
if (status != kInsertionStatusSuccess)
return status;
// Blob validation looks good. Unwrap the SettingsDocument and insert it.
std::unique_ptr<SettingsDocument> document(
LockedSettingsContainer::DecodePayload(std::move(container)));
if (!document)
return kInsertionStatusBadPayload;
document->source_id_ = source_id;
// Write the blob to the BlobStore.
BlobStore::Handle handle = blob_store_.Store(source_id, blob);
if (!handle.IsValid())
return kInsertionStatusStorageFailure;
document->handle_ = handle;
status = InsertDocument(std::move(document));
// If the insertion failed remove the corresponding blob from the BlobStore.
if (status != kInsertionStatusSuccess && !blob_store_.Purge(handle))
LOG(ERROR) << "Failed to purge document.";
return status;
}
SettingsDocumentManager::InsertionStatus
SettingsDocumentManager::InsertDocument(
std::unique_ptr<const SettingsDocument> document) {
DCHECK(!document->source_id_.empty());
DCHECK(document->handle_.IsValid());
auto source_iter = sources_.find(document->source_id_);
CHECK(sources_.end() != source_iter);
SourceMapEntry& entry = source_iter->second;
// Make sure that all documents from the same source are in well-defined order
// to each other, regardless of whether they overlap or not.
const auto insertion_point = std::lower_bound(
entry.documents_.begin(), entry.documents_.end(), document,
DocumentVersionStampComparator(document->source_id_));
if (insertion_point != entry.documents_.end() &&
(*insertion_point)->GetVersionStamp().Get(document->source_id_) ==
document->GetVersionStamp().Get(document->source_id_)) {
return kInsertionStatusVersionClash;
}
// Perform access control checks.
if (!entry.source_.CheckAccess(document.get(), kSettingStatusActive))
return kInsertionStatusAccessViolation;
// Everything looks good, attempt the insertion.
std::set<Key> changed_keys;
std::vector<const SettingsDocument*> unreferenced_documents;
if (!settings_map_->InsertDocument(document.get(), &changed_keys,
&unreferenced_documents)) {
DCHECK(unreferenced_documents.empty());
return kInsertionStatusCollision;
}
entry.documents_.insert(insertion_point, std::move(document));
// Purge any unreferenced documents. This might actually include
// |document|, if e.g. all values |document| provides are already clobbered by
// a newer document inserted before.
for (auto unreferenced_document : unreferenced_documents) {
if (!PurgeBlobAndDocument(unreferenced_document))
LOG(ERROR) << "Failed to purge unreferenced document";
}
// Process any trust configuration changes.
UpdateTrustConfiguration(&changed_keys);
FOR_EACH_OBSERVER(SettingsObserver, observers_,
OnSettingsChanged(changed_keys));
return kInsertionStatusSuccess;
}
void SettingsDocumentManager::RevalidateSourceDocuments(
const SourceMapEntry& entry,
std::set<Key>* changed_keys,
std::priority_queue<std::string>* sources_to_revalidate) {
std::vector<const SettingsDocument*> obsolete_documents;
for (auto& doc : entry.documents_) {
if (RevalidateDocument(&entry.source_, doc.get()))
// |doc| is still valid.
continue;
// |doc| is no longer valid, remove it from the |settings_map_|.
std::set<Key> keys_changed_by_removal;
std::vector<const SettingsDocument*> unreferenced_documents;
settings_map_->RemoveDocument(doc.get(), &keys_changed_by_removal,
&unreferenced_documents);
// Note that the document that is being removed here, must not be removed
// from its DocumentEntry vector, since it gets added to the list of
// unreferenced documents anyways. Since these are deleted below this
// would result in a double free().
obsolete_documents.insert(obsolete_documents.end(),
unreferenced_documents.begin(),
unreferenced_documents.end());
UpdateSourceValidationQueue(keys_changed_by_removal, sources_to_revalidate);
// Update |changed_keys| to include the keys affected by the removal.
changed_keys->insert(keys_changed_by_removal.begin(),
keys_changed_by_removal.end());
}
// Purge any unreferenced documents.
for (auto obsolete_document : obsolete_documents) {
if (!PurgeBlobAndDocument(obsolete_document))
LOG(ERROR) << "Failed to purge unreferenced document";
}
}
void SettingsDocumentManager::UpdateTrustConfiguration(
std::set<Key>* changed_keys) {
// A priority queue of sources that have pending configuration changes and
// need to be re-parsed and their settings documents be validated. Affected
// sources are processed in lexicographic priority order, because source
// configuration changes may cascade: Changing a source may invalidate
// delegations it has made, resulting in further source configuration changes.
// However, these can only affect sources that have a lower priority than the
// current, so they'll show up after the current source in the validation
// queue. Hence, the code can just make one pass through affected sources in
// order and be sure to catch all cascading changes.
std::priority_queue<std::string> sources_to_revalidate;
UpdateSourceValidationQueue(*changed_keys, &sources_to_revalidate);
while (!sources_to_revalidate.empty()) {
// Pick the highest-priority source.
const std::string source_id = sources_to_revalidate.top();
do {
sources_to_revalidate.pop();
} while (!sources_to_revalidate.empty() &&
source_id == sources_to_revalidate.top());
// Get or create the source map entry.
SourceMapEntry& entry = sources_.emplace(std::piecewise_construct,
std::forward_as_tuple(source_id),
std::forward_as_tuple(source_id))
.first->second;
// Re-parse the source configuration. If the source is no longer explicitly
// configured, purge it (after removing its documents below).
bool purge_source =
!entry.source_.Update(source_delegate_factory_function_, *this);
// Re-validate all documents belonging to this source.
RevalidateSourceDocuments(entry, changed_keys, &sources_to_revalidate);
if (purge_source)
sources_.erase(source_id);
}
}
bool SettingsDocumentManager::PurgeBlobAndDocument(
const SettingsDocument* document) {
auto source_iter = sources_.find(document->source_id_);
if (source_iter == sources_.end())
return false;
std::unique_ptr<const SettingsDocument> dummy_for_lower_bound_search(
document);
auto document_iter = std::lower_bound(
source_iter->second.documents_.begin(),
source_iter->second.documents_.end(), dummy_for_lower_bound_search,
DocumentVersionStampComparator(document->source_id_));
dummy_for_lower_bound_search.release();
if (document_iter == source_iter->second.documents_.end())
return false;
BlobStore::Handle handle((*document_iter)->handle_);
source_iter->second.documents_.erase(document_iter);
return blob_store_.Purge(handle);
}
SettingsDocumentManager::InsertionStatus
SettingsDocumentManager::ParseAndValidateBlob(
const Source* source,
BlobRef blob,
std::unique_ptr<LockedSettingsContainer>* container) const {
// Parse the blob. Try with the formats allowed by the source. If there are no
// formats in source config, try the default format (identified by empty
// string).
for (const std::string& format : source->blob_formats()) {
*container = settings_blob_parser_function_(format, blob);
if (*container)
break;
}
if (!*container && source->blob_formats().empty())
*container = settings_blob_parser_function_(std::string(), blob);
if (!*container)
return kInsertionStatusParseError;
// Validate the blob, i.e. check its integrity and verify it's authentic
// with respect to the corresponding source configuration.
if (!source->delegate()->ValidateContainer(*container->get()))
return kInsertionStatusValidationError;
// Validate the blob's version components.
for (auto& component : (*container)->GetVersionComponents()) {
const Source* version_source = FindSource(component->GetSourceId());
if (!version_source ||
!version_source->delegate()->ValidateVersionComponent(*component)) {
return kInsertionStatusValidationError;
}
}
return kInsertionStatusSuccess;
}
bool SettingsDocumentManager::RevalidateDocument(
const Source* source,
const SettingsDocument* doc) const {
// Load corresponding blob from the BlobStore.
const std::vector<uint8_t> blob = blob_store_.Load(doc->handle_);
// Parse and validate the document against the SourceDelegate. |container| is
// discarded without being used in this method, as we are only interested in
// whether the LockedSettingsContainer can be validated.
std::unique_ptr<LockedSettingsContainer> container;
if (ParseAndValidateBlob(source, BlobRef(&blob), &container) !=
kInsertionStatusSuccess) {
return false;
}
// NB: When re-validating documents, "withdrawn" status is sufficient.
return source->CheckAccess(doc, kSettingStatusWithdrawn);
}
const Source* SettingsDocumentManager::FindSource(
const std::string& source_id) const {
auto source_iter = sources_.find(source_id);
return source_iter == sources_.end() ? nullptr : &source_iter->second.source_;
}
} // namespace settingsd