blob: 91f1a58eb8d06fcc7a23c9c3dbf0e29abc737675 [file] [log] [blame]
// Copyright 2020 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 <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/command_line.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <base/test/task_environment.h>
#include <base/test/test_timeouts.h>
#include <debugd/dbus-proxy-mocks.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <gmock/gmock.h>
#include <session_manager/dbus-proxy-mocks.h>
#include "crash-reporter/chrome_collector.h"
#include "crash-reporter/paths.h"
#include "crash-reporter/test_util.h"
namespace {
using ::testing::_;
using ::testing::WithArgs;
class Environment {
public:
Environment() {
// Disable logging per instructions.
logging::SetMinLogLevel(logging::LOGGING_FATAL);
// Needed for TestTimeouts::Initialize(). We don't have access to the real
// command-line, and even if we did, it's not clear we would want to use it.
const char* const kFakeCommandLine[] = {"chrome_collector_fuzzer"};
base::CommandLine::Init(std::size(kFakeCommandLine), kFakeCommandLine);
// Needed for SingleThreadTaskEnvironment.
TestTimeouts::Initialize();
}
};
class ChromeCollectorForFuzzing : public ChromeCollector {
public:
explicit ChromeCollectorForFuzzing(CrashSendingMode crash_sending_mode,
std::string user_name,
std::string user_hash,
std::string dri_error_state,
std::string dmesg_result)
: ChromeCollector(crash_sending_mode),
user_name_(std::move(user_name)),
user_hash_(std::move(user_hash)),
dri_error_state_(std::move(dri_error_state)),
dmesg_result_(std::move(dmesg_result)) {}
void SetUpDBus() override {
// Mock out all DBus calls so (a) we don't actually call DBus and (b) we
// don't CHECK fail when the DBus calls fail.
auto session_manager_mock =
std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>();
test_util::SetActiveSessions(session_manager_mock.get(),
{{user_name_, user_hash_}});
session_manager_proxy_ = std::move(session_manager_mock);
auto proxy_mock = std::make_unique<org::chromium::debugdProxyMock>();
std::function<void(base::OnceCallback<void(const std::string&)> &&)>
handler_dri_error_state =
[this](base::OnceCallback<void(const std::string&)> callback) {
task_environment_.GetMainThreadTaskRunner()->PostNonNestableTask(
FROM_HERE,
base::BindOnce(std::move(callback), dri_error_state_));
};
ON_CALL(*proxy_mock, GetLogAsync("i915_error_state", _, _, _))
.WillByDefault(WithArgs<1>(handler_dri_error_state));
std::function<void(base::OnceCallback<void(const std::string&)> &&)>
handler_dmesg =
[this](base::OnceCallback<void(const std::string&)> callback) {
task_environment_.GetMainThreadTaskRunner()->PostNonNestableTask(
FROM_HERE,
base::BindOnce(std::move(callback), dmesg_result_));
};
ON_CALL(*proxy_mock, CallDmesgAsync(_, _, _, _))
.WillByDefault(WithArgs<1>(handler_dmesg));
debugd_proxy_ = std::move(proxy_mock);
}
private:
// RunLoop requires a task environment.
base::test::SingleThreadTaskEnvironment task_environment_;
// Results from the fake RetrieveActiveSessions call
const std::string user_name_;
const std::string user_hash_;
// Results for the fake GetLogAsync call
const std::string dri_error_state_;
// Results for the fake CallDmesgAsync call
const std::string dmesg_result_;
};
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static Environment env;
// Put all files into a per-run temp directory.
base::ScopedTempDir temp_dir;
CHECK(temp_dir.CreateUniqueTempDir());
base::FilePath test_dir = temp_dir.GetPath();
paths::SetPrefixForTesting(test_dir);
FuzzedDataProvider provider(data, size);
// Force daemon store on or off, since fuzzers should not have
// non-deterministic behavior.
bool use_daemon_store = provider.ConsumeBool();
// Exactly one of exe_name and non_exe_error_key can be set or we CHECK fail.
std::string exe_name;
std::string non_exe_error_key;
if (provider.ConsumeBool()) {
exe_name = provider.ConsumeRandomLengthString();
if (exe_name.empty()) {
return 0; // Or we'll CHECK-fail. Fuzzers shouldn't exit on any input.
}
} else {
non_exe_error_key = provider.ConsumeRandomLengthString();
if (non_exe_error_key.empty()) {
return 0; // Or we'll CHECK-fail. Fuzzers shouldn't exit on any input.
}
}
// pid and uid must be >= 0 Or we'll CHECK-fail. Fuzzers shouldn't exit on any
// input.
pid_t pid = provider.ConsumeIntegralInRange(
(pid_t)0, std::numeric_limits<pid_t>::max());
uid_t uid = provider.ConsumeIntegralInRange(
(uid_t)0, std::numeric_limits<uid_t>::max());
std::string user_name = provider.ConsumeRandomLengthString();
std::string user_hash = provider.ConsumeRandomLengthString();
// If the user_hash looks like an absolute directory path,
// GetDaemonStoreCrashDirectories will CHECK fail when calling
// base::FilePath::Append().
base::FilePath user_hash_path(user_hash);
if (user_hash_path.IsAbsolute()) {
return 0;
}
// If the user_hash_path uses "..", the fuzzer can "escape" from the temp
// directory and overwrite random files.
if (user_hash_path.ReferencesParent()) {
return 0;
}
std::string dri_error_state = provider.ConsumeRandomLengthString();
std::string dmesg_result = provider.ConsumeRandomLengthString();
// Despite the Memfd in the name of HandleCrashThroughMemfd, we can pass a
// file handle to a normal file. memfd isn't supported by QEMU so better to
// just use normal files here.
base::FilePath test_input_path = test_dir.Append("test_input");
std::string input = provider.ConsumeRemainingBytesAsString();
base::WriteFile(test_input_path, input.c_str(), input.length());
base::File test_input(test_input_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!test_input.IsValid()) {
return 0;
}
// Empty because otherwise we CHECK-fail if this isn't a test image.
const std::string kEmptyDumpDir;
// kNormalCrashSendMode -- This makes it much simpler to mock out the DBus
// calls, and we're not fuzzing the crash loop logic.
ChromeCollectorForFuzzing collector(
CrashCollector::kNormalCrashSendMode, std::move(user_name),
std::move(user_hash), std::move(dri_error_state),
std::move(dmesg_result));
collector.Initialize(false);
collector.force_daemon_store_for_testing(use_daemon_store);
collector.HandleCrashThroughMemfd(test_input.TakePlatformFile(), pid, uid,
exe_name, non_exe_error_key, kEmptyDumpDir);
return 0;
}