chromium/ui/accessibility/ax_table_fuzzer.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include <tuple>

#include "build/build_config.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree.h"

// The purpose of this script is to fuzz code that parses
// table-like structures. As a result, we want to generate
// accessibility trees that contain lots of table-related
// roles.
//
// We also bias towards cells and rows so that we end up
// with more of those overall.
ax::mojom::Role GetInterestingTableRole(unsigned char byte) {
  switch (byte % 16) {
    default:
    case 0:
    case 1:
    case 2:
    case 3:
      return ax::mojom::Role::kCell;
    case 4:
    case 5:
      return ax::mojom::Role::kRow;
    case 6:
      return ax::mojom::Role::kTable;
    case 7:
      return ax::mojom::Role::kGrid;
    case 8:
      return ax::mojom::Role::kColumnHeader;
    case 9:
      return ax::mojom::Role::kRowHeader;
    case 10:
      return ax::mojom::Role::kGenericContainer;
    case 11:
      return ax::mojom::Role::kNone;
    case 12:
      return ax::mojom::Role::kLayoutTable;
    case 13:
      return ax::mojom::Role::kLayoutTableCell;
    case 14:
      return ax::mojom::Role::kLayoutTableRow;
    case 15:
      return ax::mojom::Role::kMain;
  }
}

// We want some of the nodes in the accessibility tree to have
// table-related attributes.
ax::mojom::IntAttribute GetInterestingTableAttribute(unsigned char byte) {
  switch (byte % 10) {
    case 0:
    default:
      return ax::mojom::IntAttribute::kTableCellRowIndex;
    case 1:
      return ax::mojom::IntAttribute::kTableCellColumnIndex;
    case 2:
      return ax::mojom::IntAttribute::kTableRowCount;
    case 3:
      return ax::mojom::IntAttribute::kTableColumnCount;
    case 4:
      return ax::mojom::IntAttribute::kAriaRowCount;
    case 5:
      return ax::mojom::IntAttribute::kAriaColumnCount;
    case 6:
      return ax::mojom::IntAttribute::kTableCellRowSpan;
    case 7:
      return ax::mojom::IntAttribute::kTableCellColumnSpan;
    case 8:
      return ax::mojom::IntAttribute::kAriaCellRowIndex;
    case 9:
      return ax::mojom::IntAttribute::kAriaCellColumnIndex;
  }
}

// Call all of the table-related APIs on an accessibility node.
// These will be no-ops if the node is not part of a complete
// table. We don't care about any of the results, we just want
// to make sure none of these crash or hang.
void TestTableAPIs(const ui::AXNode* node) {
  std::ignore = node->IsTable();
  std::ignore = node->GetTableColCount();
  std::ignore = node->GetTableRowCount();
  std::ignore = node->GetTableAriaColCount();
  std::ignore = node->GetTableAriaRowCount();
  std::ignore = node->GetTableCellCount();
  std::ignore = node->GetTableCaption();
  for (int i = 0; i < 8; i++)
    std::ignore = node->GetTableCellFromIndex(i);
  for (int i = 0; i < 3; i++)
    for (int j = 0; j < 3; j++)
      std::ignore = node->GetTableCellFromCoords(i, j);
  // Note: some of the APIs return IDs - we don't care what's
  // returned, we just want to make sure these APIs don't
  // crash. Normally |ids| is an out argument only, but
  // there's no reason we shouldn't be able to pass a vector
  // that was previously used by another call.
  std::vector<ui::AXNodeID> ids;
  for (int i = 0; i < 3; i++) {
    std::vector<ui::AXNodeID> col_header_node_ids =
        node->GetTableColHeaderNodeIds(i);
    ids.insert(ids.end(), col_header_node_ids.begin(),
               col_header_node_ids.end());

    std::vector<ui::AXNodeID> row_header_node_ids =
        node->GetTableRowHeaderNodeIds(i);
    ids.insert(ids.end(), row_header_node_ids.begin(),
               row_header_node_ids.end());
  }
  std::vector<ui::AXNodeID> unique_cell_ids = node->GetTableUniqueCellIds();
  ids.insert(ids.end(), unique_cell_ids.begin(), unique_cell_ids.end());

  std::ignore = node->IsTableRow();
  std::ignore = node->GetTableRowRowIndex();
#if BUILDFLAG(IS_APPLE)
  std::ignore = node->IsTableColumn();
  std::ignore = node->GetTableColColIndex();
#endif
  std::ignore = node->IsTableCellOrHeader();
  std::ignore = node->GetTableCellIndex();
  std::ignore = node->GetTableCellColIndex();
  std::ignore = node->GetTableCellRowIndex();
  std::ignore = node->GetTableCellColSpan();
  std::ignore = node->GetTableCellRowSpan();
  std::ignore = node->GetTableCellAriaColIndex();
  std::ignore = node->GetTableCellAriaRowIndex();
  std::vector<ui::AXNodeID> cell_col_header_node_ids =
      node->GetTableCellColHeaderNodeIds();
  ids.insert(ids.end(), cell_col_header_node_ids.begin(),
             cell_col_header_node_ids.end());
  std::vector<ui::AXNodeID> cell_row_header_node_ids =
      node->GetTableCellRowHeaderNodeIds();
  ids.insert(ids.end(), cell_row_header_node_ids.begin(),
             cell_row_header_node_ids.end());
  std::vector<ui::AXNode*> headers;
  node->GetTableCellColHeaders(&headers);
  node->GetTableCellRowHeaders(&headers);

  for (const ui::AXNode* child : node->children()) {
    TestTableAPIs(child);
  }
}

// Entry point for LibFuzzer.
extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
  ui::AXTreeUpdate initial_state;
  initial_state.root_id = 1;
  size_t i = 0;

  // The root of the accessibility tree.
  ui::AXNodeData root;
  root.id = 1;
  if (i < size)
    root.role = GetInterestingTableRole(data[i++]);
  root.child_ids.push_back(2);
  initial_state.nodes.push_back(root);

  // Force the next node of the accessibility tree to be a table,
  // and give it no attributes but a few children.
  ui::AXNodeData table;
  table.id = 2;
  table.role = ax::mojom::Role::kTable;
  if (i < size) {
    size_t child_count = data[i++] % 8;
    for (size_t j = 0; j < child_count && i < size; j++)
      table.child_ids.push_back(3 + data[i++] % 32);
  }
  initial_state.nodes.push_back(table);

  // Create more accessibility nodes that might result in a table.
  int next_id = 3;
  while (i < size) {
    ui::AXNodeData node;
    node.id = next_id++;
    if (i < size)
      node.role = GetInterestingTableRole(data[i++]);
    if (i < size) {
      int attr_count = data[i++] % 6;
      for (int j = 0; j < attr_count && i + 1 < size; j++) {
        unsigned char attr = data[i++];
        int32_t value = static_cast<int32_t>(data[i++]) - 2;
        node.AddIntAttribute(GetInterestingTableAttribute(attr), value);
      }
    }
    if (i < size) {
      size_t child_count = data[i++] % 8;
      for (size_t j = 0; j < child_count && i < size; j++)
        node.child_ids.push_back(4 + data[i++] % 32);
    }
    initial_state.nodes.push_back(node);
  }

  // Run with --v=1 to aid in debugging a specific crash.
  VLOG(1) << "Input accessibility tree:\n" << initial_state.ToString();

  ui::AXTree tree;
  if (tree.Unserialize(initial_state))
    TestTableAPIs(tree.root());

  return 0;
}