Merge bitcoin/bitcoin#28015: fuzz: Generate rpc fuzz targets individually

fa1e27fe8e fuzz: Generate rpc fuzz targets individually (MarcoFalke)

Pull request description:

  The `rpc` fuzz target was added more than two years ago in e45863166f. However, the bug https://github.com/bitcoin/bitcoin/issues/27913 was only found recently. Thus, it is pretty clear that fuzz engines can't deal with a search space that is too broad and can be extended in too many directions.

  Fix that by limiting the search space to each RPC method name and then iterate over all names, instead of letting the fuzz engine do the iteration.

  With this, the bug can be found in seconds, as opposed to years of CPU time (or never).

ACKs for top commit:
  brunoerg:
    ACK fa1e27fe8e
  dergoegge:
    ACK fa1e27fe8e

Tree-SHA512: 45ccba842367650d010320603153276b1b303deda9ba8c6bb31a4d2473b00aa5bca866db95f541485d65efd8276e2575026968c037872ef344fa33cf45bcdcd7
This commit is contained in:
fanquake 2023-07-07 10:57:22 +01:00
commit cf4da5ec29
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1

View File

@ -200,29 +200,47 @@ def generate_corpus(*, fuzz_pool, src_dir, build_dir, corpus_dir, targets):
{corpus_dir}. {corpus_dir}.
""" """
logging.info("Generating corpus to {}".format(corpus_dir)) logging.info("Generating corpus to {}".format(corpus_dir))
rpc_target = "rpc"
has_rpc = rpc_target in targets
if has_rpc:
targets.remove(rpc_target)
targets = [(t, {}) for t in targets]
if has_rpc:
lines = subprocess.run(
["git", "grep", "--function-context", "RPC_COMMANDS_SAFE_FOR_FUZZING{", os.path.join(src_dir, "src", "test", "fuzz", "rpc.cpp")],
check=True,
stdout=subprocess.PIPE,
text=True,
).stdout.splitlines()
lines = [l.split("\"", 1)[1].split("\"")[0] for l in lines if l.startswith("src/test/fuzz/rpc.cpp- \"")]
targets += [(rpc_target, {"LIMIT_TO_RPC_COMMAND": r}) for r in lines]
def job(command, t): def job(command, t, t_env):
logging.debug("Running '{}'\n".format(" ".join(command))) logging.debug(f"Running '{command}'")
logging.debug("Command '{}' output:\n'{}'\n".format( logging.debug("Command '{}' output:\n'{}'\n".format(
' '.join(command), command,
subprocess.run( subprocess.run(
command, command,
env=get_fuzz_env(target=t, source_dir=src_dir), env={
**t_env,
**get_fuzz_env(target=t, source_dir=src_dir),
},
check=True, check=True,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, text=True,
).stderr)) ).stderr,
))
futures = [] futures = []
for target in targets: for target, t_env in targets:
target_corpus_dir = os.path.join(corpus_dir, target) target_corpus_dir = corpus_dir / target
os.makedirs(target_corpus_dir, exist_ok=True) os.makedirs(target_corpus_dir, exist_ok=True)
command = [ command = [
os.path.join(build_dir, 'src', 'test', 'fuzz', 'fuzz'), os.path.join(build_dir, 'src', 'test', 'fuzz', 'fuzz'),
"-runs=100000", "-runs=100000",
target_corpus_dir, target_corpus_dir,
] ]
futures.append(fuzz_pool.submit(job, command, target)) futures.append(fuzz_pool.submit(job, command, target, t_env))
for future in as_completed(futures): for future in as_completed(futures):
future.result() future.result()