Coverage Report

Created: 2026-05-30 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/scenario_runner.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 Implementation of ScenarioRunner::Run.
9
10
#include "proto_fuzzer/scenario_runner.h"
11
12
#include <curl/curl.h>
13
14
#include <memory>
15
#include <string>
16
#include <vector>
17
18
#include "proto_fuzzer/mock_server.h"
19
#include "proto_fuzzer/mock_server_base.h"
20
#include "proto_fuzzer/option_apply.h"
21
#include "proto_fuzzer/websocket_mock_server.h"
22
23
namespace proto_fuzzer {
24
25
namespace {
26
27
/// @brief RAII wrapper for CURL* easy handles.
28
struct CurlEasyDeleter {
29
7.82k
  void operator()(CURL* h) const noexcept {
30
7.82k
    if (h) curl_easy_cleanup(h);
31
7.82k
  }
32
};
33
using CurlEasyPtr = std::unique_ptr<CURL, CurlEasyDeleter>;
34
35
/// Map a Scheme enum to the URL scheme literal.
36
7.86k
const char* SchemePrefix(curl::fuzzer::proto::Scheme scheme) {
37
7.86k
  switch (scheme) {
38
5.69k
    case curl::fuzzer::proto::SCHEME_HTTP:
39
5.69k
      return "http";
40
145
    case curl::fuzzer::proto::SCHEME_HTTPS:
41
145
      return "https";
42
1.59k
    case curl::fuzzer::proto::SCHEME_WS:
43
1.59k
      return "ws";
44
393
    case curl::fuzzer::proto::SCHEME_WSS:
45
393
      return "wss";
46
29
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
47
31
    default:
48
31
      return nullptr;
49
7.86k
  }
50
7.86k
}
51
52
/// Pick the MockServerBase subclass to use for 'scenario'. The scheme is the
53
/// sole classifier today: WS / WSS → WebSocketMockServer, HTTP / HTTPS →
54
/// MockServer. Returns nullptr for unsupported / unspecified schemes so the
55
/// runner can skip the scenario cleanly.
56
7.82k
std::unique_ptr<MockServerBase> MakeMockServerForScenario(const curl::fuzzer::proto::Scenario& scenario) {
57
7.82k
  switch (scenario.scheme()) {
58
5.68k
    case curl::fuzzer::proto::SCHEME_HTTP:
59
5.83k
    case curl::fuzzer::proto::SCHEME_HTTPS:
60
5.83k
      return std::make_unique<MockServer>();
61
1.59k
    case curl::fuzzer::proto::SCHEME_WS:
62
1.98k
    case curl::fuzzer::proto::SCHEME_WSS:
63
1.98k
      return std::make_unique<WebSocketMockServer>();
64
0
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
65
0
    default:
66
0
      return nullptr;
67
7.82k
  }
68
7.82k
}
69
70
}  // namespace
71
72
/// @class proto_fuzzer::ScenarioRunner
73
/// @brief Executes one Scenario end-to-end: applies options, picks a mock
74
///        server for the scheme, and hands off to the mock's DriveScenario.
75
///        Instances are cheap; create one per fuzz case so per-scenario state
76
///        is torn down cleanly.
77
78
/// Default-construct an empty runner. All state is set up inside Run().
79
7.86k
ScenarioRunner::ScenarioRunner() = default;
80
81
/// Default destructor; per-run state is local to Run() so nothing to tear
82
/// down at instance scope.
83
7.86k
ScenarioRunner::~ScenarioRunner() = default;
84
85
/// Run the scenario. Classifies the scheme to pick a MockServer subclass,
86
/// applies baseline + per-option setopt calls, builds the URL from
87
/// scenario.scheme + scenario.host_path, and drives the transfer via the
88
/// mock's own DriveScenario.
89
/// @param scenario The Scenario describing the curl operations to perform.
90
/// @return 0 on normal completion (including curl errors that aren't harness
91
///         failures). The libFuzzer entrypoint doesn't care about the return
92
///         value; it's there for tests.
93
7.86k
int ScenarioRunner::Run(const curl::fuzzer::proto::Scenario& scenario) {
94
7.86k
  const char* prefix = SchemePrefix(scenario.scheme());
95
7.86k
  if (prefix == nullptr || scenario.host_path().empty()) {
96
41
    return 0;
97
41
  }
98
99
7.82k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario);
100
7.82k
  if (!mock) {
101
0
    return 0;
102
0
  }
103
104
7.82k
  CurlEasyPtr easy(curl_easy_init());
105
7.82k
  if (!easy) {
106
0
    return 0;
107
0
  }
108
109
7.82k
  std::vector<std::string> string_storage;
110
7.82k
  string_storage.reserve(scenario.options_size());
111
112
7.82k
  struct curl_slist* connect_to = ApplyBaselineOptions(easy.get());
113
114
7.82k
  std::string url = std::string(prefix) + "://" + scenario.host_path();
115
7.82k
  curl_easy_setopt(easy.get(), CURLOPT_URL, url.c_str());
116
117
7.82k
  mock->Install(easy.get());
118
119
65.6k
  for (const auto& option : scenario.options()) {
120
    // Intentionally ignore per-option CURLcode: the fuzzer's job is to stress
121
    // curl, not to validate that every option is applied cleanly.
122
65.6k
    (void)ApplySetOption(easy.get(), option, &string_storage);
123
65.6k
  }
124
125
7.82k
  mock->DriveScenario(easy.get(), scenario);
126
127
7.82k
  easy.reset();
128
7.82k
  curl_slist_free_all(connect_to);
129
7.82k
  return 0;
130
7.82k
}
131
132
}  // namespace proto_fuzzer