blob: 2555e32292472519221b6853b907143e9a9d414d [file] [log] [blame]
Jordan R Abrahams-Whitehead315df5e2022-10-24 21:55:37 +00001#!/usr/bin/env python3
2# Copyright 2022 The ChromiumOS Authors.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Run fuzzers on ChromeOS Devices for given packages.
7
8Given a remote host and a list of portage packages,
9run the associated package fuzzers in a sysroot on the
10remote host.
11
12As of 2022-10-24, this script is intended to
13only test fuzzers on x86_64 devices.
14
15Example:
16 cros_on_device_fuzz setup 'myhost:1234' 'my_package'
17 cros_on_device_fuzz fuzz 'myhost:1234' 'my_installed_fuzzer'
18"""
19
20import logging
21from pathlib import Path
22import re
23import tempfile
24from typing import Iterable
25
26from chromite.lib import commandline
27from chromite.lib import cros_build_lib
28from chromite.lib import on_device_fuzz
29from chromite.lib import remote_access
30
31
32_DEFAULT_PRIVATE_KEY = Path.home() / ".ssh" / "testing_rsa"
33_DUT_FUZZER_ROOT = Path("/usr/local/tmp/fuzzer_root")
34_IMPLICIT_SYSTEM_PACKAGE = "virtual/implicit-system"
35_PACKAGE_SEPARATOR = re.compile("[ ,]+")
36
37
38def main(argv: Iterable[str]):
39 """Dispatch to other functions."""
40 args = parse_args(argv)
41 args.func(args)
42
43
44def setup_main(args):
45 """Setup up the DUT for fuzzing."""
46 cros_build_lib.AssertInsideChroot()
47 device = _parse_device(args.device_host, _DEFAULT_PRIVATE_KEY)
48 # We do this version check to ensure we have a CrOS device
49 # immediately.
50 try:
51 version = device.version
52 except Exception:
53 logging.error("Unable to get version of remote %s", args.device_host)
54 raise
55 logging.info("Connected to %s; CrOS version %s", args.device_host, version)
56 packages = set(re.split(_PACKAGE_SEPARATOR, args.packages))
57 # We require virtual/implicit-system so that we can
58 # build the on-device sysroot. Without it, we'll
59 # miss critical libs and binaries.
60 packages.add(_IMPLICIT_SYSTEM_PACKAGE)
61 with tempfile.TemporaryDirectory() as tmpdir:
62 tmpdir = Path(tmpdir)
63 tarball_name = "sysroot_fuzzer_tarball.tar.xz"
64 on_device_fuzz.create_sysroot_tarball(
65 packages=packages,
66 board="amd64-generic",
67 output_path=tmpdir / tarball_name,
68 )
69 on_device_fuzz.create_dut_sysroot(
70 device,
71 sysroot_tarball=tmpdir / tarball_name,
72 sysroot_device_path=_DUT_FUZZER_ROOT,
73 )
74 logging.info("Fuzzer set up complete for %s", args.device_host)
75
76
77def fuzz_main(args):
78 """Run a given fuzzer on the DUT."""
79 cros_build_lib.AssertInsideChroot()
80 fuzzer_install_path = Path("/usr/libexec/fuzzers")
81 device = _parse_device(args.device_host, _DEFAULT_PRIVATE_KEY)
82 libfuzzer_options = {}
83 on_device_fuzz.run_fuzzer_executable(
84 device,
85 sysroot_device_path=_DUT_FUZZER_ROOT,
86 sysroot_fuzzer_path=fuzzer_install_path / args.fuzzer_name,
87 libfuzzer_options=libfuzzer_options,
88 )
89
90
91def _parse_device(
92 device_host: str, private_key: Path
93) -> remote_access.ChromiumOSDevice:
94 """Get ChromiumOSDevice host from device_host format string.
95
96 Gets a ChromiumOSDevice, executing as root.
97
98 Args:
99 device_host: 'hostname:port' or 'hostname' string.
100 private_key: Path to private key to log into the device for.
101
102 Returns:
103 A remote_access.ChromiumOSDevice connection.
104
105 Raises:
106 ValueError if device_host is not formatted correctly.
107 """
108 host = device_host.split(":")
109 if len(host) == 2:
110 device = remote_access.ChromiumOSDevice(
111 host[0],
112 port=host[1],
113 username="root",
114 private_key=private_key,
115 connect=True,
116 )
117 elif len(host) == 1:
118 device = remote_access.ChromiumOSDevice(
119 host[0],
120 username="root",
121 private_key=private_key,
122 connect=True,
123 )
124 else:
125 raise ValueError(f"Badly formatted device host: {device_host}")
126 return device
127
128
129def parse_args(raw_args: Iterable[str]):
130 """Parse CLI arguments."""
131 parser = commandline.ArgumentParser(description=__doc__)
132 parser.add_argument(
133 "--private-key",
134 type=Path,
135 default=_DEFAULT_PRIVATE_KEY,
136 help=f"RSA key for device. (default: {_DEFAULT_PRIVATE_KEY})",
137 )
138 subparsers = parser.add_subparsers()
139
140 # setup subcommand
141 setup_parser = subparsers.add_parser(
142 "setup", help="Installs fuzzers on device sysroot."
143 )
144 setup_parser.set_defaults(func=setup_main)
145 device_host_help = "Device host, in the format 'hostname:port'"
146 setup_parser.add_argument(
147 "--device-sysroot-path",
148 type=Path,
149 help="Absolute location on device for the"
150 f" fuzzer sysroot. (default: {_DUT_FUZZER_ROOT})",
151 default=_DUT_FUZZER_ROOT,
152 )
153 setup_parser.add_argument("device_host", help=device_host_help)
154 setup_parser.add_argument(
155 "packages",
156 help="Portage packages to install fuzzers for."
157 " Space or comma separated."
158 f" Automatically includes {_IMPLICIT_SYSTEM_PACKAGE}",
159 )
160
161 # fuzz subcommand
162 fuzz_parser = subparsers.add_parser(
163 "fuzz", help="Runs a fuzzer in device sysroot."
164 )
165 fuzz_parser.set_defaults(func=fuzz_main)
166 fuzz_parser.add_argument("device_host", help=device_host_help)
167 fuzz_parser.add_argument(
168 "fuzzer_name", help="Name of the fuzzer executable to run."
169 )
170
171 return parser.parse_args(raw_args)