chromium/content/test/fuzzer/browser_accessibility_fuzzer.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accessibility/platform/browser_accessibility.h"

#include <fuzzer/FuzzedDataProvider.h>

#include <memory>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_content_client.h"
#include "ui/accessibility/platform/browser_accessibility_manager.h"
#include "ui/accessibility/platform/one_shot_accessibility_tree_search.h"
#include "ui/accessibility/platform/test_ax_node_id_delegate.h"
#include "ui/accessibility/platform/test_ax_platform_tree_manager_delegate.h"

struct Env {
  Env() {
    base::CommandLine::Init(0, nullptr);

    // BrowserAccessibilityStateImpl requires a ContentBrowserClient.
    content_client_ = std::make_unique<content::TestContentClient>();
    content_browser_client_ =
        std::make_unique<content::TestContentBrowserClient>();
    content::SetContentClient(content_client_.get());
    content::SetBrowserClientForTesting(content_browser_client_.get());
  }

  ~Env() {
    content::SetBrowserClientForTesting(nullptr);
    content::SetContentClient(nullptr);
  }

  content::BrowserTaskEnvironment task_environment;
  std::unique_ptr<content::ContentBrowserClient> content_browser_client_;
  std::unique_ptr<content::ContentClient> content_client_;
  base::AtExitManager at_exit;
};

namespace content {

// Return an accessibility role to use in a tree to fuzz. Out of the
// dozens of roles, these are the ones that tend to have special meaning
// or otherwise affect the logic in BrowserAccessibility code.
ax::mojom::Role GetInterestingRole(FuzzedDataProvider& fdp) {
  switch (fdp.ConsumeIntegralInRange(0, 12)) {
    default:
    case 0:
      return ax::mojom::Role::kNone;
    case 1:
      return ax::mojom::Role::kStaticText;
    case 2:
      return ax::mojom::Role::kInlineTextBox;
    case 3:
      return ax::mojom::Role::kParagraph;
    case 4:
      return ax::mojom::Role::kLineBreak;
    case 5:
      return ax::mojom::Role::kGenericContainer;
    case 6:
      return ax::mojom::Role::kButton;
    case 7:
      return ax::mojom::Role::kTextField;
    case 8:
      return ax::mojom::Role::kIframePresentational;
    case 9:
      return ax::mojom::Role::kIframe;
    case 10:
      return ax::mojom::Role::kHeading;
    case 11:
      return ax::mojom::Role::kPopUpButton;
    case 12:
      return ax::mojom::Role::kLink;
  }
}

// Add some states to the node based on the FuzzedDataProvider.
// Currently we're messing with ignored and invisible because that
// affects a lot of the tree walking code.
void AddStates(FuzzedDataProvider& fdp, ui::AXNodeData* node) {
  if (fdp.ConsumeBool())
    node->AddState(ax::mojom::State::kIgnored);
  if (fdp.ConsumeBool())
    node->AddState(ax::mojom::State::kInvisible);
}

// Construct an accessibility tree. The shape of the tree is static, but
// some of the properties of the nodes in the tree are determined by
// the fuzz input. Once the tree is constructed, fuzz by calling some
// functions that walk the tree in various ways to ensure they don't crash.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  static Env env;
  auto accessibility_state = BrowserAccessibilityStateImpl::Create();
  FuzzedDataProvider fdp(data, size);

  // The tree structure is always the same, only the data changes.
  //
  //       1
  //    /      \
  //  2     3    4
  // / \   / \  / \
  // 5 6   7 8  9 10
  //
  // In addition, there's a child tree that may be linked off one of
  // the nodes.

  ui::AXTreeID parent_tree_id = ui::AXTreeID::CreateNewAXTreeID();
  ui::AXTreeID child_tree_id = ui::AXTreeID::CreateNewAXTreeID();

  const int num_nodes = 10;

  ui::AXTreeUpdate tree;
  tree.root_id = 1;
  tree.tree_data.tree_id = parent_tree_id;
  tree.has_tree_data = true;
  tree.nodes.resize(10);

  tree.nodes[0].id = 1;
  tree.nodes[0].role = ax::mojom::Role::kRootWebArea;
  tree.nodes[0].child_ids = {2, 3, 4};
  // The root node cannot be ignored, so we'll only try invisible.
  if (fdp.ConsumeBool()) {
    tree.nodes[0].AddState(ax::mojom::State::kInvisible);
  }

  tree.nodes[1].id = 2;
  tree.nodes[1].role = GetInterestingRole(fdp);
  tree.nodes[1].child_ids = {5, 6};
  AddStates(fdp, &tree.nodes[1]);

