Coverage Report

Created: 2026-09-01 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/fuzzer_main.cc
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief Shared LPM mutation and execution for profile-split protobuf targets.
9
10
#include "proto_fuzzer/fuzzer_main.h"
11
12
#include <curl/curl.h>
13
#include <libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h>
14
15
#include <cassert>
16
#include <csignal>
17
18
#include "curl_fuzzer.pb.h"
19
#include "proto_fuzzer/option_apply.h"
20
#include "proto_fuzzer/scenario_runner.h"
21
#include "proto_fuzzer/target_policy.h"
22
23
namespace {
24
25
constexpr bool kUseBinaryFormat = true;
26
27
/// Register the fixed lane's normalizer before asking LPM to parse or mutate
28
/// its first input. LoadProtoInput runs LPM's Fix() only for direct corpus
29
/// loads; a just-mutated input is recovered from LPM's cache and has already
30
/// passed the same postprocessor. Ordering the registration here therefore
31
/// covers both paths without normalizing cached mutations twice.
32
///
33
/// One libFuzzer process exposes exactly one entrypoint and therefore one
34
/// profile. Capturing the first profile lets the shared runtime use LPM's
35
/// process-wide postprocessor registry without hiding target selection in a
36
/// compiler definition. The compatibility target deliberately registers
37
/// nothing, preserving its historical mixed-corpus mutation semantics.
38
38.5k
void EnsureTargetPostProcessor(proto_fuzzer::TargetProfile profile) {
39
38.5k
  if (profile == proto_fuzzer::TargetProfile::kCompatibility) {
40
15.8k
    return;
41
15.8k
  }
42
43
22.6k
  static const proto_fuzzer::TargetProfile registered_profile = profile;
44
22.6k
  static const protobuf_mutator::libfuzzer::PostProcessorRegistration<curl::fuzzer::proto::Scenario>
45
22.6k
      policy_registration([](curl::fuzzer::proto::Scenario* scenario, unsigned int /*seed*/) {
46
22.6k
        proto_fuzzer::ApplyTargetPolicy(scenario, registered_profile);
47
22.6k
        proto_fuzzer::CanonicalizeOptionValueCases(scenario);
48
22.6k
      });
49
50
  // A second profile in one process would cause LPM's global registry to
51
  // enforce the wrong lane. Real fuzz binaries cannot do this; keep the
52
  // assertion to make misuse by future in-process callers immediately clear.
53
22.6k
  assert(profile == registered_profile);
54
22.6k
  (void)policy_registration;
55
22.6k
}
56
57
// Wire curl_global_init once so repeated fuzz iterations don't pay for it on every call. libFuzzer reuses the process;
58
// static ctors run once.
59
struct CurlGlobalBootstrap {
60
18
  CurlGlobalBootstrap() {
61
    // Keep parity with the legacy harness. libcurl normally suppresses
62
    // SIGPIPE for its own writes, but fuzzed connection lifecycles also race
63
    // mock-peer teardown; those failures should be reported as socket errors,
64
    // not mistaken for process crashes.
65
18
    std::signal(SIGPIPE, SIG_IGN);
66
18
    curl_global_init(CURL_GLOBAL_ALL);
67
18
  }
68
};
69
const CurlGlobalBootstrap kGlobalBootstrap;
70
71
}  // namespace
72
73
namespace proto_fuzzer {
74
75
std::size_t ProtoFuzzerCustomMutator(TargetProfile profile, std::uint8_t* data, std::size_t size, std::size_t max_size,
76
0
                                     unsigned int seed) {
77
0
  EnsureTargetPostProcessor(profile);
78
0
  curl::fuzzer::proto::Scenario scenario;
79
0
  return protobuf_mutator::libfuzzer::CustomProtoMutator(kUseBinaryFormat, data, size, max_size, seed, &scenario);
80
0
}
81
82
std::size_t ProtoFuzzerCustomCrossOver(TargetProfile profile, const std::uint8_t* data1, std::size_t size1,
83
                                       const std::uint8_t* data2, std::size_t size2, std::uint8_t* out,
84
0
                                       std::size_t max_out_size, unsigned int seed) {
85
0
  EnsureTargetPostProcessor(profile);
86
0
  curl::fuzzer::proto::Scenario scenario1;
87
0
  curl::fuzzer::proto::Scenario scenario2;
88
0
  return protobuf_mutator::libfuzzer::CustomProtoCrossOver(kUseBinaryFormat, data1, size1, data2, size2, out,
89
0
                                                           max_out_size, seed, &scenario1, &scenario2);
90
0
}
91
92
38.5k
int ProtoFuzzerTestOneInput(TargetProfile profile, const std::uint8_t* data, std::size_t size) {
93
38.5k
  EnsureTargetPostProcessor(profile);
94
38.5k
  curl::fuzzer::proto::Scenario scenario;
95
38.5k
  if (protobuf_mutator::libfuzzer::LoadProtoInput(kUseBinaryFormat, data, size, &scenario)) {
96
38.3k
    ScenarioRunner().Run(scenario, RunModeFor(profile));
97
38.3k
  }
98
38.5k
  return 0;
99
38.5k
}
100
101
}  // namespace proto_fuzzer