Coverage Report

Created: 2026-04-28 07:09

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
3.28k
  void operator()(CURL* h) const noexcept {
30
3.28k
    if (h) curl_easy_cleanup(h);
31
3.28k
  }
32
};
33
using CurlEasyPtr = std::unique_ptr<CURL, CurlEasyDeleter>;
34
35
/// Map a Scheme enum to the URL scheme literal.
36
3.30k
const char* SchemePrefix(curl::fuzzer::proto::Scheme scheme) {
37
3.30k
  switch (scheme) {
38
2.16k
    case curl::fuzzer::proto::SCHEME_HTTP:
39
2.16k
      return "http";
40
69
    case curl::fuzzer::proto::SCHEME_HTTPS:
41
69
      return "https";
42
822
    case curl::fuzzer::proto::SCHEME_WS:
43
822
      return "ws";
44
235
    case curl::fuzzer::proto::SCHEME_WSS:
45
235
      return "wss";
46
10
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
47
12
    default:
48
12
      return nullptr;
49
3.30k
  }
50
3.30k
}
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
3.28k
std::unique_ptr<MockServerBase> MakeMockServerForScenario(const curl::fuzzer::proto::Scenario& scenario) {
57
3.28k
  switch (scenario.scheme()) {
58
2.16k
    case curl::fuzzer::proto::SCHEME_HTTP:
59
2.23k
    case curl::fuzzer::proto::SCHEME_HTTPS:
60
2.23k
      return std::make_unique<MockServer>();
61
821
    case curl::fuzzer::proto::SCHEME_WS:
62
1.05k
    case curl::fuzzer::proto::SCHEME_WSS:
63
1.05k
      return std::make_unique<WebSocketMockServer>();
64
0
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
65
0
    default:
66
0
      return nullptr;
67
3.28k
  }
68
3.28k
}
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
3.30k
ScenarioRunner::ScenarioRunner() = default;
80
81
/// Default destructor; per-run state is local to Run() so nothing to tear
82
/// down at instance scope.
83
3.30k
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
3.30k
int ScenarioRunner::Run(const curl::fuzzer::proto::Scenario& scenario) {
94
3.30k
  const char* prefix = SchemePrefix(scenario.scheme());
95
3.30k
  if (prefix == nullptr || scenario.host_path().empty()) {
96
19
    return 0;
97
19
  }
98
99
3.28k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario);
100
3.28k
  if (!mock) {
101
0
    return 0;
102
0
  }
103
104
3.28k
  CurlEasyPtr easy(curl_easy_init());
105
3.28k
  if (!easy) {
106
0
    return 0;
107
0
  }
108
109
3.28k
  std::vector<std::string> string_storage;
110
3.28k
  string_storage.reserve(scenario.options_size());
111
112
3.28k
  struct curl_slist* connect_to = ApplyBaselineOptions(easy.get());
113
114
3.28k
  std::string url = std::string(prefix) + "://" + scenario.host_path();
115
3.28k
  curl_easy_setopt(easy.get(), CURLOPT_URL, url.c_str());
116
117
3.28k
  mock->Install(easy.get());
118
119
47.3k
  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
47.3k
    (void)ApplySetOption(easy.get(), option, &string_storage);
123
47.3k
  }
124
125
3.28k
  mock->DriveScenario(easy.get(), scenario);
126
127
3.28k
  easy.reset();
128
3.28k
  curl_slist_free_all(connect_to);
129
3.28k
  return 0;
130
3.28k
}
131
132
}  // namespace proto_fuzzer