#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include <atspi/atspi-types.h>
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <optional>
#include <random>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include "base/base_paths.h"
#include "base/containers/fixed_flat_set.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/memory/singleton.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/fuzzing/atspi_in_process_fuzzer.pb.h"
#include "chrome/test/fuzzing/in_process_proto_fuzzer.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/test/test_helpers.h"
#include "sql/transaction.h"
#include "testing/libfuzzer/libfuzzer_exports.h"
#include "ui/accessibility/platform/inspect/ax_inspect_scenario.h"
#include "ui/accessibility/platform/inspect/ax_inspect_utils_auralinux.h"
#include "ui/base/glib/scoped_gobject.h"
constexpr auto kBlockedControls =
base::MakeFixedFlatSet<std::string_view>({"Close", "Relaunch"});
#if 0
#define ATSPI_FUZZER_LOG LOG(INFO)
#else
#define ATSPI_FUZZER_LOG EAT_STREAM_PARAMETERS
#endif
using ScopedAtspiAccessible = ScopedGObject<AtspiAccessible>;
using test::fuzzing::atspi_fuzzing::Action;
using test::fuzzing::atspi_fuzzing::ActionVerb;
using test::fuzzing::atspi_fuzzing::ControlPath;
using test::fuzzing::atspi_fuzzing::PathElement;
static constexpr size_t kNumControlsToDeclareToCentipede = 65536;
__attribute__((used,
retain,
section("__centipede_extra_features"))) static uint64_t
extra_features[kNumControlsToDeclareToCentipede];
constexpr uint64_t kControlsReachedDomain = 0;
enum NodeDepth { Root, FirstLevel, Other };
class UiNode {
public:
explicit UiNode(ScopedAtspiAccessible accessible, NodeDepth depth);
std::string& GetName() const;
std::string& GetRole() const;
bool IsBlocklisted() const;
std::optional<size_t> FindMatchingChild(const PathElement& selector) const;
UiNode* GetNthChild(size_t index) { return children_[index].get(); }
void RescanAndFindNewChildren(std::vector<std::vector<UiNode*>>& new_controls,
const std::vector<UiNode*> my_path);
void GetAllChildren(std::vector<std::vector<UiNode*>>& new_controls,
const std::vector<UiNode*> my_path);
void AppendControlPath(ControlPath& output_path,
const base::span<UiNode*>& path_to_control);
ScopedAtspiAccessible& Get() { return node_; }
bool ContainsPasswordNode() const;
bool ProbablyActionable() const;
private:
void ScanAttributes() const;
mutable ScopedAtspiAccessible
node_;
NodeDepth depth_;
mutable std::optional<std::string> name_;
mutable std::optional<std::string> role_;
std::vector<std::unique_ptr<UiNode>> children_;
};
class AtspiInProcessFuzzer
: public InProcessTextProtoFuzzer<test::fuzzing::atspi_fuzzing::FuzzCase> {
public:
AtspiInProcessFuzzer();
void SetUpOnMainThread() override;
bool UseSingleProcessMode() override;
int Fuzz(const test::fuzzing::atspi_fuzzing::FuzzCase& fuzz_case) override;
static size_t CustomMutator(uint8_t* data,
size_t size,
size_t max_size,
unsigned int seed);
private:
void LoadAPage();
int HandleAction(const Action& action, size_t& control_path_id);
static ScopedAtspiAccessible GetRootNode();
static bool InvokeAction(ScopedAtspiAccessible& node, size_t action_id);
static bool ReplaceText(ScopedAtspiAccessible& node,
const std::string& newtext);
static bool SetSelection(ScopedAtspiAccessible& node,
const std::vector<uint32_t>& new_selection);
std::string StringifyNodePath(const ControlPath& path);
static std::string DebugPath(const ControlPath& path);
static size_t MutateUsingLPM(uint8_t* data,
size_t size,
size_t max_size,
unsigned int seed);
static std::optional<size_t> MutateControlPath(uint8_t* data,
size_t size,
size_t max_size,
std::minstd_rand& random);
static void AddPrerequisiteActionToTestCase(
google::protobuf::TextFormat::Parser& parser,
AtspiInProcessFuzzer::FuzzCase& input,
const std::string& control_path,
size_t position_to_insert,
std::minstd_rand& random,
size_t overflow_guard);
static bool AttemptMutateMessage(AtspiInProcessFuzzer::FuzzCase& message,
std::minstd_rand& random);
std::optional<UiNode> ui_state_;
bool merge_mode_ = false;
};
struct ActionPath {
std::string control_path;
std::string verb_string;
};
REGISTER_IN_PROCESS_FUZZER(AtspiInProcessFuzzer)
static void TestOneProtoInput(AtspiInProcessFuzzer::FuzzCase);
using FuzzerProtoType =
protobuf_mutator::libfuzzer::macro_internal::GetFirstParam<
decltype(&TestOneProtoInput)>::type;
DEFINE_CUSTOM_PROTO_CROSSOVER_IMPL(false, FuzzerProtoType)
DEFINE_POST_PROCESS_PROTO_MUTATION_IMPL(FuzzerProtoType)
class Database {
public:
static Database* GetInstance();
std::optional<std::string> GetRandomControlPath(std::minstd_rand& random);
std::optional<ActionPath> GetPrerequisite(const std::string& control,
std::minstd_rand& random);
void InsertControlPathAndPrerequisites(
const std::string& newly_visible_control,
const std::optional<ActionPath>& prerequisite_action,
bool probably_actionable);
private:
Database();
void DropTableIfExists(const std::string& table);
std::optional<int64_t> InsertControlPath(const std::string& control_path,
bool probably_actionable,
bool has_prereq);
friend struct base::DefaultSingletonTraits<Database>;
std::unique_ptr<sql::Database> db_;
};
AtspiInProcessFuzzer::AtspiInProcessFuzzer() {
setenv("ACCESSIBILITY_ENABLED", "1", 1);
}
bool AtspiInProcessFuzzer::UseSingleProcessMode() {
return false;
}
void AtspiInProcessFuzzer::SetUpOnMainThread() {
InProcessTextProtoFuzzer<
test::fuzzing::atspi_fuzzing::FuzzCase>::SetUpOnMainThread();
LoadAPage();
ATSPI_FUZZER_LOG << "Waiting for AX tree to be populated";
size_t counter = 0;
bool found_ui = false;
while (!found_ui) {
base::RunLoop nested_run_loop;
base::OneShotTimer timer;
timer.Start(FROM_HERE, base::Milliseconds(100),
nested_run_loop.QuitClosure());
nested_run_loop.Run();
ScopedAtspiAccessible root_node = GetRootNode();
ui_state_ = UiNode(root_node, NodeDepth::Root);
if (ui_state_->ContainsPasswordNode()) {
found_ui = true;
break;
}
CHECK(counter++ < 300)
<< "It took more than 30 seconds for the AX tree to be populated";
}
std::vector<std::vector<UiNode*>> all_controls;
std::vector<UiNode*> root_path;
ui_state_->GetAllChildren(all_controls, root_path);
ATSPI_FUZZER_LOG << "AX tree populated; found " << all_controls.size()
<< " controls.";
for (auto& control : all_controls) {
ControlPath path;
ui_state_->AppendControlPath(path, control);
ATSPI_FUZZER_LOG << " " << DebugPath(path);
Database::GetInstance()->InsertControlPathAndPrerequisites(
StringifyNodePath(path), std::nullopt,
control.back()->ProbablyActionable());
}
ATSPI_FUZZER_LOG << "Initial controls inserted into database.";
merge_mode_ = InMergeMode();
LOG(INFO) << "Merging mode: " << merge_mode_;
}
std::string AtspiInProcessFuzzer::DebugPath(const ControlPath& path) {
std::stringstream ss;
for (auto& elem : path.path_to_control()) {
if (elem.has_named()) {
ss << '"' << elem.named().name() << '"';
} else {
ss << '"' << elem.anonymous().role()
<< "\":" << elem.anonymous().ordinal();
}
ss << ", ";
}
std::string output = ss.str();
if (!output.empty()) {
output.resize(output.size() - 2);
}
return output;
}
void AtspiInProcessFuzzer::LoadAPage() {
std::string html_string =
"<html><head><title>Test</title></head><body><form>Username: <input "
"name=\"username\" type=\"text\">Password: "
"<input name=\"password\" type=\"password\"><input name=\"Submit\" "
"type=\"submit\"></form></body></html>";
std::string url_string = "data:text/html;charset=utf-8,";
const bool kUsePlus = false;
url_string.append(base::EscapeQueryParamValue(html_string, kUsePlus));
CHECK(ui_test_utils::NavigateToURL(browser(), GURL(url_string)));
}
int AtspiInProcessFuzzer::Fuzz(
const test::fuzzing::atspi_fuzzing::FuzzCase& fuzz_case) {
size_t control_path_id = 0;
for (const test::fuzzing::atspi_fuzzing::Action& action :
fuzz_case.action()) {
for (const test::fuzzing::atspi_fuzzing::PathElement& path_element :
action.path_to_control().path_to_control()) {
switch (path_element.element_type_case()) {
case test::fuzzing::atspi_fuzzing::PathElement::kNamed: {
const std::string& name = path_element.named().name();
if (name.empty() || name.find('\0') != std::string::npos ||
!base::IsStringUTF8(name)) {
return -1;
}
} break;
case test::fuzzing::atspi_fuzzing::PathElement::kAnonymous: {
const std::string& role = path_element.anonymous().role();
if (role.empty() || role.find('\0') != std::string::npos ||
!base::IsStringUTF8(role)) {
return -1;
}
} break;
default:
return -1;
}
}
if (action.verb().action_choice_case() ==
test::fuzzing::atspi_fuzzing::ActionVerb::ACTION_CHOICE_NOT_SET) {
return -1;
}
}
for (const test::fuzzing::atspi_fuzzing::Action& action :
fuzz_case.action()) {
int status = HandleAction(action, control_path_id);
if (status != 0) {
return status;
}
}
return 0;
}
int AtspiInProcessFuzzer::HandleAction(
const test::fuzzing::atspi_fuzzing::Action& action,
size_t& control_path_id) {
UiNode* current_control = &*ui_state_;
std::vector<size_t> current_control_path;
for (const test::fuzzing::atspi_fuzzing::PathElement& path_element :
action.path_to_control().path_to_control()) {
std::optional<size_t> selected_control =
current_control->FindMatchingChild(path_element);
if (!selected_control.has_value()) {
ATSPI_FUZZER_LOG << "Failed to find "
<< DebugPath(action.path_to_control());
return -1;
}
current_control_path.push_back(*selected_control);
current_control = current_control->GetNthChild(*selected_control);
if (control_path_id < kNumControlsToDeclareToCentipede) {
base::span<uint8_t> path_data(
reinterpret_cast<uint8_t*>(current_control_path.data()),
current_control_path.size() * sizeof(size_t));
size_t hash =
base::FastHash(path_data) & std::numeric_limits<uint32_t>::max();
extra_features[control_path_id++] = (kControlsReachedDomain << 32) | hash;
}
}
if (current_control->IsBlocklisted()) {
return -1;
}
ATSPI_FUZZER_LOG << "Acting on " << DebugPath(action.path_to_control());
switch (action.verb().action_choice_case()) {
case test::fuzzing::atspi_fuzzing::ActionVerb::kTakeAction:
if (!InvokeAction(current_control->Get(),
action.verb().take_action().action_id())) {
return -1;
}
break;
case test::fuzzing::atspi_fuzzing::ActionVerb::kReplaceText:
if (!ReplaceText(current_control->Get(),
action.verb().replace_text().new_text())) {
return -1;
}
break;
case test::fuzzing::atspi_fuzzing::ActionVerb::kSetSelection: {
std::vector<uint32_t> new_selection(
action.verb().set_selection().selected_child().begin(),
action.verb().set_selection().selected_child().end());
if (!SetSelection(current_control->Get(), new_selection)) {
return -1;
}
} break;
case test::fuzzing::atspi_fuzzing::ActionVerb::ACTION_CHOICE_NOT_SET:
break;
}
base::RunLoop().RunUntilIdle();
if (merge_mode_) {
return 0;
}
std::vector<std::vector<UiNode*>> new_controls;
const std::vector<UiNode*> empty_path;
ui_state_->RescanAndFindNewChildren(new_controls, empty_path);
if (new_controls.empty()) {
return 0;
}
ActionPath action_path;
if (!google::protobuf::TextFormat::PrintToString(action.verb(),
&action_path.verb_string)) {
return 0;
}
if (!google::protobuf::TextFormat::PrintToString(action.path_to_control(),
&action_path.control_path)) {
return 0;
}
ATSPI_FUZZER_LOG << "Interacting with " << DebugPath(action.path_to_control())
<< " made visible:";
for (auto& node : new_controls) {
ControlPath node_path;
ui_state_->AppendControlPath(node_path, node);
ATSPI_FUZZER_LOG << " " << DebugPath(node_path);
Database::GetInstance()->InsertControlPathAndPrerequisites(
StringifyNodePath(node_path), action_path,
node.back()->ProbablyActionable());
}
return 0;
}
std::string AtspiInProcessFuzzer::StringifyNodePath(
const test::fuzzing::atspi_fuzzing::ControlPath& path) {
std::string output_path_string;
if (!google::protobuf::TextFormat::PrintToString(path, &output_path_string)) {
return "";
}
return output_path_string;
}
ScopedAtspiAccessible AtspiInProcessFuzzer::GetRootNode() {
__pid_t pid = getpid();
int selectors = ui::AXTreeSelector::None;
AtspiAccessible* accessible = ui::FindAccessible(ui::AXTreeSelector(
selectors, "", static_cast<gfx::AcceleratedWidget>(pid)));
CHECK(accessible);
return WrapGObject(accessible);
}
namespace {
bool CheckOk(gboolean ok, GError** error) {
if (*error) {
g_clear_error(error);
return false;
}
return ok;
}
std::string CheckString(char* result, GError** error) {
std::string retval;
if (!*error) {
retval = result;
}
g_clear_error(error);
free(result);
return retval;
}
}
bool AtspiInProcessFuzzer::InvokeAction(ScopedAtspiAccessible& node,
size_t action_id) {
AtspiAction* action = atspi_accessible_get_action_iface(node);
if (!action) {
return false;
}
GError* error = nullptr;
size_t num_actions = atspi_action_get_n_actions(action, &error);
if (error) {
g_clear_error(&error);
return false;
}
if (num_actions == 0) {
return false;
}
gboolean ok = atspi_action_do_action(action, action_id % num_actions, &error);
return CheckOk(ok, &error);
}
bool AtspiInProcessFuzzer::ReplaceText(ScopedAtspiAccessible& node,
const std::string& newtext) {
AtspiEditableText* editable = atspi_accessible_get_editable_text_iface(node);
if (!editable) {
return false;
}
GError* error = nullptr;
gboolean ok =
atspi_editable_text_set_text_contents(editable, newtext.data(), &error);
return CheckOk(ok, &error);
}
bool AtspiInProcessFuzzer::SetSelection(
ScopedAtspiAccessible& node,
const std::vector<uint32_t>& new_selection) {
AtspiSelection* selection = atspi_accessible_get_selection_iface(node);
if (!selection) {
return false;
}
GError* error = nullptr;
int child_count = atspi_accessible_get_child_count(node, &error);
if (!CheckOk(error != nullptr, &error)) {
return false;
}
if (child_count == 0) {
return false;
}
std::set<uint32_t> children_to_select;
for (uint32_t id : new_selection) {
children_to_select.insert(id % child_count);
}
gboolean ok = atspi_selection_clear_selection(selection, &error);
if (!CheckOk(ok, &error)) {
return false;
}
for (auto idx : children_to_select) {
ok = atspi_selection_select_child(selection, idx, &error);
if (!CheckOk(ok, &error)) {
return false;
}
}
return true;
}
bool UiNode::IsBlocklisted() const {
return kBlockedControls.contains(GetName());
}
bool UiNode::ContainsPasswordNode() const {
if (GetName() == "Password: ") {
return true;
}
for (auto& child : children_) {
if (child->ContainsPasswordNode()) {
return true;
}
}
return false;
}
bool UiNode::ProbablyActionable() const {
if (!GetName().empty()) {
return true;
}
std::string& role = GetRole();
return role != "frame" && role != "panel";
}
std::string& UiNode::GetName() const {
if (name_.has_value()) {
return *name_;
}
if (depth_ != NodeDepth::Other) {
name_ = "";
} else {
GError* error = nullptr;
name_ = CheckString(atspi_accessible_get_name(node_, &error), &error);
}
return *name_;
}
std::string& UiNode::GetRole() const {
if (role_.has_value()) {
return *role_;
}
GError* error = nullptr;
role_ = CheckString(atspi_accessible_get_role_name(node_, &error), &error);
return *role_;
}
std::optional<size_t> UiNode::FindMatchingChild(
const test::fuzzing::atspi_fuzzing::PathElement& selector) const {
switch (selector.element_type_case()) {
case test::fuzzing::atspi_fuzzing::PathElement::kNamed: {
for (size_t i = 0; i < children_.size(); i++) {
auto& control = children_[i];
std::string& name = control->GetName();
if (name == selector.named().name().data()) {
return i;
}
}
break;
}
case test::fuzzing::atspi_fuzzing::PathElement::kAnonymous: {
size_t to_skip = selector.anonymous().ordinal();
for (size_t i = 0; i < children_.size(); i++) {
auto& control = children_[i];
std::string name = control->GetName();
if (name == "") {
std::string& role = control->GetRole();
if (role == selector.anonymous().role().data()) {
if (to_skip-- == 0) {
return i;
}
}
}
}
break;
}
case test::fuzzing::atspi_fuzzing::PathElement::ELEMENT_TYPE_NOT_SET:
break;
}
return std::nullopt;
}
UiNode::UiNode(ScopedAtspiAccessible accessible, NodeDepth depth)
: node_(accessible), depth_(depth) {
ScanAttributes();
GError* error = nullptr;
int child_count = atspi_accessible_get_child_count(node_, &error);
if (error) {
g_clear_error(&error);
return;
}
if (child_count <= 0) {
return;
}
children_.reserve(child_count);
NodeDepth next_depth =
(depth == NodeDepth::Root) ? NodeDepth::FirstLevel : NodeDepth::Other;
for (int i = 0; i < child_count; i++) {
AtspiAccessible* child =
atspi_accessible_get_child_at_index(node_, i, &error);
if (error) {
g_clear_error(&error);
continue;
}
if (child) {
children_.push_back(
std::make_unique<UiNode>(WrapGObject(child), next_depth));
}
}
}
void UiNode::ScanAttributes() const {
GError* error = nullptr;
GHashTable* attributes = atspi_accessible_get_attributes(node_, &error);
if (!error && attributes) {
GHashTableIter i;
void* key = nullptr;
void* value = nullptr;
g_hash_table_iter_init(&i, attributes);
while (g_hash_table_iter_next(&i, &key, &value)) {
}
}
g_clear_error(&error);
g_hash_table_unref(attributes);
}
void UiNode::RescanAndFindNewChildren(
std::vector<std::vector<UiNode*>>& new_controls,
const std::vector<UiNode*> my_path) {
std::unordered_map<AtspiAccessible*, std::unique_ptr<UiNode>*> old_nodes;
old_nodes.reserve(children_.size());
for (auto& child : children_) {
old_nodes.insert(std::make_pair(child->Get(), &child));
}
ScanAttributes();
GError* error = nullptr;
int child_count = atspi_accessible_get_child_count(node_, &error);
if (error) {
g_clear_error(&error);
return;
}
if (child_count < 0) {
return;
}
std::vector<std::unique_ptr<UiNode>> revised_children;
revised_children.reserve(child_count);
NodeDepth next_depth =
(depth_ == NodeDepth::Root) ? NodeDepth::FirstLevel : NodeDepth::Other;
for (int i = 0; i < child_count; i++) {
AtspiAccessible* child =
atspi_accessible_get_child_at_index(node_, i, &error);
if (error) {
g_clear_error(&error);
continue;
}
if (child) {
auto it = old_nodes.find(child);
std::vector<UiNode*> new_node_path = my_path;
if (it == old_nodes.end()) {
revised_children.push_back(
std::make_unique<UiNode>(WrapGObject(child), next_depth));
new_node_path.push_back(revised_children.back().get());
new_controls.push_back(new_node_path);
revised_children.back()->GetAllChildren(new_controls, new_node_path);
} else {
revised_children.push_back(std::move(*it->second));
new_node_path.push_back(revised_children.back().get());
revised_children.back()->RescanAndFindNewChildren(new_controls,
new_node_path);
}
}
}
children_ = std::move(revised_children);
}
void UiNode::GetAllChildren(std::vector<std::vector<UiNode*>>& new_controls,
const std::vector<UiNode*> my_path) {
for (auto& child : children_) {
std::vector<UiNode*> child_path = my_path;
child_path.push_back(child.get());
new_controls.push_back(child_path);
child->GetAllChildren(new_controls, child_path);
}
}
void UiNode::AppendControlPath(ControlPath& output_path,
const base::span<UiNode*>& path_to_control) {
UiNode* desired_child = path_to_control.front();
PathElement* output_element = output_path.add_path_to_control();
if (!desired_child->GetName().empty()) {
*output_element->mutable_named()->mutable_name() = desired_child->GetName();
} else {
std::string& desired_role = desired_child->GetRole();
*output_element->mutable_anonymous()->mutable_role() = desired_role;
size_t prior_controls_with_this_role = 0;
for (auto& child : children_) {
std::string& name = child->GetName();
if (name.empty()) {
if (child.get() == desired_child) {
output_element->mutable_anonymous()->set_ordinal(
prior_controls_with_this_role);
break;
}
if (child->GetRole() == desired_role) {
prior_controls_with_this_role++;
}
}
}
}
if (path_to_control.size() > 1) {
desired_child->AppendControlPath(output_path, path_to_control.subspan(1u));
}
}
namespace {
bool ParseTextMessage(base::span<uint8_t> data,
AtspiInProcessFuzzer::FuzzCase* output) {
std::string data_string = {reinterpret_cast<const char*>(data.data()),
data.size()};
output->Clear();
google::protobuf::TextFormat::Parser parser;
parser.SetRecursionLimit(100);
parser.AllowPartialMessage(true);
parser.AllowUnknownField(true);
if (!parser.ParseFromString(data_string, output)) {
output->Clear();
return false;
}
return true;
}
size_t SaveMessageAsText(const AtspiInProcessFuzzer::FuzzCase& message,
uint8_t* data,
size_t max_size) {
std::string tmp;
if (!google::protobuf::TextFormat::PrintToString(message, &tmp)) {
return 0;
}
if (tmp.size() <= max_size) {
memcpy(data, tmp.data(), tmp.size());
return tmp.size();
}
return 0;
}
}
size_t AtspiInProcessFuzzer::MutateUsingLPM(uint8_t* data,
size_t size,
size_t max_size,
unsigned int seed) {
AtspiInProcessFuzzer::FuzzCase input;
return protobuf_mutator::libfuzzer::CustomProtoMutator(
false, data, size, max_size, seed, &input);
}
std::optional<size_t> AtspiInProcessFuzzer::MutateControlPath(
uint8_t* data,
size_t size,
size_t max_size,
std::minstd_rand& random) {
AtspiInProcessFuzzer::FuzzCase input;
base::span<uint8_t> message_data(data, size);
ParseTextMessage(message_data, &input);
if (AttemptMutateMessage(input, random)) {
return SaveMessageAsText(input, data, max_size);
}
return std::nullopt;
}
void AtspiInProcessFuzzer::AddPrerequisiteActionToTestCase(
google::protobuf::TextFormat::Parser& parser,
AtspiInProcessFuzzer::FuzzCase& input,
const std::string& control_path,
size_t position_to_insert,
std::minstd_rand& random,
size_t overflow_guard) {
if (overflow_guard > 100) {
return;
}
std::optional<ActionPath> prereq =
Database::GetInstance()->GetPrerequisite(control_path, random);
if (prereq) {
Action* new_action = input.add_action();
if (!parser.ParseFromString(prereq->control_path,
new_action->mutable_path_to_control())) {
return;
}
if (!parser.ParseFromString(prereq->verb_string,
new_action->mutable_verb())) {
return;
}
google::protobuf::RepeatedPtrField<Action>* action_field(
input.mutable_action());
for (size_t i(input.action_size() - 1); i > position_to_insert; --i) {
action_field->SwapElements(i, i - 1);
}
AddPrerequisiteActionToTestCase(parser, input, prereq->control_path,
position_to_insert, random,
overflow_guard + 1);
}
}
bool AtspiInProcessFuzzer::AttemptMutateMessage(
AtspiInProcessFuzzer::FuzzCase& input,
std::minstd_rand& random) {
if (input.action_size() == 0) {
test::fuzzing::atspi_fuzzing::Action* action = input.add_action();
action->mutable_verb()->mutable_take_action();
}
size_t random_action =
std::uniform_int_distribution<size_t>(0, input.action_size() * 2)(random);
size_t chosen_action =
std::min(random_action, static_cast<size_t>(input.action_size()) - 1);
test::fuzzing::atspi_fuzzing::Action* action =
input.mutable_action(chosen_action);
std::optional<std::string> control_path =
Database::GetInstance()->GetRandomControlPath(random);
if (!control_path.has_value()) {
return false;
}
action->mutable_path_to_control()->Clear();
google::protobuf::TextFormat::Parser parser;
parser.SetRecursionLimit(100);
parser.AllowPartialMessage(true);
parser.AllowUnknownField(true);
if (!parser.ParseFromString(*control_path,
action->mutable_path_to_control())) {
return false;
}
AddPrerequisiteActionToTestCase(parser, input, *control_path, chosen_action,
random, 0);
return true;
}
size_t AtspiInProcessFuzzer::CustomMutator(uint8_t* data,
size_t size,
size_t max_size,
unsigned int seed) {
std::minstd_rand random(seed);
int mutation_strategy = std::uniform_int_distribution(0, 100)(random);
switch (mutation_strategy) {
case 0:
return MutateUsingLPM(data, size, max_size, random());
case 1: {
size = MutateUsingLPM(data, size, max_size, random());
std::optional<size_t> new_size =
MutateControlPath(data, size, max_size, random);
return new_size.value_or(size);
}
default: {
std::optional<size_t> new_size =
MutateControlPath(data, size, max_size, random);
if (!new_size.has_value()) {
return MutateUsingLPM(data, size, max_size, random());
}
return *new_size;
}
}
}
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
size_t size,
size_t max_size,
unsigned int seed) {
return AtspiInProcessFuzzer::CustomMutator(data, size, max_size, seed);
}
Database* Database::GetInstance() {
return base::Singleton<Database>::get();
}
Database::Database() {
base::ScopedAllowBlockingForTesting allow_blocking;
db_ = std::make_unique<sql::Database>(
sql::DatabaseOptions()
.set_exclusive_locking(false),
sql::test::kTestTag);
base::FilePath db_path;
CHECK(base::PathService::Get(base::DIR_TEMP, &db_path));
db_path = db_path.AppendASCII("atspi_in_process_fuzzer_controls.db");
CHECK(db_->Open(db_path));
CHECK(db_->Execute("PRAGMA foreign_keys = ON"));
sql::Transaction transaction(db_.get());
CHECK(transaction.Begin());
DropTableIfExists("roles");
DropTableIfExists("names");
DropTableIfExists("prereqs");
DropTableIfExists("actions");
DropTableIfExists("controls");
DropTableIfExists("controlsv2");
DropTableIfExists("controlsv3");
if (!db_->DoesTableExist("controlsv4")) {
CHECK(db_->Execute(
"create table controlsv4 (id INTEGER PRIMARY KEY, path "
"TEXT NOT NULL UNIQUE, probably_actionable BOOL, has_prereq BOOL)"));
}
if (!db_->DoesTableExist("actionsv2")) {
CHECK(db_->Execute(
"create table actionsv2 (id INTEGER PRIMARY KEY, control_id "
"INTEGER NOT NULL, verb TEXT NOT NULL, FOREIGN KEY(control_id) "
"REFERENCES controlsv4(id) ON DELETE CASCADE, unique(control_id, "
"verb))"));
}
if (!db_->DoesTableExist("prereqsv2")) {
CHECK(db_->Execute(
"create table prereqsv2 (control_id INTEGER NOT NULL, "
"action_id INTEGER NOT NULL, FOREIGN KEY(control_id) REFERENCES "
"controlsv4(id) ON DELETE CASCADE, FOREIGN KEY(action_id) REFERENCES "
"actionsv2(id) ON DELETE CASCADE, unique(control_id, action_id))"));
}
CHECK(transaction.Commit());
}
void Database::DropTableIfExists(const std::string& table_name) {
if (db_->DoesTableExist(table_name)) {
CHECK(db_->Execute(base::StrCat({"drop table ", table_name})));
}
}
std::optional<int64_t> Database::InsertControlPath(const std::string& path,
bool probably_actionable,
bool has_prereq) {
sql::Statement stmt(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR IGNORE INTO controlsv4 (path, "
"probably_actionable, has_prereq) VALUES (?, ?, ?)"));
stmt.BindString(0, path);
stmt.BindBool(1, probably_actionable);
stmt.BindBool(2, has_prereq);
if (!stmt.Run()) {
return std::nullopt;
}
sql::Statement get_statement(db_->GetCachedStatement(
SQL_FROM_HERE, "select id from controlsv4 where path = ?"));
get_statement.BindString(0, path);
if (!get_statement.Step()) {
return std::nullopt;
}
int64_t controlv2_id = get_statement.ColumnInt64(0);
return controlv2_id;
}
void Database::InsertControlPathAndPrerequisites(
const std::string& newly_visible_control,
const std::optional<ActionPath>& prerequisite_action,
bool probably_actionable) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::optional<int64_t> control_id =
InsertControlPath(newly_visible_control, probably_actionable,
prerequisite_action.has_value());
if (!control_id) {
return;
}
if (prerequisite_action) {
std::optional<int64_t> prereq_control_id =
InsertControlPath(prerequisite_action->control_path, true, false);
if (!prereq_control_id) {
return;
}
sql::Statement stmt(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR IGNORE INTO actionsv2 (control_id, verb) VALUES (?, ?)"));
stmt.BindInt64(0, *prereq_control_id);
stmt.BindString(1, prerequisite_action->verb_string);
if (!stmt.Run()) {
return;
}
sql::Statement get_statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"select id from actionsv2 where control_id = ? and verb = ?"));
get_statement.BindInt64(0, *prereq_control_id);
get_statement.BindString(1, prerequisite_action->verb_string);
if (!get_statement.Step()) {
return;
}
int64_t action_id = get_statement.ColumnInt64(0);
sql::Statement stmt2(
db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT OR IGNORE INTO prereqsv2 (control_id, "
"action_id) VALUES (?, ?)"));
stmt2.BindInt64(0, *control_id);
stmt2.BindInt64(1, action_id);
if (!stmt2.Run()) {
return;
}
}
const int64_t kMaxRowsAllowed = 1000;
sql::Statement delete_stmt(db_->GetCachedStatement(
SQL_FROM_HERE,
"DELETE from controlsv4 where id in (select id from controlsv4 "
"order by "
"random() limit max(0, ((select count(*) from controlsv4) - ?)))"));
delete_stmt.BindInt64(0, kMaxRowsAllowed);
base::IgnoreResult(delete_stmt.Run());
}
std::optional<std::string> Database::GetRandomControlPath(
std::minstd_rand& random) {
base::ScopedAllowBlockingForTesting allow_blocking;
size_t random_selector =
std::uniform_int_distribution<int64_t>(0, INT64_MAX)(random);
const std::string query =
"WITH controlsv4_scored AS (select controlsv4.*, "
"CASE WHEN probably_actionable = true THEN 10 ELSE 1 END AS "
"actionability_bias, "
"CASE WHEN has_prereq = true THEN 20 ELSE 1 END AS "
"prereq_bias, "
"MIN(LENGTH(path) / 20, 1) AS path_bias "
"FROM controlsv4), "
"controlsv4_with_bias AS ("
"SELECT id, path, CAST(path_bias * actionability_bias * prereq_bias AS "
"INTEGER) AS bias FROM controlsv4_scored), "
"controlsv4_repeated AS (SELECT id, path, bias, 1 AS counter FROM "
"controlsv4_with_bias "
"UNION ALL select id, path, bias, counter + 1 FROM controlsv4_repeated "
"WHERE counter < bias) "
"SELECT path FROM controlsv4_repeated LIMIT 1 offset (? % (SELECT "
"count(*) FROM controlsv4_repeated))";
sql::Statement get_statement(db_->GetCachedStatement(SQL_FROM_HERE, query));
get_statement.BindInt64(0, random_selector);
if (!get_statement.Step()) {
return std::nullopt;
}
return get_statement.ColumnString(0);
}
std::optional<ActionPath> Database::GetPrerequisite(const std::string& control,
std::minstd_rand& random) {
base::ScopedAllowBlockingForTesting allow_blocking;
size_t random_selector =
std::uniform_int_distribution<int64_t>(INT64_MIN, INT64_MAX)(random);
sql::Statement get_statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"with prereq_options as (select prereq_control.path as prereq_path, "
"this_control.path as this_path, verb from "
"controlsv4 as this_control, controlsv4 as prereq_control, actionsv2, "
"prereqsv2 where this_control.id = prereqsv2.control_id and "
"prereqsv2.action_id "
"= actionsv2.id and actionsv2.control_id = prereq_control.id)"
"select prereq_path, verb from prereq_options where this_path = ? limit "
"1 offset (? % (select count(*) from "
"prereq_options))"));
get_statement.BindString(0, control);
get_statement.BindInt64(1, random_selector);
if (!get_statement.Step()) {
return std::nullopt;
}
ActionPath result;
result.control_path = get_statement.ColumnString(0);
result.verb_string = get_statement.ColumnString(1);
return result;
}