blob: a56d2f49611b31030df2848996a2909858c75fee [file] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cryptohome/fuzzers/fuzzed_platform.h"
#include <linux/fs.h>
#include <memory.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <algorithm>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/check_op.h>
#include <base/containers/contains.h>
#include <base/files/file.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/sequence_checker.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <base/unguessable_token.h>
#include <brillo/process/process.h>
#include <brillo/process/process_mock.h>
#include <brillo/secure_blob.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <gmock/gmock.h>
#include "cryptohome/dircrypto_util.h"
#include "cryptohome/fuzzers/blob_mutator.h"
#include "cryptohome/fuzzers/fuzzed_proto_generator.h"
#include "cryptohome/platform.h"
namespace cryptohome {
using ::brillo::Blob;
using ::brillo::BlobFromString;
using ::brillo::BlobToString;
using ::brillo::SecureBlob;
namespace {
// Default file attributes used for virtual FS. They mimic typical conditions
// (for code running under root).
constexpr int kDefaultUserId = 0;
constexpr int kDefaultGroupId = 0;
constexpr mode_t kDefaultDirPermissions = 0755;
constexpr mode_t kDefaultFilePermissions = 0644;
// Semi-arbitrary parameters for blob mutation (e.g., applied to virtual FS
// contents):
// * How many times the mutated blob can be bigger than the original one:
constexpr size_t kBlobMutationSizeFactor = 2;
// * Upper boundary for the mutated blob size (used only if the original blob is
// smaller):
constexpr size_t kBlobMutationDefaultMaxSize = 100;
// Used for interpreting the input supplied by the fuzzing framework as an
// instruction for how to build a fuzzed result in a piece of logic.
enum class FuzzResultStrategy {
// The implementation should simulate the behavior as close to the "real"
// platform as possible. E.g., file read operation should return contents from
// the virtual FS.
// NOTE: It's important this enum item comes first, because this speeds up the
// fuzzer in discovering the "typical" scenario for code-under-test (this enum
// is easy to pick by adding zeroes into the input or simply by making it too
// short).
kSimulate,
// The implementation should return success (and fill output parameters, if
// there are any, with "random" data).
kPretendSuccess,
// The implementation should return failure.
kPretendFailure,
// Must be the last item.
kMaxValue = kPretendFailure
};
void AssertIsValidAbsolutePath(const base::FilePath& path) {
CHECK(path.IsAbsolute()) << "path=" << path;
// We make an assumption that code that uses `Platform` shouldn't pass paths
// with ".." as it might indicate a security issue.
CHECK(!path.ReferencesParent()) << "path=" << path;
}
// Removes extraneous characters from the path. E.g., "./b" is transformed into
// "b", "a//b/" into "a/b". The input path must not contain "..".
base::FilePath CanonicalizePath(const base::FilePath& path) {
const std::vector<std::string> components = path.GetComponents();
if (components.empty()) {
return base::FilePath();
}
base::FilePath joined;
for (const auto& component : components) {
CHECK_NE(component, base::FilePath::kParentDirectory);
if (component == base::FilePath::kCurrentDirectory) {
continue;
}
if (joined.empty()) {
joined = base::FilePath(component);
} else {
joined = joined.Append(component);
}
}
return joined;
}
bool IsPathDescendant(const base::FilePath& candidate,
const base::FilePath& ancestor,
bool recursive) {
if (!ancestor.IsParent(candidate)) {
return false;
}
if (!recursive && candidate.DirName() != ancestor) {
return false;
}
return true;
}
base::FilePath GenerateArbitraryDescendant(
const base::FilePath& ancestor,
bool recursive,
FuzzedDataProvider& fuzzed_data_provider) {
static constexpr char kFallbackPath[] = "foo";
base::FilePath to_append(fuzzed_data_provider.ConsumeRandomLengthString());
if (to_append.value().empty() || to_append.IsAbsolute() ||
to_append.ReferencesParent() ||
to_append != CanonicalizePath(to_append) ||
(!recursive && to_append.GetComponents().size() > 1)) {
// If the random value doesn't satisfy conditions, use a stub value.
to_append = base::FilePath(kFallbackPath);
}
return ancestor.Append(to_append);
}
base::stat_wrapper_t GetStat(const FuzzedPlatform::VirtualFsEntry& entry) {
constexpr dev_t kStubDev = 123;
constexpr ino64_t kStubIno = 456;
constexpr dev_t kStubRdev = 789;
constexpr time_t kStubAtime = 333;
constexpr time_t kStubMtime = 222;
constexpr time_t kStubCtime = 111;
constexpr blksize_t kStubBlksize = 512;
constexpr off64_t kStubSizeOfBlock = 512;
base::stat_wrapper_t stat = {};
stat.st_dev = kStubDev;
stat.st_ino = kStubIno;
stat.st_mode = (entry.is_dir ? S_IFDIR : S_IFREG) | entry.permissions;
stat.st_nlink = 0;
stat.st_uid = entry.user_id;
stat.st_gid = entry.group_id;
stat.st_rdev = kStubRdev;
stat.st_size = entry.file_contents.size();
stat.st_atime = kStubAtime;
stat.st_mtime = kStubMtime;
stat.st_ctime = kStubCtime;
stat.st_blksize = kStubBlksize;
stat.st_blocks =
(entry.file_contents.size() + kStubSizeOfBlock - 1) / kStubSizeOfBlock;
return stat;
}
base::stat_wrapper_t GenerateArbitraryStat(
FuzzedDataProvider& fuzzed_data_provider) {
base::stat_wrapper_t stat = {};
stat.st_dev = fuzzed_data_provider.ConsumeIntegral<dev_t>();
stat.st_ino = fuzzed_data_provider.ConsumeIntegral<ino64_t>();
stat.st_mode = fuzzed_data_provider.ConsumeIntegral<mode_t>();
stat.st_nlink = fuzzed_data_provider.ConsumeIntegral<nlink_t>();
stat.st_uid = fuzzed_data_provider.ConsumeIntegral<uid_t>();
stat.st_gid = fuzzed_data_provider.ConsumeIntegral<gid_t>();
stat.st_rdev = fuzzed_data_provider.ConsumeIntegral<dev_t>();
stat.st_size = fuzzed_data_provider.ConsumeIntegral<off64_t>();
stat.st_atime = fuzzed_data_provider.ConsumeIntegral<time_t>();
stat.st_mtime = fuzzed_data_provider.ConsumeIntegral<time_t>();
stat.st_ctime = fuzzed_data_provider.ConsumeIntegral<time_t>();
stat.st_blksize = fuzzed_data_provider.ConsumeIntegral<blksize_t>();
stat.st_blocks = fuzzed_data_provider.ConsumeIntegral<blkcnt64_t>();
return stat;
}
// Fuzzed analog of `base::FileEnumerator`. Returns virtual FS contents but,
// with some chance, skips some entries or injects arbitrary ones.
class FuzzedFileEnumerator : public FileEnumerator {
public:
FuzzedFileEnumerator(const base::FilePath& path,
bool recursive,
int file_type,
bool fill_info,
const FuzzedPlatform::VirtualFs& virtual_fs,
FuzzedDataProvider& fuzzed_data_provider)
: path_(path),
recursive_(recursive),
file_type_(file_type),
fill_info_(fill_info),
virtual_fs_(virtual_fs),
fuzzed_data_provider_(fuzzed_data_provider),
virtual_fs_iter_(virtual_fs_.begin()) {}
base::FilePath Next() override {
do {
NextImpl();
} while (current_path_.empty() && virtual_fs_iter_ != virtual_fs_.end());
return current_path_;
}
FileInfo GetInfo() override {
DCHECK(fill_info_);
return current_file_info_;
}
private:
void NextImpl() {
current_path_ = base::FilePath();
current_file_info_ = FileInfo();
while (virtual_fs_iter_ != virtual_fs_.end() &&
!IsMatching(virtual_fs_iter_->first, virtual_fs_iter_->second)) {
++virtual_fs_iter_;
}
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
if (virtual_fs_iter_ == virtual_fs_.end()) {
return;
}
current_path_ = virtual_fs_iter_->first;
if (fill_info_) {
current_file_info_ =
FileInfo(current_path_, GetStat(virtual_fs_iter_->second));
}
++virtual_fs_iter_;
return;
}
case FuzzResultStrategy::kPretendSuccess: {
current_path_ = GenerateArbitraryDescendant(path_, recursive_,
fuzzed_data_provider_);
if (fill_info_) {
current_file_info_ = FileInfo(
current_path_, GenerateArbitraryStat(fuzzed_data_provider_));
}
return;
}
case FuzzResultStrategy::kPretendFailure: {
// Pretend the next file in the virtual FS disappeared.
if (virtual_fs_iter_ != virtual_fs_.end()) {
++virtual_fs_iter_;
}
return;
}
}
}
bool IsMatching(const base::FilePath& candidate_path,
const FuzzedPlatform::VirtualFsEntry& candidate) const {
if (!IsPathDescendant(candidate_path, path_, recursive_)) {
return false;
}
if (candidate.is_dir && !(file_type_ & base::FileEnumerator::DIRECTORIES)) {
return false;
}
if (!candidate.is_dir && !(file_type_ & base::FileEnumerator::FILES)) {
return false;
}
return true;
}
const base::FilePath path_;
const bool recursive_;
const int file_type_;
const bool fill_info_;
const FuzzedPlatform::VirtualFs& virtual_fs_;
FuzzedDataProvider& fuzzed_data_provider_;
FuzzedPlatform::VirtualFs::const_iterator virtual_fs_iter_;
base::FilePath current_path_;
FileInfo current_file_info_;
};
Blob GenerateArbitraryFileContents(const Blob& original_contents,
FuzzedDataProvider& fuzzed_data_provider) {
enum class Strategy {
kCreateRandom,
kCreateRandomProto,
kMutateOriginal,
// Must be the last item.
kMaxValue = kMutateOriginal
};
switch (fuzzed_data_provider.ConsumeEnum<Strategy>()) {
case Strategy::kCreateRandom: {
return BlobFromString(fuzzed_data_provider.ConsumeRandomLengthString());
}
case Strategy::kCreateRandomProto: {
FuzzedProtoGenerator generator(fuzzed_data_provider);
return generator.Generate();
}
case Strategy::kMutateOriginal: {
const size_t max_length =
std::max(original_contents.size() * kBlobMutationSizeFactor,
kBlobMutationDefaultMaxSize);
return MutateBlob(original_contents, /*min_length=*/0, max_length,
&fuzzed_data_provider);
}
}
}
} // namespace
FuzzedPlatform::FuzzedPlatform(FuzzedDataProvider& fuzzed_data_provider)
: fuzzed_data_provider_(fuzzed_data_provider) {
// As a baseline, the root folder is always there.
virtual_fs_[base::FilePath("/")] = VirtualFsEntry{
.is_dir = true,
.permissions = kDefaultDirPermissions,
.user_id = kDefaultUserId,
.group_id = kDefaultGroupId,
};
}
FuzzedPlatform::~FuzzedPlatform() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool FuzzedPlatform::Mount(const base::FilePath& from,
const base::FilePath& to,
const std::string& /*type*/,
uint32_t /*mount_flags*/,
const std::string& /*mount_options*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(from);
AssertIsValidAbsolutePath(to);
// Mounts are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::Bind(const base::FilePath& from,
const base::FilePath& to,
RemountOption /*remount*/,
bool /*nosymfollow*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(from);
AssertIsValidAbsolutePath(to);
// Mounts are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::Unmount(const base::FilePath& path,
bool /*lazy*/,
bool* was_busy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Mounts are not simulated, hence return random result.
if (GetArbitraryOutcome()) {
return true;
}
if (was_busy) {
*was_busy = fuzzed_data_provider_.ConsumeBool();
}
return false;
}
void FuzzedPlatform::LazyUnmount(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Mounts are not simulated, so nothing to do here.
}
ExpireMountResult FuzzedPlatform::ExpireMount(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Mounts are not simulated, so return random result.
return fuzzed_data_provider_.ConsumeEnum<ExpireMountResult>();
}
bool FuzzedPlatform::GetLoopDeviceMounts(
std::multimap<const base::FilePath, const base::FilePath>* mounts) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(mounts);
return GetLoopDeviceMountsImpl(/*key_prefix=*/base::FilePath("/"), mounts);
}
bool FuzzedPlatform::GetMountsBySourcePrefix(
const base::FilePath& from_prefix,
std::multimap<const base::FilePath, const base::FilePath>* mounts) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(from_prefix);
CHECK(mounts);
return GetLoopDeviceMountsImpl(
/*key_prefix=*/from_prefix.AsEndingWithSeparator(), mounts);
}
bool FuzzedPlatform::GetMountsByDevicePrefix(
const std::string& from_prefix,
std::multimap<const base::FilePath, const base::FilePath>* mounts) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(mounts);
return GetLoopDeviceMountsImpl(base::FilePath(from_prefix), mounts);
}
bool FuzzedPlatform::IsDirectoryMounted(const base::FilePath& directory) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(directory);
// Mounts are not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeBool();
}
std::optional<std::vector<bool>> FuzzedPlatform::AreDirectoriesMounted(
const std::vector<base::FilePath>& directories) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& path : directories) {
CHECK(path.IsAbsolute()) << "path=" << path;
CHECK(!path.ReferencesParent()) << "path=" << path;
}
// Mounts are not simulated, hence return random result.
if (!GetArbitraryOutcome()) {
return std::nullopt;
}
std::vector<bool> result(directories.size());
for (size_t i = 0; i < result.size(); ++i) {
result[i] = fuzzed_data_provider_.ConsumeBool();
}
return result;
}
std::unique_ptr<brillo::Process> FuzzedPlatform::CreateProcessInstance() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::make_unique<testing::NiceMock<brillo::ProcessMock>>();
}
bool FuzzedPlatform::GetOwnership(const base::FilePath& path,
uid_t* user_id,
gid_t* group_id,
bool /*follow_links*/) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(user_id);
CHECK(group_id);
return GetFileInfo(path, /*out_permissions=*/nullptr, user_id, group_id);
}
bool FuzzedPlatform::SetOwnership(const base::FilePath& path,
uid_t user_id,
gid_t group_id,
bool /*follow_links*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return SetFileInfo(path,
/*expect_is_dir=*/false, /*new_mode=*/std::nullopt,
user_id, group_id);
}
bool FuzzedPlatform::GetPermissions(const base::FilePath& path,
mode_t* mode) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(mode);
return GetFileInfo(path, mode, /*user_id=*/nullptr,
/*group_id=*/nullptr);
}
bool FuzzedPlatform::SetPermissions(const base::FilePath& path, mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return SetFileInfo(path,
/*expect_is_dir=*/false, mode,
/*new_user_id=*/std::nullopt,
/*new_group_id=*/std::nullopt);
}
int64_t FuzzedPlatform::AmountOfFreeDiskSpace(
const base::FilePath& path) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Disk space is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeIntegralInRange<int64_t>(
0, std::numeric_limits<int64_t>::max());
}
int64_t FuzzedPlatform::GetQuotaCurrentSpaceForUid(const base::FilePath& device,
uid_t /*user_id*/) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device);
// Quota is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeIntegralInRange<int64_t>(
0, std::numeric_limits<int64_t>::max());
}
int64_t FuzzedPlatform::GetQuotaCurrentSpaceForGid(const base::FilePath& device,
gid_t /*group_id*/) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device);
// Quota is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeIntegralInRange<int64_t>(
0, std::numeric_limits<int64_t>::max());
}
int64_t FuzzedPlatform::GetQuotaCurrentSpaceForProjectId(
const base::FilePath& device, int /*project_id*/) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device);
// Quota is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeIntegralInRange<int64_t>(
0, std::numeric_limits<int64_t>::max());
}
bool FuzzedPlatform::GetQuotaProjectId(const base::FilePath& path,
int* project_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(project_id);
// Quota is not simulated, hence return random result.
if (!GetArbitraryOutcome()) {
return false;
}
*project_id = fuzzed_data_provider_.ConsumeIntegralInRange<int>(
0, std::numeric_limits<int32_t>::max());
return true;
}
bool FuzzedPlatform::SetQuotaProjectId(const base::FilePath& path,
int /*project_id*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Quota is not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::SetQuotaProjectIdWithFd(int /*project_id*/,
int /*fd*/,
int* out_error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(out_error);
// Quota is not simulated, hence return random result.
int code = fuzzed_data_provider_.ConsumeIntegralInRange<int>(
0, std::numeric_limits<int>::max());
if (code != 0) {
*out_error = code;
return false;
}
return true;
}
bool FuzzedPlatform::SetQuotaProjectInheritanceFlagWithFd(bool /*enable*/,
int /*fd*/,
int* out_error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(out_error);
// Quota is not simulated, hence return random result.
int code = fuzzed_data_provider_.ConsumeIntegralInRange<int>(
0, std::numeric_limits<int>::max());
if (code != 0) {
*out_error = code;
return false;
}
return true;
}
bool FuzzedPlatform::FileExists(const base::FilePath& path) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate:
return virtual_fs_.count(CanonicalizePath(path)) > 0;
case FuzzResultStrategy::kPretendSuccess:
return true;
case FuzzResultStrategy::kPretendFailure:
return false;
}
}
int FuzzedPlatform::Access(const base::FilePath& path, uint32_t flag) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return -1;
}
mode_t permissions = iter->second.permissions;
bool failed_read = (flag & R_OK) && !(permissions & S_IRUSR);
bool failed_write = (flag & W_OK) && !(permissions & S_IWUSR);
bool failed_exec = (flag & X_OK) && !(permissions & S_IXUSR);
if (failed_read || failed_write || failed_exec) {
return -1;
}
return 0;
}
case FuzzResultStrategy::kPretendSuccess: {
return 0;
}
case FuzzResultStrategy::kPretendFailure: {
return -1;
}
}
}
bool FuzzedPlatform::DirectoryExists(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
return iter != virtual_fs_.end() && iter->second.is_dir;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::GetFileSize(const base::FilePath& path, int64_t* size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(size);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
*size = iter->second.file_contents.size();
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
*size = fuzzed_data_provider_.ConsumeIntegralInRange<int64_t>(
0, std::numeric_limits<int64_t>::max());
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
int64_t FuzzedPlatform::ComputeDirectoryDiskUsage(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Disk space is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeIntegralInRange<int64_t>(
0, std::numeric_limits<int64_t>::max());
}
FILE* FuzzedPlatform::OpenFile(const base::FilePath& path, const char* mode) {
// We don't simulate reading/writing of data; currently, all callsites of
// `OpenFile()` use it only for creating empty files. Zero couldn't be chosen
// as `fmemopen()` rejects it.
constexpr int kMemopenSize = 1;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(mode);
if (std::string(mode) != "wx") {
// We don't simulate all possible modes since it's a lot of work with little
// benefit until we have callsites that use other modes.
LOG(FATAL) << "Fuzzed OpenFile is not simulated for mode=" << mode;
}
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_path = CanonicalizePath(path);
const auto dir_iter = virtual_fs_.find(canonical_path.DirName());
if (dir_iter == virtual_fs_.end() || !dir_iter->second.is_dir) {
return nullptr;
}
if (virtual_fs_.count(canonical_path)) {
return nullptr;
}
virtual_fs_[canonical_path] = VirtualFsEntry{
.is_dir = false,
.permissions = kDefaultFilePermissions,
.user_id = kDefaultUserId,
.group_id = kDefaultGroupId,
};
return fmemopen(/*buf=*/nullptr, kMemopenSize, mode);
}
case FuzzResultStrategy::kPretendSuccess: {
return fmemopen(/*buf=*/nullptr, kMemopenSize, mode);
}
case FuzzResultStrategy::kPretendFailure: {
return nullptr;
}
}
}
bool FuzzedPlatform::CloseFile(FILE* file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(file);
// Close the file regardless of the strategy chosen below, to avoid leaks.
const bool real_result = base::CloseFile(file);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate:
return real_result;
case FuzzResultStrategy::kPretendSuccess:
return true;
case FuzzResultStrategy::kPretendFailure:
return false;
}
}
void FuzzedPlatform::InitializeFile(base::File* file,
const base::FilePath& path,
uint32_t flags) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LOG(FATAL) << "Not simulated yet";
}
bool FuzzedPlatform::LockFile(int /*fd*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// File locking is not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::ReadFile(const base::FilePath& path, Blob* blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(blob);
return ReadFileImpl(path, blob);
}
bool FuzzedPlatform::ReadFileToString(const base::FilePath& path,
std::string* str) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(str);
Blob blob;
if (!ReadFileImpl(path, &blob)) {
return false;
}
*str = BlobToString(blob);
return true;
}
bool FuzzedPlatform::ReadFileToSecureBlob(const base::FilePath& path,
SecureBlob* sblob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(sblob);
Blob blob;
if (!ReadFileImpl(path, &blob)) {
return false;
}
sblob->assign(blob.begin(), blob.end());
return true;
}
bool FuzzedPlatform::WriteFile(const base::FilePath& path, const Blob& blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, blob, kDefaultFilePermissions);
}
bool FuzzedPlatform::WriteSecureBlobToFile(const base::FilePath& path,
const SecureBlob& sblob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, Blob(sblob.begin(), sblob.end()),
kDefaultFilePermissions);
}
bool FuzzedPlatform::WriteStringToFile(const base::FilePath& path,
const std::string& str) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, BlobFromString(str), kDefaultFilePermissions);
}
bool FuzzedPlatform::WriteArrayToFile(const base::FilePath& path,
const char* data,
size_t size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(data || !size);
return WriteFileImpl(path, size ? Blob(data, data + size) : Blob(),
kDefaultFilePermissions);
}
bool FuzzedPlatform::WriteFileAtomic(const base::FilePath& path,
const Blob& blob,
mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, blob, mode);
}
bool FuzzedPlatform::WriteSecureBlobToFileAtomic(const base::FilePath& path,
const SecureBlob& sblob,
mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, Blob(sblob.begin(), sblob.end()), mode);
}
bool FuzzedPlatform::WriteStringToFileAtomic(const base::FilePath& path,
const std::string& data,
mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, BlobFromString(data), mode);
}
bool FuzzedPlatform::WriteFileAtomicDurable(const base::FilePath& path,
const Blob& blob,
mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, blob, mode);
}
bool FuzzedPlatform::WriteSecureBlobToFileAtomicDurable(
const base::FilePath& path, const SecureBlob& sblob, mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, Blob(sblob.begin(), sblob.end()), mode);
}
bool FuzzedPlatform::WriteStringToFileAtomicDurable(const base::FilePath& path,
const std::string& str,
mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return WriteFileImpl(path, BlobFromString(str), mode);
}
bool FuzzedPlatform::TouchFileDurable(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
return iter != virtual_fs_.end() && !iter->second.is_dir;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::DeleteFile(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return DeleteImpl(path, /*recursive=*/false);
}
bool FuzzedPlatform::DeletePathRecursively(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return DeleteImpl(path, /*recursive=*/true);
}
bool FuzzedPlatform::DeleteFileDurable(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return DeleteImpl(path, /*recursive=*/false);
}
bool FuzzedPlatform::CreateDirectory(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return CreateDirectoryImpl(path, kDefaultDirPermissions, kDefaultUserId,
kDefaultGroupId);
}
bool FuzzedPlatform::EnumerateDirectoryEntries(
const base::FilePath& path,
bool recursive,
std::vector<base::FilePath>* ent_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(ent_list);
FuzzedFileEnumerator en(
path, recursive,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES,
/*fill_info=*/false, virtual_fs_, fuzzed_data_provider_);
for (base::FilePath path = en.Next(); !path.empty(); path = en.Next()) {
ent_list->push_back(path);
}
// The real implementation always returns `true`, so do we.
return true;
}
bool FuzzedPlatform::IsDirectoryEmpty(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_path = CanonicalizePath(path);
for (const auto& [item_path, item] : virtual_fs_) {
if (canonical_path.IsParent(item_path)) {
return false;
}
}
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
FileEnumerator* FuzzedPlatform::GetFileEnumerator(const base::FilePath& path,
bool recursive,
int file_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
return new FuzzedFileEnumerator(CanonicalizePath(path), recursive, file_type,
/*fill_info=*/true, virtual_fs_,
fuzzed_data_provider_);
}
bool FuzzedPlatform::Stat(const base::FilePath& path,
base::stat_wrapper_t* buf) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(buf);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
*buf = GetStat(iter->second);
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
*buf = GenerateArbitraryStat(fuzzed_data_provider_);
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::HasExtendedFileAttribute(const base::FilePath& path,
const std::string& name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
return iter->second.extended_attrs.count(name) > 0;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::ListExtendedFileAttributes(
const base::FilePath& path, std::vector<std::string>* attr_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(attr_list);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
for (const auto& [name, value] : iter->second.extended_attrs) {
attr_list->push_back(name);
}
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
for (;;) {
std::string attr = fuzzed_data_provider_.ConsumeRandomLengthString();
if (attr.empty()) {
break;
}
attr_list->push_back(attr);
}
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::GetExtendedFileAttributeAsString(
const base::FilePath& path, const std::string& name, std::string* value) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(value);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
const auto& attrs = iter->second.extended_attrs;
const auto attr_iter = attrs.find(name);
if (attr_iter == attrs.end()) {
return false;
}
*value = attr_iter->second;
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
*value = fuzzed_data_provider_.ConsumeRandomLengthString();
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::GetExtendedFileAttribute(const base::FilePath& path,
const std::string& name,
char* value,
ssize_t size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(value);
std::string string_value;
if (!GetExtendedFileAttributeAsString(path, name, &string_value)) {
return false;
}
if (string_value.size() != size) {
return false;
}
memcpy(value, string_value.c_str(), string_value.size());
return true;
}
bool FuzzedPlatform::SetExtendedFileAttribute(const base::FilePath& path,
const std::string& name,
const char* value,
size_t size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(value || !size);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
iter->second.extended_attrs[name] =
size ? std::string(value, value + size) : std::string();
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::RemoveExtendedFileAttribute(const base::FilePath& path,
const std::string& name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
auto& attrs = iter->second.extended_attrs;
const auto attr_iter = attrs.find(name);
if (attr_iter == attrs.end()) {
return false;
}
attrs.erase(attr_iter);
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::GetExtFileAttributes(const base::FilePath& path,
int* flags) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(flags);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
*flags = iter->second.ext_flags;
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
*flags = fuzzed_data_provider_.ConsumeIntegralInRange<int>(
0, std::numeric_limits<int>::max());
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::SetExtFileAttributes(const base::FilePath& path,
int flags) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
iter->second.ext_flags = flags;
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::HasNoDumpFileAttribute(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
return (iter->second.ext_flags & FS_NODUMP_FL) != 0;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::Rename(const base::FilePath& from,
const base::FilePath& to) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(from);
AssertIsValidAbsolutePath(to);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_from = CanonicalizePath(from);
if (!virtual_fs_.count(canonical_from)) {
return false;
}
std::vector<std::pair<base::FilePath, VirtualFsEntry>> new_entries;
for (auto iter = virtual_fs_.begin(); iter != virtual_fs_.end();) {
const base::FilePath& item_path = iter->first;
if (item_path == canonical_from || canonical_from.IsParent(item_path)) {
base::FilePath new_path = CanonicalizePath(to);
canonical_from.AppendRelativePath(item_path, &new_path);
new_entries.emplace_back(std::move(new_path),
std::move(iter->second));
iter = virtual_fs_.erase(iter);
} else {
++iter;
}
}
virtual_fs_.insert(new_entries.begin(), new_entries.end());
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
base::Time FuzzedPlatform::GetCurrentTime() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The clocks should be mutated by the fuzzer using libchrome's mock time:
// that way global singletons and threading primitives will know about it too.
return base::Time::Now();
}
bool FuzzedPlatform::Copy(const base::FilePath& from,
const base::FilePath& to) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(from);
AssertIsValidAbsolutePath(to);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_from = CanonicalizePath(from);
const auto iter_from = virtual_fs_.find(canonical_from);
if (iter_from == virtual_fs_.end() || !iter_from->second.is_dir) {
return false;
}
std::vector<std::pair<base::FilePath, VirtualFsEntry>> new_entries;
for (const auto& [entry_path, entry] : virtual_fs_) {
if (entry_path != canonical_from &&
!canonical_from.IsParent(entry_path)) {
continue;
}
base::FilePath new_path = CanonicalizePath(to);
canonical_from.AppendRelativePath(entry_path, &new_path);
VirtualFsEntry new_entry = entry;
new_entry.user_id = kDefaultUserId;
new_entry.group_id = kDefaultGroupId;
new_entry.permissions =
new_entry.is_dir ? kDefaultDirPermissions : kDefaultFilePermissions;
new_entries.emplace_back(std::move(new_path), std::move(new_entry));
}
virtual_fs_.insert(new_entries.begin(), new_entries.end());
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::StatVFS(const base::FilePath& path, struct statvfs* vfs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(vfs);
// FS stats are not simulated, hence return random result.
if (!GetArbitraryOutcome()) {
return false;
}
memset(vfs, 0, sizeof(struct statvfs));
vfs->f_bsize = fuzzed_data_provider_.ConsumeIntegralInRange<
unsigned long>( // NOLINT(runtime/int)
1, std::numeric_limits<unsigned long>::max()); // NOLINT(runtime/int)
vfs->f_frsize = fuzzed_data_provider_.ConsumeIntegralInRange<
unsigned long>( // NOLINT(runtime/int)
1, std::numeric_limits<unsigned long>::max()); // NOLINT(runtime/int)
vfs->f_blocks = fuzzed_data_provider_.ConsumeIntegralInRange<fsblkcnt_t>(
1, std::numeric_limits<fsblkcnt_t>::max());
vfs->f_bfree = fuzzed_data_provider_.ConsumeIntegralInRange<fsblkcnt_t>(
0, std::numeric_limits<fsblkcnt_t>::max());
vfs->f_bavail = fuzzed_data_provider_.ConsumeIntegralInRange<fsblkcnt_t>(
0, std::numeric_limits<fsblkcnt_t>::max());
vfs->f_files = fuzzed_data_provider_.ConsumeIntegralInRange<fsfilcnt_t>(
1, std::numeric_limits<fsfilcnt_t>::max());
vfs->f_ffree = fuzzed_data_provider_.ConsumeIntegralInRange<fsfilcnt_t>(
0, std::numeric_limits<fsfilcnt_t>::max());
vfs->f_favail = fuzzed_data_provider_.ConsumeIntegralInRange<fsfilcnt_t>(
0, std::numeric_limits<fsfilcnt_t>::max());
vfs->f_fsid = fuzzed_data_provider_
.ConsumeIntegral<unsigned long>(); // NOLINT(runtime/int)
vfs->f_flag = fuzzed_data_provider_
.ConsumeIntegral<unsigned long>(); // NOLINT(runtime/int)
// Don't put extremely small or big values into `f_namemax`, in case some code
// allocates buffers of this size.
vfs->f_namemax =
fuzzed_data_provider_
.ConsumeIntegralInRange<unsigned long>( // NOLINT(runtime/int)
10, 10000);
return true;
}
bool FuzzedPlatform::SameVFS(const base::FilePath& mnt_a,
const base::FilePath& mnt_b) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(mnt_a);
AssertIsValidAbsolutePath(mnt_b);
// VFS is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeBool();
}
bool FuzzedPlatform::FindFilesystemDevice(const base::FilePath& filesystem,
std::string* device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(filesystem);
CHECK(device);
// FS devices are not simulated, hence return random result.
base::FilePath path(fuzzed_data_provider_.ConsumeRandomLengthString());
if (!path.IsAbsolute() || path.ReferencesParent() ||
path != CanonicalizePath(path)) {
// If the generation gave invalid value, don't return it.
return false;
}
*device = path.value();
return true;
}
bool FuzzedPlatform::ReportFilesystemDetails(const base::FilePath& filesystem,
const base::FilePath& logfile) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(filesystem);
AssertIsValidAbsolutePath(logfile);
// FS reporting is not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::SetupProcessKeyring() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Keyrings are not simulated, hence return random result.
return GetArbitraryOutcome();
}
int FuzzedPlatform::GetDirectoryPolicyVersion(const base::FilePath& dir) const {
// This is an arbitrarily chosen boundary for the fuzzer.
constexpr int kMaxValue = 1000;
// This is the minimum value that also denotes an error.
constexpr int kMinValue = -1;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(dir);
// Keyrings are not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeIntegralInRange<int>(kMinValue,
kMaxValue);
}
bool FuzzedPlatform::CheckFscryptKeyIoctlSupport() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Fscrypt is not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeBool();
}
dircrypto::KeyState FuzzedPlatform::GetDirCryptoKeyState(
const base::FilePath& dir) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(dir);
// Keyrings are not simulated, hence return random result.
return fuzzed_data_provider_.ConsumeEnum<dircrypto::KeyState>();
}
bool FuzzedPlatform::SetDirCryptoKey(
const base::FilePath& dir,
const dircrypto::KeyReference& /*key_reference*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(dir);
// Keyrings are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::InvalidateDirCryptoKey(
const dircrypto::KeyReference& /*key_reference*/,
const base::FilePath& shadow_root) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(shadow_root);
// Keyrings are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::ClearUserKeyring() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Keyrings are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::FirmwareWriteProtected() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Firmware is not simulated, hence return random result.
return !fuzzed_data_provider_.ConsumeBool();
}
bool FuzzedPlatform::SyncFile(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
// Sync isn't simulated, but we should do sanity checks.
const auto iter = virtual_fs_.find(CanonicalizePath(path));
return iter != virtual_fs_.end() && !iter->second.is_dir;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::SyncDirectory(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
// Sync isn't simulated, but we should do sanity checks.
const auto iter = virtual_fs_.find(CanonicalizePath(path));
return iter != virtual_fs_.end() && iter->second.is_dir;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
void FuzzedPlatform::Sync() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Nothing to simulate here.
}
bool FuzzedPlatform::CreateSymbolicLink(const base::FilePath& path,
const base::FilePath& target) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(!target.empty());
CHECK(!target.ReferencesParent());
// Symlinks are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::ReadLink(const base::FilePath& path,
base::FilePath* target) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK(target);
// Symlinks are not simulated, hence return random result.
if (!GetArbitraryOutcome()) {
return false;
}
*target =
GenerateArbitraryDescendant(/*ancestor=*/base::FilePath("/"),
/*recursive=*/true, fuzzed_data_provider_);
return true;
}
bool FuzzedPlatform::SetFileTimes(const base::FilePath& path,
const struct timespec& atime,
const struct timespec& mtime,
bool follow_links) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// File times are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::SendFile(int fd_to,
int fd_from,
off_t /*offset*/,
size_t /*count*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_GE(fd_to, 0);
CHECK_GE(fd_from, 0);
// File descriptors are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::CreateSparseFile(const base::FilePath& path,
int64_t /*size*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Don't allocate `size` bytes here, since the number can be big and as full
// simulation of sparse files is not crucial for the fuzzer.
return WriteFileImpl(path, Blob(), kDefaultFilePermissions);
}
bool FuzzedPlatform::GetBlkSize(const base::FilePath& device, uint64_t* size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device);
CHECK(size);
// FS blocks are not simulated, hence return random result.
*size = fuzzed_data_provider_.ConsumeIntegralInRange<uint64_t>(
0, std::numeric_limits<uint64_t>::max());
return *size > 0;
}
bool FuzzedPlatform::DetachLoop(const base::FilePath& device_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device_path);
// Loop devices are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::DiscardDevice(const base::FilePath& device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device);
// Loop devices are not simulated, hence return random result.
return GetArbitraryOutcome();
}
std::vector<Platform::LoopDevice> FuzzedPlatform::GetAttachedLoopDevices() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Loop devices are not simulated, hence return random result.
std::vector<LoopDevice> loop_devices;
for (;;) {
const base::FilePath backing_file(
fuzzed_data_provider_.ConsumeRandomLengthString());
const base::FilePath device(
fuzzed_data_provider_.ConsumeRandomLengthString());
if (!backing_file.IsAbsolute() || backing_file.ReferencesParent() ||
backing_file != CanonicalizePath(backing_file) ||
!device.IsAbsolute() || device.ReferencesParent() ||
device != CanonicalizePath(device)) {
// Treat an invalidly generated pair as a signal to finish generation.
break;
}
loop_devices.push_back(LoopDevice{
.backing_file = backing_file,
.device = device,
});
}
return loop_devices;
}
bool FuzzedPlatform::FormatExt4(const base::FilePath& file,
const std::vector<std::string>& /*opts*/,
uint64_t /*blocks*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(file);
// FS properties are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::Tune2Fs(const base::FilePath& file,
const std::vector<std::string>& /*opts*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(file);
// FS properties are not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::ResizeFilesystem(const base::FilePath& file,
uint64_t /*blocks*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(file);
// FS properties are not simulated, hence return random result.
return GetArbitraryOutcome();
}
std::optional<std::string> FuzzedPlatform::GetSELinuxContextOfFD(int /*fd*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// SELinux is not simulated, hence return random result.
std::string context = fuzzed_data_provider_.ConsumeRandomLengthString();
if (context.empty()) {
return std::nullopt;
}
return context;
}
bool FuzzedPlatform::SetSELinuxContext(const base::FilePath& path,
const std::string& /*context*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// SELinux is not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::RestoreSELinuxContexts(const base::FilePath& path,
bool /*recursive*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// SELinux is not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::SafeDirChmod(const base::FilePath& path, mode_t mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return SetFileInfo(path,
/*expect_is_dir=*/true, mode, /*new_user_id=*/std::nullopt,
/*new_group_id=*/std::nullopt);
}
bool FuzzedPlatform::SafeDirChown(const base::FilePath& path,
uid_t user_id,
gid_t group_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return SetFileInfo(path, /*expect_is_dir=*/true, /*new_mode=*/std::nullopt,
user_id, group_id);
}
bool FuzzedPlatform::SafeCreateDirAndSetOwnershipAndPermissions(
const base::FilePath& path, mode_t mode, uid_t user_id, gid_t gid) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return CreateDirectoryImpl(path, mode, user_id, gid);
}
bool FuzzedPlatform::UdevAdmSettle(const base::FilePath& device_path,
bool /*wait_for_device*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(device_path);
// Adm is not simulated, hence return random result.
return GetArbitraryOutcome();
}
bool FuzzedPlatform::IsStatefulLogicalVolumeSupported() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// LVM is not simulated, hence return random result.
return GetArbitraryOutcome();
}
base::FilePath FuzzedPlatform::GetStatefulDevice() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The devices are not simulated, hence return random result.
base::FilePath path(fuzzed_data_provider_.ConsumeRandomLengthString());
if (!path.IsAbsolute() || path.ReferencesParent() ||
path != CanonicalizePath(path)) {
// If the generation gave invalid value, return an empty value instead.
return base::FilePath();
}
return path;
}
brillo::LoopDeviceManager* FuzzedPlatform::GetLoopDeviceManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return &loop_device_manager_;
}
brillo::LogicalVolumeManager* FuzzedPlatform::GetLogicalVolumeManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(b/254864841): Fuzz mock methods.
return &logical_volume_manager_;
}
bool FuzzedPlatform::GetArbitraryOutcome() const {
// Negation is used in order to let fuzzer discover the "successful" scenario
// of code-under-test more easily (when the input blob contains lots of zeroes
// or is simply too short).
return !fuzzed_data_provider_.ConsumeBool();
}
bool FuzzedPlatform::WriteFileImpl(const base::FilePath& path,
Blob blob,
mode_t permissions) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK_NE(path.StripTrailingSeparators(), path.DirName()) << "path=" << path;
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_path = CanonicalizePath(path);
const auto dir_iter = virtual_fs_.find(canonical_path.DirName());
if (dir_iter == virtual_fs_.end()) {
// Parent directory doesn't exist.
return false;
}
const auto iter = virtual_fs_.find(canonical_path);
if (iter != virtual_fs_.end() && iter->second.is_dir) {
// A directory with the given path exists.
return false;
}
virtual_fs_[canonical_path] = VirtualFsEntry{
.is_dir = false,
.file_contents = std::move(blob),
.permissions = permissions,
.user_id = kDefaultUserId,
.group_id = kDefaultGroupId,
};
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
base::UnguessableToken FuzzedPlatform::CreateUnguessableToken() {
uint64_t high, low;
// Loop until we get suitable values (`UnguessableToken` forbids all-zeroes).
do {
high = random_engine_64_();
low = random_engine_64_();
} while (high == 0 && low == 0);
std::optional<base::UnguessableToken> token =
base::UnguessableToken::Deserialize(high, low);
CHECK(token.has_value());
return *token;
}
bool FuzzedPlatform::ReadFileImpl(const base::FilePath& path, Blob* blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
CHECK_NE(path.StripTrailingSeparators(), path.DirName()) << "path=" << path;
const auto iter = virtual_fs_.find(CanonicalizePath(path));
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
if (iter == virtual_fs_.end() || iter->second.is_dir) {
return false;
}
*blob = iter->second.file_contents;
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
*blob = GenerateArbitraryFileContents(
iter != virtual_fs_.end() ? iter->second.file_contents : Blob(),
fuzzed_data_provider_);
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::CreateDirectoryImpl(const base::FilePath& path,
mode_t permissions,
uid_t user_id,
gid_t group_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Choose among a few ways to generate the result.
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_path = CanonicalizePath(path);
// Check none of ancestors is a regular file.
for (base::FilePath current = canonical_path;
current != base::FilePath("/"); current = current.DirName()) {
const auto iter = virtual_fs_.find(current);
if (iter != virtual_fs_.end() && !iter->second.is_dir) {
return false;
}
}
// Create each ancestor until reaching an existing one.
for (base::FilePath current = canonical_path;
current != base::FilePath("/"); current = current.DirName()) {
if (virtual_fs_.find(current) != virtual_fs_.end()) {
break;
}
virtual_fs_[current] = VirtualFsEntry{
.is_dir = true,
.permissions = permissions,
.user_id = user_id,
.group_id = group_id,
};
}
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::DeleteImpl(const base::FilePath& path, bool recursive) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
// Choose among a few ways to generate the result.
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const base::FilePath canonical_path = CanonicalizePath(path);
if (!recursive) {
// Check there's no subdirectory in the virtual FS.
for (const auto& [entry_path, entry] : virtual_fs_) {
if (entry_path.DirName() == canonical_path && entry.is_dir) {
return false;
}
}
}
for (auto iter = virtual_fs_.begin(); iter != virtual_fs_.end();) {
const auto& [entry_path, entry] = *iter;
if (entry_path == canonical_path ||
IsPathDescendant(entry_path, canonical_path, recursive)) {
iter = virtual_fs_.erase(iter);
} else {
++iter;
}
}
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::SetFileInfo(const base::FilePath& path,
bool expect_is_dir,
std::optional<mode_t> new_permissions,
std::optional<uid_t> new_user_id,
std::optional<gid_t> new_group_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
// Use virtual FS.
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
VirtualFsEntry& entry = iter->second;
if (expect_is_dir && !entry.is_dir) {
return false;
}
if (new_permissions.has_value()) {
entry.permissions = new_permissions.value();
}
if (new_user_id.has_value()) {
entry.user_id = new_user_id.value();
}
if (new_group_id.has_value()) {
entry.group_id = new_group_id.value();
}
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::GetFileInfo(const base::FilePath& path,
mode_t* out_permissions,
uid_t* out_user_id,
gid_t* out_group_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AssertIsValidAbsolutePath(path);
switch (fuzzed_data_provider_.ConsumeEnum<FuzzResultStrategy>()) {
case FuzzResultStrategy::kSimulate: {
const auto iter = virtual_fs_.find(CanonicalizePath(path));
if (iter == virtual_fs_.end()) {
return false;
}
const VirtualFsEntry& entry = iter->second;
if (out_permissions) {
*out_permissions = entry.permissions;
}
if (out_user_id) {
*out_user_id = entry.user_id;
}
if (out_group_id) {
*out_group_id = entry.group_id;
}
return true;
}
case FuzzResultStrategy::kPretendSuccess: {
if (out_permissions) {
*out_permissions =
fuzzed_data_provider_.ConsumeIntegralInRange<mode_t>(0, 0777);
}
if (out_user_id) {
*out_user_id = fuzzed_data_provider_.ConsumeIntegral<uid_t>();
}
if (out_group_id) {
*out_group_id = fuzzed_data_provider_.ConsumeIntegral<gid_t>();
}
return true;
}
case FuzzResultStrategy::kPretendFailure: {
return false;
}
}
}
bool FuzzedPlatform::GetLoopDeviceMountsImpl(
const base::FilePath& key_prefix,
std::multimap<const base::FilePath, const base::FilePath>* mounts) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(mounts);
// Mounting is not simulated, hence return random result.
if (!GetArbitraryOutcome()) {
return false;
}
for (;;) {
const base::FilePath key(key_prefix.value() +
fuzzed_data_provider_.ConsumeRandomLengthString());
const base::FilePath value(
fuzzed_data_provider_.ConsumeRandomLengthString());
if (!key.IsAbsolute() || key.ReferencesParent() ||
key != CanonicalizePath(key) || !value.IsAbsolute() ||
value.ReferencesParent() || value != CanonicalizePath(value)) {
// Treat an invalidly generated pair as a signal to finish generation.
break;
}
mounts->emplace(key, value);
}
return !mounts->empty();
}
} // namespace cryptohome