Coverage Report

Created: 2026-09-14 07:12

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
#include <cstdlib>
18
19
#include "curl_fuzzer.pb.h"
20
#include "proto_fuzzer/option_apply.h"
21
#include "proto_fuzzer/scenario_runner.h"
22
#include "proto_fuzzer/target_policy.h"
23
24
namespace {
25
26
constexpr bool kUseBinaryFormat = true;
27
28
/// Register the fixed lane's normalizer before asking LPM to parse or mutate
29
/// its first input. LoadProtoInput runs LPM's Fix() only for direct corpus
30
/// loads; a just-mutated input is recovered from LPM's cache and has already
31
/// passed the same postprocessor. Ordering the registration here therefore
32
/// covers both paths without normalizing cached mutations twice.
33
///
34
/// One libFuzzer process exposes exactly one entrypoint and therefore one
35
/// profile. Capturing the first profile lets the shared runtime use LPM's
36
/// process-wide postprocessor registry without hiding target selection in a
37
/// compiler definition. The compatibility target deliberately registers
38
/// nothing, preserving its historical mixed-corpus mutation semantics.
39
294k
void EnsureTargetPostProcessor(proto_fuzzer::TargetProfile profile) {
40
294k
  if (profile == proto_fuzzer::TargetProfile::kCompatibility) {
41
25.9k
    return;
42
25.9k
  }
43
44
268k
  static const proto_fuzzer::TargetProfile registered_profile = profile;
45
268k
  static const protobuf_mutator::libfuzzer::PostProcessorRegistration<curl::fuzzer::proto::Scenario>
46
268k
      policy_registration([](curl::fuzzer::proto::Scenario* scenario, unsigned int /*seed*/) {
47
261k
        proto_fuzzer::ApplyTargetPolicy(scenario, registered_profile);
48
261k
        proto_fuzzer::CanonicalizeOptionValueCases(scenario);
49
261k
      });
50
51
  // A second profile in one process would cause LPM's global registry to
52
  // enforce the wrong lane. Real fuzz binaries cannot do this; keep the
53
  // assertion to make misuse by future in-process callers immediately clear.
54
268k
  assert(profile == registered_profile);
55
268k
  (void)policy_registration;
56
268k
}
57
58
// Wire curl_global_init once so repeated fuzz iterations don't pay for it on every call. libFuzzer reuses the process;
59
// static ctors run once.
60
struct CurlGlobalBootstrap {
61
40
  CurlGlobalBootstrap() {
62
    // Open curl's TLS keylog at backend init. Curl_tls_keylog_open() runs once
63
    // inside curl_global_init, so setting this after bootstrap would be too
64
    // late. Keep the output in /dev/null and let a reproducer override it.
65
40
    (void)setenv("SSLKEYLOGFILE", "/dev/null", 0);
66
67
    // Keep parity with the legacy harness. libcurl normally suppresses
68
    // SIGPIPE for its own writes, but fuzzed connection lifecycles also race
69
    // mock-peer teardown; those failures should be reported as socket errors,
70
    // not mistaken for process crashes.
71
40
    std::signal(SIGPIPE, SIG_IGN);
72
40
    curl_global_init(CURL_GLOBAL_ALL);
73
40
  }
74
};
75
const CurlGlobalBootstrap kGlobalBootstrap;
76
77
}  // namespace
78
79
namespace proto_fuzzer {
80
81
std::size_t ProtoFuzzerCustomMutator(TargetProfile profile, std::uint8_t* data, std::size_t size, std::size_t max_size,
82
0
                                     unsigned int seed) {
83
0
  EnsureTargetPostProcessor(profile);
84
0
  curl::fuzzer::proto::Scenario scenario;
85
0
  return protobuf_mutator::libfuzzer::CustomProtoMutator(kUseBinaryFormat, data, size, max_size, seed, &scenario);
86
0
}
87
88
std::size_t ProtoFuzzerCustomCrossOver(TargetProfile profile, const std::uint8_t* data1, std::size_t size1,
89
                                       const std::uint8_t* data2, std::size_t size2, std::uint8_t* out,
90
0
                                       std::size_t max_out_size, unsigned int seed) {
91
0
  EnsureTargetPostProcessor(profile);
92
0
  curl::fuzzer::proto::Scenario scenario1;
93
0
  curl::fuzzer::proto::Scenario scenario2;
94
0
  return protobuf_mutator::libfuzzer::CustomProtoCrossOver(kUseBinaryFormat, data1, size1, data2, size2, out,
95
0
                                                           max_out_size, seed, &scenario1, &scenario2);
96
0
}
97
98
294k
int ProtoFuzzerTestOneInput(TargetProfile profile, const std::uint8_t* data, std::size_t size) {
99
294k
  EnsureTargetPostProcessor(profile);
100
294k
  curl::fuzzer::proto::Scenario scenario;
101
294k
  if (protobuf_mutator::libfuzzer::LoadProtoInput(kUseBinaryFormat, data, size, &scenario)) {
102
287k
    proto_fuzzer::RunScenario(scenario, RunModeFor(profile));
103
287k
  }
104
294k
  return 0;
105
294k
}
106
107
}  // namespace proto_fuzzer