/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); } |