  tree.nodes[2].id = 3;
  tree.nodes[2].role = GetInterestingRole(fdp);
  tree.nodes[2].child_ids = {7, 8};
  AddStates(fdp, &tree.nodes[2]);

  tree.nodes[3].id = 4;
  tree.nodes[3].role = GetInterestingRole(fdp);
  tree.nodes[3].child_ids = {9, 10};
  AddStates(fdp, &tree.nodes[3]);

  for (int i = 4; i < num_nodes; i++) {
    tree.nodes[i].id = i + 1;
    tree.nodes[i].role = GetInterestingRole(fdp);
    AddStates(fdp, &tree.nodes[i]);
  }

  for (int i = 0; i < num_nodes; i++)
    tree.nodes[i].SetName(fdp.ConsumeRandomLengthString(5));

  // Optionally, embed the child tree in the parent tree.
  int embedder_node = fdp.ConsumeIntegralInRange(0, num_nodes);
  if (embedder_node > 0)
    tree.nodes[embedder_node - 1].AddStringAttribute(
        ax::mojom::StringAttribute::kChildTreeId, child_tree_id.ToString());

  VLOG(1) << tree.ToString();

  // The child tree is trivial, just one node. That's still enough to exercise
  // a lot of paths.

  ui::AXTreeUpdate child_tree;
  child_tree.root_id = 1;
  child_tree.tree_data.tree_id = child_tree_id;
  child_tree.has_tree_data = true;
  child_tree.nodes.resize(1);

  child_tree.nodes[0].id = 1;
  child_tree.nodes[0].role = ax::mojom::Role::kRootWebArea;

  VLOG(1) << child_tree.ToString();

  ui::TestAXPlatformTreeManagerDelegate delegate;
  ui::TestAXNodeIdDelegate node_id_delegate;
  std::unique_ptr<ui::BrowserAccessibilityManager> manager(
      ui::BrowserAccessibilityManager::Create(tree, node_id_delegate,
                                              &delegate));
  std::unique_ptr<ui::BrowserAccessibilityManager> child_manager(
      ui::BrowserAccessibilityManager::Create(child_tree, node_id_delegate,
                                              &delegate));

  // We want to call a bunch of functions but we don't care what the
  // return values are. To ensure the compiler doesn't optimize the calls
  // away, push the return values onto a vector and make an assertion about
  // it at the end.
  std::vector<void*> results;

  // Test some tree-walking functions.
  ui::BrowserAccessibility* root = manager->GetBrowserAccessibilityRoot();
  results.push_back(root->PlatformDeepestFirstChild());
  results.push_back(root->PlatformDeepestLastChild());
  results.push_back(root->InternalDeepestFirstChild());
  results.push_back(root->InternalDeepestLastChild());

  // Test OneShotAccessibilityTreeSearch.
  ui::OneShotAccessibilityTreeSearch search(
      manager->GetBrowserAccessibilityRoot());
  search.SetDirection(fdp.ConsumeBool()
                          ? ui::OneShotAccessibilityTreeSearch::FORWARDS
                          : ui::OneShotAccessibilityTreeSearch::BACKWARDS);
  search.SetImmediateDescendantsOnly(fdp.ConsumeBool());
  search.SetCanWrapToLastElement(fdp.ConsumeBool());
  search.SetOnscreenOnly(fdp.ConsumeBool());
  if (fdp.ConsumeBool())
    search.AddPredicate(ui::AccessibilityButtonPredicate);
  if (fdp.ConsumeBool())
    search.SetSearchText(fdp.ConsumeRandomLengthString(5));
  size_t matches = search.CountMatches();
  for (size_t i = 0; i < matches; i++) {
    results.push_back(search.GetMatchAtIndex(i));
  }

  // This is just to ensure that none of the above code gets optimized away.
  CHECK_NE(0U, results.size());

  // Add a node, possibly clearing old children.
  int node_id = num_nodes + 1;
  int parent = fdp.ConsumeIntegralInRange(1, num_nodes);

  ui::AXTreeUpdate update;
  update.nodes.resize(2);
  update.nodes[0].id = parent;
  update.nodes[0].child_ids = {node_id};
  update.nodes[1].id = node_id;
  update.nodes[1].role = GetInterestingRole(fdp);
  AddStates(fdp, &update.nodes[1]);

  ui::AXUpdatesAndEvents notification;
  notification.updates.resize(1);
  notification.updates[0] = update;

  CHECK(manager->OnAccessibilityEvents(notification));

  return 0;
}

}  // namespace content