Coverage Report

Created: 2026-09-14 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/fuzz_netrc.cc
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
// Direct fuzz harness for curl's netrc file loader and lexer. The public
8
// CURLOPT_NETRC path is useful for end-to-end protocol coverage, but it makes
9
// mutations pay for URL setup and a connection before they can reach quoted
10
// tokens, macdef skipping, or entry selection. This target calls curl's
11
// internal file-backed scanner directly while still presenting it with a real
12
// FILE path through /proc/self/fd.
13
14
#include <curl/curl.h>
15
16
#include <cstddef>
17
#include <cstdint>
18
#include <string>
19
20
#include "proto_fuzzer/bounded_anonymous_input_file.h"
21
22
extern "C" {
23
#include "creds.h"
24
#include "netrc.h"
25
} // extern "C"
26
27
namespace {
28
29
constexpr std::size_t kMaxNetrcBytes = 128 * 1024;
30
31
struct CurlBootstrap {
32
2
  CurlBootstrap() { (void)curl_global_init(CURL_GLOBAL_ALL); }
33
0
  ~CurlBootstrap() { curl_global_cleanup(); }
34
};
35
36
CurlBootstrap kCurlBootstrap;
37
proto_fuzzer::BoundedAnonymousInputFile kInputFile(kMaxNetrcBytes + 1);
38
39
/// Preserve every fuzz byte while ensuring curl's filtered file buffer is
40
/// non-null. curl currently drops comment lines before calling its lexer and
41
/// passes nullptr when that leaves an empty file; one leading newline is
42
/// parser-neutral but gives the lexer a valid empty string in that case.
43
/// @param data Fuzz-controlled NETRC bytes after the harness selector.
44
/// @param size Number of fuzz-controlled bytes to retain.
45
/// @return true when the complete prefixed input is ready to scan.
46
756
bool WriteNetrcInput(const std::uint8_t *data, std::size_t size) {
47
756
  if (size > kMaxNetrcBytes) {
48
3
    return false;
49
3
  }
50
51
753
  std::string file_contents(1, '\n');
52
753
  if (size != 0) {
53
750
    file_contents.append(reinterpret_cast<const char *>(data), size);
54
750
  }
55
753
  return kInputFile.Write(
56
753
      reinterpret_cast<const std::uint8_t *>(file_contents.data()),
57
753
      file_contents.size());
58
756
}
59
60
753
void ProbeErrorStringsOnce() {
61
753
  static const bool probed = [] {
62
6
    for (int value = 0; value < static_cast<int>(NETRC_LAST); ++value) {
63
5
      (void)Curl_netrc_strerror(static_cast<NETRCcode>(value));
64
5
    }
65
1
    return true;
66
1
  }();
67
753
  (void)probed;
68
753
}
69
70
} // namespace
71
72
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data,
73
756
                                      std::size_t size) {
74
756
  if (size == 0 || !WriteNetrcInput(data + 1, size - 1) ||
75
753
      kInputFile.path() == nullptr) {
76
3
    return 0;
77
3
  }
78
79
753
  ProbeErrorStringsOnce();
80
81
753
  CURL *easy = curl_easy_init();
82
753
  if (easy == nullptr) {
83
0
    return 0;
84
0
  }
85
86
  // Use curl's real private type so layout/API changes fail at compile time.
87
753
  struct store_netrc store;
88
753
  struct Curl_creds *creds = nullptr;
89
753
  Curl_netrc_init(&store);
90
91
753
  const char *hostname = (data[0] & 1U) != 0 ? "other.test" : "fuzz.test";
92
753
  const char *user = (data[0] & 2U) != 0 ? "fuzz-user" : nullptr;
93
753
  const NETRCcode result =
94
753
      Curl_netrc_scan(reinterpret_cast<struct Curl_easy *>(easy), &store,
95
753
                      hostname, user, kInputFile.path(), &creds);
96
753
  (void)Curl_netrc_strerror(result);
97
98
753
  Curl_creds_unlink(&creds);
99
753
  Curl_netrc_cleanup(&store);
100
753
  curl_easy_cleanup(easy);
101
753
  return 0;
102
753
}