blob: 86ade2325c128e60b8ac700e5d9f3cadcab9ad06 [file] [log] [blame]
// Copyright (c) 2012 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.
//
// Provides the implementation of StatefulRecovery.
#include "cryptohome/stateful_recovery.h"
#include <linux/reboot.h>
#include <sys/reboot.h>
#include <unistd.h>
#include <string>
#include <base/files/file_path.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include "cryptohome/platform.h"
using base::FilePath;
namespace cryptohome {
const char StatefulRecovery::kRecoverSource[] =
"/mnt/stateful_partition/encrypted";
const char StatefulRecovery::kRecoverDestination[] =
"/mnt/stateful_partition/decrypted";
const char StatefulRecovery::kRecoverBlockUsage[] =
"/mnt/stateful_partition/decrypted/block-usage.txt";
const char StatefulRecovery::kRecoverFilesystemDetails[] =
"/mnt/stateful_partition/decrypted/filesystem-details.txt";
const char StatefulRecovery::kFlagFile[] =
"/mnt/stateful_partition/decrypt_stateful";
StatefulRecovery::StatefulRecovery(Platform* platform,
MountFunction mountfn,
UnmountFunction unmountfn,
IsOwnerFunction isownerfn)
: requested_(false),
platform_(platform),
mountfn_(mountfn),
unmountfn_(unmountfn),
isownerfn_(isownerfn) {}
bool StatefulRecovery::Requested() {
requested_ = ParseFlagFile();
return requested_;
}
bool StatefulRecovery::CopyPartitionInfo() {
struct statvfs vfs;
if (!platform_->StatVFS(FilePath(kRecoverSource), &vfs))
return false;
base::Value dv(base::Value::Type::DICTIONARY);
dv.SetStringKey("filesystem", FilePath(kRecoverSource).value());
dv.SetIntKey("blocks-total", vfs.f_blocks);
dv.SetIntKey("blocks-free", vfs.f_bfree);
dv.SetIntKey("blocks-avail", vfs.f_bavail);
dv.SetIntKey("inodes-total", vfs.f_files);
dv.SetIntKey("inodes-free", vfs.f_ffree);
dv.SetIntKey("inodes-avail", vfs.f_favail);
std::string output;
base::JSONWriter::WriteWithOptions(dv, base::JSONWriter::OPTIONS_PRETTY_PRINT,
&output);
if (!platform_->WriteStringToFile(FilePath(kRecoverBlockUsage), output))
return false;
if (!platform_->ReportFilesystemDetails(FilePath(kRecoverSource),
FilePath(kRecoverFilesystemDetails)))
return false;
return true;
}
bool StatefulRecovery::CopyUserContents() {
int rc;
FilePath path;
if (!mountfn_.Run(user_, passkey_, &path)) {
// mountfn_ logged the error already.
return false;
}
rc = platform_->Copy(path, FilePath(kRecoverDestination));
unmountfn_.Run();
// If it failed, unmountfn_ would log the error.
if (rc)
return true;
LOG(ERROR) << "Failed to copy " << path.value();
return false;
}
bool StatefulRecovery::CopyPartitionContents() {
int rc;
rc = platform_->Copy(FilePath(kRecoverSource), FilePath(kRecoverDestination));
if (rc)
return true;
LOG(ERROR) << "Failed to copy " << FilePath(kRecoverSource).value();
return false;
}
bool StatefulRecovery::RecoverV1() {
// Version 1 requires write protect be disabled.
if (platform_->FirmwareWriteProtected()) {
LOG(ERROR) << "Refusing v1 recovery request: firmware is write protected.";
return false;
}
if (!CopyPartitionContents())
return false;
if (!CopyPartitionInfo())
return false;
return true;
}
bool StatefulRecovery::RecoverV2() {
bool wrote_data = false;
bool is_authenticated_owner = false;
// If possible, copy user contents.
if (CopyUserContents()) {
wrote_data = true;
// If user authenticated, check if they are the owner.
if (isownerfn_.Run(user_)) {
is_authenticated_owner = true;
}
}
// Version 2 requires either write protect disabled or system owner.
if (!platform_->FirmwareWriteProtected() || is_authenticated_owner) {
if (!CopyPartitionContents() || !CopyPartitionInfo()) {
// Even if we wrote out user data, claim failure here if the
// encrypted-stateful partition couldn't be extracted.
return false;
}
wrote_data = true;
}
return wrote_data;
}
bool StatefulRecovery::Recover() {
if (!requested_)
return false;
// Start with a clean slate. Note that there is a window of opportunity for
// another process to create the directory with funky permissions after the
// delete takes place but before we manage to recreate. Since the parent
// directory is root-owned though, this isn't a problem in practice.
const FilePath kDestinationPath(kRecoverDestination);
if (!platform_->DeletePathRecursively(kDestinationPath) ||
!platform_->CreateDirectory(kDestinationPath)) {
PLOG(ERROR) << "Failed to create fresh " << kDestinationPath.value();
return false;
}
if (version_ == "2") {
return RecoverV2();
} else if (version_ == "1") {
return RecoverV1();
} else {
LOG(ERROR) << "Unknown recovery version: " << version_;
return false;
}
}
void StatefulRecovery::PerformReboot() {
// TODO(wad) Replace with a mockable helper.
if (system("/usr/bin/crossystem recovery_request=1") != 0) {
LOG(ERROR) << "Failed to set recovery request!";
}
platform_->Sync();
reboot(LINUX_REBOOT_CMD_RESTART);
}
bool StatefulRecovery::ParseFlagFile() {
std::string contents;
size_t delim, pos;
if (!platform_->ReadFileToString(FilePath(kFlagFile), &contents))
return false;
// Make sure there is a trailing newline.
contents += "\n";
do {
pos = 0;
delim = contents.find("\n", pos);
if (delim == std::string::npos)
break;
version_ = contents.substr(pos, delim);
if (version_ == "1")
return true;
if (version_ != "2")
break;
pos = delim + 1;
delim = contents.find("\n", pos);
if (delim == std::string::npos)
break;
user_ = contents.substr(pos, delim - pos);
pos = delim + 1;
delim = contents.find("\n", pos);
if (delim == std::string::npos)
break;
passkey_ = contents.substr(pos, delim - pos);
return true;
} while (0);
// TODO(ellyjones): UMA stat?
LOG(ERROR) << "Bogus stateful recovery request file:" << contents;
return false;
}
} // namespace cryptohome