Coverage Report

Created: 2026-10-28 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/resolver_getaddrinfo_shim.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 Target-local deterministic getaddrinfo backend for resolver fuzzing.
9
10
#include <arpa/inet.h>
11
#include <netdb.h>
12
#include <netinet/in.h>
13
#include <sys/socket.h>
14
15
#include <cerrno>
16
#include <chrono>
17
#include <cstdint>
18
#include <cstdlib>
19
#include <cstring>
20
#include <thread>
21
22
#include "proto_fuzzer/resolver_cancel_gate.h"
23
24
namespace {
25
26
constexpr char kPositiveHost[] = "resolver-positive.test";
27
constexpr char kPartialHost[] = "resolver-partial.test";
28
constexpr char kNegativeHost[] = "resolver-negative.test";
29
constexpr char kLinkLocalHost[] = "resolver-link-local.test";
30
constexpr char kQueuedCancelHost[] = "resolver-queued-cancel.test";
31
32
1.27k
unsigned short ParsePort(const char* service) {
33
1.27k
  if (service == nullptr || *service == '\0') {
34
0
    return 0;
35
0
  }
36
1.27k
  char* end = nullptr;
37
1.27k
  errno = 0;
38
1.27k
  const unsigned long value = std::strtoul(service, &end, 10);
39
1.27k
  if (errno != 0 || end == service || *end != '\0' || value > UINT16_MAX) {
40
0
    return 0;
41
0
  }
42
1.27k
  return static_cast<unsigned short>(value);
43
1.27k
}
44
45
2.42k
addrinfo* AllocateAddress(int family, const addrinfo* hints, unsigned short port, const void* address) {
46
2.42k
  auto* node = static_cast<addrinfo*>(std::calloc(1, sizeof(addrinfo)));
47
2.42k
  if (node == nullptr) {
48
0
    return nullptr;
49
0
  }
50
51
2.42k
  const std::size_t address_size = family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
52
2.42k
  auto* socket_address = static_cast<sockaddr*>(std::calloc(1, address_size));
53
2.42k
  if (socket_address == nullptr) {
54
0
    std::free(node);
55
0
    return nullptr;
56
0
  }
57
58
2.42k
  node->ai_flags = hints == nullptr ? 0 : hints->ai_flags;
59
2.42k
  node->ai_family = family;
60
2.42k
  node->ai_socktype = hints == nullptr || hints->ai_socktype == 0 ? SOCK_STREAM : hints->ai_socktype;
61
2.42k
  node->ai_protocol = hints == nullptr ? 0 : hints->ai_protocol;
62
2.42k
  node->ai_addrlen = static_cast<socklen_t>(address_size);
63
2.42k
  node->ai_addr = socket_address;
64
65
2.42k
  if (family == AF_INET) {
66
1.38k
    auto* ipv4 = reinterpret_cast<sockaddr_in*>(socket_address);
67
1.38k
    ipv4->sin_family = AF_INET;
68
1.38k
    ipv4->sin_port = htons(port);
69
1.38k
    std::memcpy(&ipv4->sin_addr, address, sizeof(ipv4->sin_addr));
70
1.38k
  } else {
71
1.03k
    auto* ipv6 = reinterpret_cast<sockaddr_in6*>(socket_address);
72
1.03k
    ipv6->sin6_family = AF_INET6;
73
1.03k
    ipv6->sin6_port = htons(port);
74
1.03k
    std::memcpy(&ipv6->sin6_addr, address, sizeof(ipv6->sin6_addr));
75
1.03k
  }
76
2.42k
  return node;
77
2.42k
}
78
79
bool AppendAddress(addrinfo** head, addrinfo** tail, int family, const addrinfo* hints, unsigned short port,
80
2.42k
                   const char* address) {
81
2.42k
  in_addr ipv4{};
82
2.42k
  in6_addr ipv6{};
83
2.42k
  const void* bytes = nullptr;
84
2.42k
  if (family == AF_INET) {
85
1.38k
    if (inet_pton(AF_INET, address, &ipv4) != 1) {
86
0
      return false;
87
0
    }
88
1.38k
    bytes = &ipv4;
89
1.38k
  } else {
90
1.03k
    if (inet_pton(AF_INET6, address, &ipv6) != 1) {
91
0
      return false;
92
0
    }
93
1.03k
    bytes = &ipv6;
94
1.03k
  }
95
96
2.42k
  addrinfo* node = AllocateAddress(family, hints, port, bytes);
97
2.42k
  if (node == nullptr) {
98
0
    return false;
99
0
  }
100
2.42k
  if (*tail == nullptr) {
101
1.27k
    *head = node;
102
1.27k
  } else {
103
1.15k
    (*tail)->ai_next = node;
104
1.15k
  }
105
2.42k
  *tail = node;
106
2.42k
  return true;
107
2.42k
}
108
109
1.27k
void FreeAddressList(addrinfo* address) noexcept {
110
3.69k
  while (address != nullptr) {
111
2.42k
    addrinfo* next = address->ai_next;
112
2.42k
    std::free(address->ai_addr);
113
2.42k
    std::free(address->ai_canonname);
114
2.42k
    std::free(address);
115
2.42k
    address = next;
116
2.42k
  }
117
1.27k
}
118
119
}  // namespace
120
121
/// Replace libc name service only in curl_fuzzer_proto_resolver. The fixed
122
/// hostnames are installed by target policy, so no mutation or ambient
123
/// resolver configuration can select another destination.
124
/// @param nodename Hostname selected by the fixed resolver target policy.
125
/// @param service Decimal service port, or null when no port is requested.
126
/// @param hints Requested address family and socket properties, or null.
127
/// @param[out] result Newly allocated address chain on success.
128
/// @return Zero on success, otherwise the matching EAI_* resolver error.
129
1.34k
extern "C" int getaddrinfo(const char* nodename, const char* service, const addrinfo* hints, addrinfo** result) {
130
1.34k
  if (result == nullptr) {
131
0
    return EAI_FAIL;
132
0
  }
133
1.34k
  *result = nullptr;
134
1.34k
  if (nodename == nullptr) {
135
0
    return EAI_NONAME;
136
0
  }
137
138
1.34k
  const bool negative = std::strcmp(nodename, kNegativeHost) == 0;
139
140
1.34k
  const bool positive = std::strcmp(nodename, kPositiveHost) == 0;
141
1.34k
  const bool partial = std::strcmp(nodename, kPartialHost) == 0;
142
1.34k
  const bool link_local = std::strcmp(nodename, kLinkLocalHost) == 0;
143
1.34k
  const bool queued_cancel = std::strcmp(nodename, kQueuedCancelHost) == 0;
144
1.34k
  if (!positive && !negative && !partial && !link_local && !queued_cancel) {
145
0
    return EAI_NONAME;
146
0
  }
147
148
  // This hostname is selected only by the queued-cancellation resolver mode.
149
  // The main thread waits until the first handle's first family is stopped
150
  // here, then adds and starts a duplicate handle. With the public resolver
151
  // pool capped at one worker, no later family can be dequeued before the
152
  // duplicate's pending resolver work is cancelled.
153
1.34k
  if (queued_cancel) {
154
325
    proto_fuzzer::WaitAtResolverCancelGate();
155
325
  }
156
157
  // Hold every owned answer for one bounded interval so curl's first public
158
  // poll observes live resolver work. Unlike a scheduler race or debug-only
159
  // environment hook, this is part of the target-local backend contract.
160
1.34k
  std::this_thread::sleep_for(std::chrono::milliseconds(1));
161
1.34k
  if (negative) {
162
70
    return EAI_NONAME;
163
70
  }
164
165
1.27k
  const int family = hints == nullptr ? AF_UNSPEC : hints->ai_family;
166
1.27k
  if (family != AF_UNSPEC && family != AF_INET && family != AF_INET6) {
167
0
    return EAI_FAMILY;
168
0
  }
169
170
1.27k
  const unsigned short port = ParsePort(service);
171
1.27k
  if (partial && family == AF_INET) {
172
    // Keep the A answer behind the AAAA answer by a small, fixed interval.
173
    // curl can then consume the first asynchronous family through its live
174
    // resolver accessor before the complete result moves into the DNS cache.
175
232
    std::this_thread::sleep_for(std::chrono::milliseconds(4));
176
232
  }
177
1.27k
  addrinfo* head = nullptr;
178
1.27k
  addrinfo* tail = nullptr;
179
1.27k
  bool ok = true;
180
1.27k
  if (positive || partial || queued_cancel) {
181
1.03k
    if (family == AF_UNSPEC || family == AF_INET6) {
182
399
      ok = AppendAddress(&head, &tail, AF_INET6, hints, port, "::1") &&
183
399
           AppendAddress(&head, &tail, AF_INET6, hints, port, "::2");
184
399
    }
185
1.03k
    if (ok && (family == AF_UNSPEC || family == AF_INET)) {
186
635
      ok = AppendAddress(&head, &tail, AF_INET, hints, port, "127.0.0.1") &&
187
635
           AppendAddress(&head, &tail, AF_INET, hints, port, "127.0.0.2");
188
635
    }
189
1.03k
  } else if (family == AF_INET6) {
190
120
    ok = AppendAddress(&head, &tail, AF_INET6, hints, port, "fe80::1");
191
120
  } else if (family == AF_UNSPEC) {
192
117
    ok = AppendAddress(&head, &tail, AF_INET6, hints, port, "fe80::1") &&
193
117
         AppendAddress(&head, &tail, AF_INET, hints, port, "127.0.0.1");
194
117
  } else {
195
0
    ok = AppendAddress(&head, &tail, AF_INET, hints, port, "127.0.0.1");
196
0
  }
197
198
1.27k
  if (!ok || head == nullptr) {
199
0
    FreeAddressList(head);
200
0
    return EAI_MEMORY;
201
0
  }
202
1.27k
  *result = head;
203
1.27k
  return 0;
204
1.27k
}
205
206
/// Match the ownership contract of the target-local getaddrinfo definition.
207
/// @param address Address chain returned by the target-local getaddrinfo.
208
1.27k
extern "C" void freeaddrinfo(addrinfo* address) noexcept { FreeAddressList(address); }