blob: dbb4e9efa061fe41aa67f4c6265610007577d769 [file] [log] [blame]
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Run fuzzers on ChromeOS Devices for given packages.
Given a remote host and a list of portage packages,
run the associated package fuzzers in a sysroot on the
remote host.
As of 2022-10-24, this script is intended to
only test fuzzers on x86_64 devices.
Example:
cros_on_device_fuzz setup 'myhost:1234' 'my_package'
cros_on_device_fuzz fuzz 'myhost:1234' 'my_installed_fuzzer'
"""
import logging
from pathlib import Path
import re
import tempfile
from typing import Iterable, Optional
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import on_device_fuzz
from chromite.lib import remote_access
_DUT_FUZZER_ROOT = Path("/usr/local/tmp/fuzzer_root")
_IMPLICIT_SYSTEM_PACKAGE = "virtual/implicit-system"
_PACKAGE_SEPARATOR = re.compile(r"[ ,]+")
_FUZZER_BOARD = "amd64-generic"
def main(argv: Iterable[str]) -> None:
"""Dispatch to other functions."""
opts = parse_args(argv)
opts.func(opts)
def setup_main(opts) -> None:
"""Setup up the DUT for fuzzing."""
cros_build_lib.AssertInsideChroot()
device = _get_device(opts.device, opts.private_key)
# Get a nice string representation of the device host for logging
host = _format_collection(opts.device)
# We do this version check to ensure we have a CrOS device
# immediately.
try:
version = device.version
if not version:
raise RuntimeError("Version is null or empty.")
except Exception:
logging.error("Unable to get version of remote %s", host)
raise
logging.info("Connected to %s; CrOS version %s", host, version)
packages = set(re.split(_PACKAGE_SEPARATOR, opts.packages))
# We require virtual/implicit-system so that we can
# build the on-device sysroot. Without it, we'll
# miss critical libs and binaries.
packages.add(_IMPLICIT_SYSTEM_PACKAGE)
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)
tarball_name = "sysroot_fuzzer_tarball.tar.xz"
on_device_fuzz.create_sysroot_tarball(
packages=packages,
board=_FUZZER_BOARD,
output_path=tmpdir / tarball_name,
)
on_device_fuzz.create_dut_sysroot(
device,
sysroot_tarball=tmpdir / tarball_name,
sysroot_device_path=_DUT_FUZZER_ROOT,
)
logging.info("Fuzzer set up complete for %s", host)
def fuzz_main(opts) -> None:
"""Run a given fuzzer on the DUT."""
cros_build_lib.AssertInsideChroot()
fuzzer_install_path = Path("/usr/libexec/fuzzers")
device = _get_device(opts.device, opts.private_key)
on_device_fuzz.run_fuzzer_executable(
device,
sysroot_device_path=_DUT_FUZZER_ROOT,
sysroot_fuzzer_path=fuzzer_install_path / opts.fuzzer_name,
libfuzzer_options={},
)
def _get_device(
collection: commandline.Device, private_key: Optional[Path]
) -> remote_access.ChromiumOSDevice:
"""Get ChromiumOSDevice host from commandline.Device collection.
The device will always execute commands as root.
Args:
collection: Device collection object to connect to.
private_key: Path to private key to log into the device for.
Returns:
A remote_access.ChromiumOSDevice connection.
"""
return remote_access.ChromiumOSDevice(
collection.hostname,
port=collection.port,
username="root",
private_key=private_key,
base_dir=_DUT_FUZZER_ROOT,
connect=True,
)
def _format_collection(collection: commandline.Device) -> str:
if collection.port is None:
return collection.hostname
return f"{collection.hostname}:{collection.port}"
def parse_args(raw_args: Iterable[str]):
"""Parse CLI arguments."""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument(
"--private-key",
type=Path,
default=None,
help="RSA key for device. (default: lib.remote_access's key path)",
)
# Device parsing aids.
device_parser = commandline.DeviceParser(commandline.DeviceScheme.SSH)
device_host_help = "Device host, in the format '[ssh://]hostname[:port]'"
subparsers = parser.add_subparsers()
# setup subcommand
subparser = subparsers.add_parser(
"setup", help="Installs fuzzers on device sysroot."
)
subparser.set_defaults(func=setup_main)
subparser.add_argument(
"--device-sysroot-path",
type=Path,
help="Absolute location on device for the"
f" fuzzer sysroot. (default: {_DUT_FUZZER_ROOT})",
default=_DUT_FUZZER_ROOT,
)
subparser.add_argument("device", type=device_parser, help=device_host_help)
subparser.add_argument(
"packages",
help="Portage packages to install fuzzers for."
" Space or comma separated."
f" Automatically includes {_IMPLICIT_SYSTEM_PACKAGE}",
)
# fuzz subcommand
subparser = subparsers.add_parser(
"fuzz", help="Runs a fuzzer in device sysroot."
)
subparser.set_defaults(func=fuzz_main)
subparser.add_argument("device", type=device_parser, help=device_host_help)
subparser.add_argument(
"fuzzer_name", help="Name of the fuzzer executable to run."
)
return parser.parse_args(raw_args)