/src/curl_fuzzer/openssl_contract_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 | | #include <openssl/ssl.h> |
8 | | |
9 | | #include <array> |
10 | | #include <atomic> |
11 | | #include <cstddef> |
12 | | #include <cstdio> |
13 | | #include <cstdlib> |
14 | | #include <mutex> |
15 | | #include <type_traits> |
16 | | |
17 | | namespace { |
18 | | |
19 | | // SSL_get_error classifies a preceding I/O result for one SSL object. Keep |
20 | | // pending results keyed by that object so interleaved connections cannot |
21 | | // replace one another's classification state. |
22 | | struct PendingIoCall { |
23 | | const SSL *ssl = nullptr; |
24 | | const char *api = nullptr; |
25 | | long long result = 0; |
26 | | }; |
27 | | |
28 | | struct FatalConnection { |
29 | | const SSL *ssl = nullptr; |
30 | | const char *io_api = nullptr; |
31 | | long long io_result = 0; |
32 | | int ssl_error = SSL_ERROR_NONE; |
33 | | }; |
34 | | |
35 | | // Fixed tables keep allocation-failure fuzzing from disabling or perturbing |
36 | | // the monitor. |
37 | | constexpr std::size_t kMaximumTrackedConnections = 256; |
38 | | std::array<PendingIoCall, kMaximumTrackedConnections> pending_io_calls{}; |
39 | | std::mutex pending_io_calls_mutex; |
40 | | std::atomic<std::size_t> pending_io_call_count{0}; |
41 | | |
42 | | constexpr std::size_t kMaximumFatalConnections = kMaximumTrackedConnections; |
43 | | std::array<FatalConnection, kMaximumFatalConnections> fatal_connections{}; |
44 | | std::mutex fatal_connections_mutex; |
45 | | std::atomic<std::size_t> fatal_connection_count{0}; |
46 | | |
47 | | // Static libssl contains calls to public SSL APIs. --wrap redirects those |
48 | | // calls too, so calls made while a real OpenSSL function is running bypass the |
49 | | // monitor and cannot replace the application call being classified. |
50 | | thread_local unsigned real_call_depth = 0; |
51 | | |
52 | | class RealCallScope { |
53 | | public: |
54 | 0 | RealCallScope() { ++real_call_depth; } |
55 | 0 | ~RealCallScope() { --real_call_depth; } |
56 | | |
57 | | RealCallScope(const RealCallScope &) = delete; |
58 | | RealCallScope &operator=(const RealCallScope &) = delete; |
59 | | }; |
60 | | |
61 | 0 | bool IsRealCallActive() { return real_call_depth != 0; } |
62 | | |
63 | 0 | const char *ErrorName(int ssl_error) { |
64 | 0 | switch (ssl_error) { |
65 | 0 | case SSL_ERROR_SSL: |
66 | 0 | return "SSL_ERROR_SSL"; |
67 | 0 | case SSL_ERROR_SYSCALL: |
68 | 0 | return "SSL_ERROR_SYSCALL"; |
69 | 0 | default: |
70 | 0 | return "unknown fatal error"; |
71 | 0 | } |
72 | 0 | } |
73 | | |
74 | | [[noreturn]] void AbortContractViolation(const char *attempted_api, |
75 | 0 | const FatalConnection &fatal) { |
76 | 0 | std::fprintf(stderr, |
77 | 0 | "OpenSSL contract assertion failed: %s called on SSL %p " |
78 | 0 | "after %s returned %lld and SSL_get_error classified it as " |
79 | 0 | "%s; no further I/O or SSL_shutdown is allowed\n", |
80 | 0 | attempted_api, static_cast<const void *>(fatal.ssl), |
81 | 0 | fatal.io_api, fatal.io_result, ErrorName(fatal.ssl_error)); |
82 | 0 | std::fflush(stderr); |
83 | 0 | std::abort(); |
84 | 0 | } |
85 | | |
86 | 0 | [[noreturn]] void AbortPendingOverflow(const PendingIoCall &call) { |
87 | 0 | std::fprintf(stderr, |
88 | 0 | "OpenSSL contract assertion failed: cannot track SSL %p " |
89 | 0 | "after %s returned %lld; pending-I/O table is full\n", |
90 | 0 | static_cast<const void *>(call.ssl), call.api, call.result); |
91 | 0 | std::fflush(stderr); |
92 | 0 | std::abort(); |
93 | 0 | } |
94 | | |
95 | 0 | [[noreturn]] void AbortFatalOverflow(const PendingIoCall &call, int ssl_error) { |
96 | 0 | std::fprintf(stderr, |
97 | 0 | "OpenSSL contract assertion failed: cannot track SSL %p " |
98 | 0 | "after %s returned %lld and SSL_get_error classified it as " |
99 | 0 | "%s; fatal-connection table is full\n", |
100 | 0 | static_cast<const void *>(call.ssl), call.api, call.result, |
101 | 0 | ErrorName(ssl_error)); |
102 | 0 | std::fflush(stderr); |
103 | 0 | std::abort(); |
104 | 0 | } |
105 | | |
106 | 0 | PendingIoCall *FindPendingIoCall(const SSL *ssl) { |
107 | 0 | for (PendingIoCall &call : pending_io_calls) { |
108 | 0 | if (call.ssl == ssl) { |
109 | 0 | return &call; |
110 | 0 | } |
111 | 0 | } |
112 | 0 | return nullptr; |
113 | 0 | } |
114 | | |
115 | 0 | FatalConnection *FindFatalConnection(const SSL *ssl) { |
116 | 0 | for (FatalConnection &fatal : fatal_connections) { |
117 | 0 | if (fatal.ssl == ssl) { |
118 | 0 | return &fatal; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | return nullptr; |
122 | 0 | } |
123 | | |
124 | 0 | void CheckUsable(const SSL *ssl, const char *attempted_api) { |
125 | 0 | if (ssl == nullptr || |
126 | 0 | fatal_connection_count.load(std::memory_order_acquire) == 0) { |
127 | 0 | return; |
128 | 0 | } |
129 | | |
130 | 0 | FatalConnection fatal; |
131 | 0 | { |
132 | 0 | std::lock_guard<std::mutex> lock(fatal_connections_mutex); |
133 | 0 | const FatalConnection *tracked = FindFatalConnection(ssl); |
134 | 0 | if (tracked != nullptr) { |
135 | 0 | fatal = *tracked; |
136 | 0 | } |
137 | 0 | } |
138 | 0 | if (fatal.ssl != nullptr) { |
139 | 0 | AbortContractViolation(attempted_api, fatal); |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | 0 | void ForgetPendingIoCall(const SSL *ssl) { |
144 | 0 | if (ssl == nullptr || |
145 | 0 | pending_io_call_count.load(std::memory_order_acquire) == 0) { |
146 | 0 | return; |
147 | 0 | } |
148 | | |
149 | 0 | std::lock_guard<std::mutex> lock(pending_io_calls_mutex); |
150 | 0 | PendingIoCall *call = FindPendingIoCall(ssl); |
151 | 0 | if (call != nullptr) { |
152 | 0 | *call = PendingIoCall{}; |
153 | 0 | pending_io_call_count.fetch_sub(1, std::memory_order_release); |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | void RecordIoResult(const SSL *ssl, const char *api, long long result, |
158 | 0 | bool zero_is_non_error = false) { |
159 | 0 | if (ssl == nullptr) { |
160 | 0 | return; |
161 | 0 | } |
162 | 0 | if (result > 0 || (zero_is_non_error && result == 0)) { |
163 | 0 | ForgetPendingIoCall(ssl); |
164 | 0 | return; |
165 | 0 | } |
166 | | |
167 | 0 | PendingIoCall recorded; |
168 | 0 | recorded.ssl = ssl; |
169 | 0 | recorded.api = api; |
170 | 0 | recorded.result = result; |
171 | 0 | bool table_full = false; |
172 | 0 | bool added = false; |
173 | 0 | { |
174 | 0 | std::lock_guard<std::mutex> lock(pending_io_calls_mutex); |
175 | 0 | PendingIoCall *call = FindPendingIoCall(ssl); |
176 | 0 | if (call == nullptr) { |
177 | 0 | for (PendingIoCall &entry : pending_io_calls) { |
178 | 0 | if (entry.ssl == nullptr) { |
179 | 0 | call = &entry; |
180 | 0 | added = true; |
181 | 0 | break; |
182 | 0 | } |
183 | 0 | } |
184 | 0 | } |
185 | 0 | if (call != nullptr) { |
186 | 0 | *call = recorded; |
187 | 0 | if (added) { |
188 | 0 | pending_io_call_count.fetch_add(1, std::memory_order_release); |
189 | 0 | } |
190 | 0 | } else { |
191 | 0 | table_full = true; |
192 | 0 | } |
193 | 0 | } |
194 | 0 | if (table_full) { |
195 | 0 | AbortPendingOverflow(recorded); |
196 | 0 | } |
197 | 0 | } |
198 | | |
199 | 0 | void RecordSslError(const SSL *ssl, int io_result, int ssl_error) { |
200 | 0 | if (ssl == nullptr || |
201 | 0 | pending_io_call_count.load(std::memory_order_acquire) == 0) { |
202 | 0 | return; |
203 | 0 | } |
204 | | |
205 | 0 | PendingIoCall classified_call; |
206 | 0 | { |
207 | 0 | std::lock_guard<std::mutex> lock(pending_io_calls_mutex); |
208 | 0 | PendingIoCall *pending = FindPendingIoCall(ssl); |
209 | 0 | if (pending == nullptr || pending->result != io_result) { |
210 | 0 | return; |
211 | 0 | } |
212 | 0 | classified_call = *pending; |
213 | 0 | *pending = PendingIoCall{}; |
214 | 0 | pending_io_call_count.fetch_sub(1, std::memory_order_release); |
215 | 0 | } |
216 | 0 | if (ssl_error != SSL_ERROR_SSL && ssl_error != SSL_ERROR_SYSCALL) { |
217 | 0 | return; |
218 | 0 | } |
219 | | |
220 | 0 | bool table_full = false; |
221 | 0 | bool added = false; |
222 | 0 | { |
223 | 0 | std::lock_guard<std::mutex> lock(fatal_connections_mutex); |
224 | 0 | FatalConnection *fatal = FindFatalConnection(ssl); |
225 | 0 | if (fatal == nullptr) { |
226 | 0 | for (FatalConnection &entry : fatal_connections) { |
227 | 0 | if (entry.ssl == nullptr) { |
228 | 0 | fatal = &entry; |
229 | 0 | added = true; |
230 | 0 | break; |
231 | 0 | } |
232 | 0 | } |
233 | 0 | } |
234 | 0 | if (fatal != nullptr) { |
235 | 0 | fatal->ssl = ssl; |
236 | 0 | fatal->io_api = classified_call.api; |
237 | 0 | fatal->io_result = classified_call.result; |
238 | 0 | fatal->ssl_error = ssl_error; |
239 | 0 | if (added) { |
240 | 0 | fatal_connection_count.fetch_add(1, std::memory_order_release); |
241 | 0 | } |
242 | 0 | } else { |
243 | 0 | table_full = true; |
244 | 0 | } |
245 | 0 | } |
246 | 0 | if (table_full) { |
247 | 0 | AbortFatalOverflow(classified_call, ssl_error); |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | 0 | void ForgetConnection(const SSL *ssl) { |
252 | 0 | ForgetPendingIoCall(ssl); |
253 | 0 | if (ssl == nullptr || |
254 | 0 | fatal_connection_count.load(std::memory_order_acquire) == 0) { |
255 | 0 | return; |
256 | 0 | } |
257 | | |
258 | 0 | std::lock_guard<std::mutex> lock(fatal_connections_mutex); |
259 | 0 | FatalConnection *fatal = FindFatalConnection(ssl); |
260 | 0 | if (fatal != nullptr) { |
261 | 0 | *fatal = FatalConnection{}; |
262 | 0 | fatal_connection_count.fetch_sub(1, std::memory_order_release); |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | | } // namespace |
267 | | |
268 | | // GNU ld and lld provide each __real_* symbol when the final link uses the |
269 | | // matching --wrap option. decltype ties these declarations to the installed |
270 | | // OpenSSL headers and makes signature drift a compile-time failure. |
271 | | extern "C" decltype(SSL_new) __real_SSL_new; |
272 | | extern "C" decltype(SSL_free) __real_SSL_free; |
273 | | extern "C" decltype(SSL_clear) __real_SSL_clear; |
274 | | extern "C" decltype(SSL_get_error) __real_SSL_get_error; |
275 | | extern "C" decltype(SSL_accept) __real_SSL_accept; |
276 | | extern "C" decltype(SSL_connect) __real_SSL_connect; |
277 | | extern "C" decltype(SSL_do_handshake) __real_SSL_do_handshake; |
278 | | extern "C" decltype(SSL_read) __real_SSL_read; |
279 | | extern "C" decltype(SSL_read_ex) __real_SSL_read_ex; |
280 | | extern "C" decltype(SSL_read_early_data) __real_SSL_read_early_data; |
281 | | extern "C" decltype(SSL_peek) __real_SSL_peek; |
282 | | extern "C" decltype(SSL_peek_ex) __real_SSL_peek_ex; |
283 | | extern "C" decltype(SSL_sendfile) __real_SSL_sendfile; |
284 | | extern "C" decltype(SSL_write) __real_SSL_write; |
285 | | extern "C" decltype(SSL_write_ex) __real_SSL_write_ex; |
286 | | extern "C" decltype(SSL_write_ex2) __real_SSL_write_ex2; |
287 | | extern "C" decltype(SSL_write_early_data) __real_SSL_write_early_data; |
288 | | extern "C" decltype(SSL_shutdown) __real_SSL_shutdown; |
289 | | extern "C" decltype(SSL_shutdown_ex) __real_SSL_shutdown_ex; |
290 | | |
291 | 0 | extern "C" SSL *__wrap_SSL_new(SSL_CTX *ctx) { |
292 | 0 | if (IsRealCallActive()) { |
293 | 0 | return __real_SSL_new(ctx); |
294 | 0 | } |
295 | 0 | SSL *ssl; |
296 | 0 | { |
297 | 0 | RealCallScope real_call; |
298 | 0 | ssl = __real_SSL_new(ctx); |
299 | 0 | } |
300 | | // OpenSSL's allocator may reuse a recently freed address. |
301 | 0 | ForgetConnection(ssl); |
302 | 0 | return ssl; |
303 | 0 | } |
304 | | static_assert(std::is_same<decltype(SSL_new), decltype(__wrap_SSL_new)>::value, |
305 | | "SSL_new signature changed"); |
306 | | |
307 | 0 | extern "C" void __wrap_SSL_free(SSL *ssl) { |
308 | 0 | if (IsRealCallActive()) { |
309 | 0 | __real_SSL_free(ssl); |
310 | 0 | return; |
311 | 0 | } |
312 | 0 | ForgetConnection(ssl); |
313 | 0 | RealCallScope real_call; |
314 | 0 | __real_SSL_free(ssl); |
315 | 0 | } |
316 | | static_assert( |
317 | | std::is_same<decltype(SSL_free), decltype(__wrap_SSL_free)>::value, |
318 | | "SSL_free signature changed"); |
319 | | |
320 | 0 | extern "C" int __wrap_SSL_clear(SSL *ssl) { |
321 | 0 | if (IsRealCallActive()) { |
322 | 0 | return __real_SSL_clear(ssl); |
323 | 0 | } |
324 | 0 | int result; |
325 | 0 | { |
326 | 0 | RealCallScope real_call; |
327 | 0 | result = __real_SSL_clear(ssl); |
328 | 0 | } |
329 | 0 | if (result == 1) { |
330 | | // A successful clear starts a new connection on the same SSL object. |
331 | 0 | ForgetConnection(ssl); |
332 | 0 | } |
333 | 0 | return result; |
334 | 0 | } |
335 | | static_assert( |
336 | | std::is_same<decltype(SSL_clear), decltype(__wrap_SSL_clear)>::value, |
337 | | "SSL_clear signature changed"); |
338 | | |
339 | 0 | extern "C" int __wrap_SSL_get_error(const SSL *ssl, int io_result) { |
340 | 0 | if (IsRealCallActive()) { |
341 | 0 | return __real_SSL_get_error(ssl, io_result); |
342 | 0 | } |
343 | 0 | int ssl_error; |
344 | 0 | { |
345 | 0 | RealCallScope real_call; |
346 | 0 | ssl_error = __real_SSL_get_error(ssl, io_result); |
347 | 0 | } |
348 | 0 | RecordSslError(ssl, io_result, ssl_error); |
349 | 0 | return ssl_error; |
350 | 0 | } |
351 | | static_assert(std::is_same<decltype(SSL_get_error), |
352 | | decltype(__wrap_SSL_get_error)>::value, |
353 | | "SSL_get_error signature changed"); |
354 | | |
355 | | #define DEFINE_IO_WRAPPER_WITH_POLICY(name, parameters, arguments, \ |
356 | | zero_is_non_error) \ |
357 | | extern "C" auto __wrap_##name parameters \ |
358 | 0 | ->decltype(__real_##name arguments) { \ |
359 | 0 | if (IsRealCallActive()) { \ |
360 | 0 | return __real_##name arguments; \ |
361 | 0 | } \ |
362 | 0 | CheckUsable(ssl, #name); \ |
363 | 0 | decltype(__real_##name arguments) result; \ |
364 | 0 | { \ |
365 | 0 | RealCallScope real_call; \ |
366 | 0 | result = __real_##name arguments; \ |
367 | 0 | } \ |
368 | 0 | RecordIoResult(ssl, #name, static_cast<long long>(result), \ |
369 | 0 | zero_is_non_error); \ |
370 | 0 | return result; \ |
371 | 0 | } \ Unexecuted instantiation: __wrap_SSL_accept Unexecuted instantiation: __wrap_SSL_connect Unexecuted instantiation: __wrap_SSL_do_handshake Unexecuted instantiation: __wrap_SSL_read Unexecuted instantiation: __wrap_SSL_read_ex Unexecuted instantiation: __wrap_SSL_read_early_data Unexecuted instantiation: __wrap_SSL_peek Unexecuted instantiation: __wrap_SSL_peek_ex Unexecuted instantiation: __wrap_SSL_sendfile Unexecuted instantiation: __wrap_SSL_write Unexecuted instantiation: __wrap_SSL_write_ex Unexecuted instantiation: __wrap_SSL_write_ex2 Unexecuted instantiation: __wrap_SSL_write_early_data Unexecuted instantiation: __wrap_SSL_shutdown Unexecuted instantiation: __wrap_SSL_shutdown_ex |
372 | | static_assert(std::is_same<decltype(name), decltype(__wrap_##name)>::value, \ |
373 | | #name " signature changed"); |
374 | | |
375 | | #define DEFINE_IO_WRAPPER(name, parameters, arguments) \ |
376 | | DEFINE_IO_WRAPPER_WITH_POLICY(name, parameters, arguments, false) |
377 | | |
378 | | #define DEFINE_ZERO_NONERROR_WRAPPER(name, parameters, arguments) \ |
379 | | DEFINE_IO_WRAPPER_WITH_POLICY(name, parameters, arguments, true) |
380 | | |
381 | | DEFINE_IO_WRAPPER(SSL_accept, (SSL * ssl), (ssl)) |
382 | | DEFINE_IO_WRAPPER(SSL_connect, (SSL * ssl), (ssl)) |
383 | | DEFINE_IO_WRAPPER(SSL_do_handshake, (SSL * ssl), (ssl)) |
384 | | DEFINE_IO_WRAPPER(SSL_read, (SSL * ssl, void *buffer, int length), |
385 | | (ssl, buffer, length)) |
386 | | DEFINE_IO_WRAPPER(SSL_read_ex, |
387 | | (SSL * ssl, void *buffer, size_t length, size_t *readbytes), |
388 | | (ssl, buffer, length, readbytes)) |
389 | | DEFINE_IO_WRAPPER(SSL_read_early_data, |
390 | | (SSL * ssl, void *buffer, size_t length, size_t *readbytes), |
391 | | (ssl, buffer, length, readbytes)) |
392 | | DEFINE_IO_WRAPPER(SSL_peek, (SSL * ssl, void *buffer, int length), |
393 | | (ssl, buffer, length)) |
394 | | DEFINE_IO_WRAPPER(SSL_peek_ex, |
395 | | (SSL * ssl, void *buffer, size_t length, size_t *readbytes), |
396 | | (ssl, buffer, length, readbytes)) |
397 | | DEFINE_ZERO_NONERROR_WRAPPER(SSL_sendfile, |
398 | | (SSL * ssl, int fd, off_t offset, size_t size, |
399 | | int flags), |
400 | | (ssl, fd, offset, size, flags)) |
401 | | DEFINE_IO_WRAPPER(SSL_write, (SSL * ssl, const void *buffer, int length), |
402 | | (ssl, buffer, length)) |
403 | | DEFINE_IO_WRAPPER(SSL_write_ex, |
404 | | (SSL * ssl, const void *buffer, size_t length, |
405 | | size_t *written), |
406 | | (ssl, buffer, length, written)) |
407 | | DEFINE_IO_WRAPPER(SSL_write_ex2, |
408 | | (SSL * ssl, const void *buffer, size_t length, uint64_t flags, |
409 | | size_t *written), |
410 | | (ssl, buffer, length, flags, written)) |
411 | | DEFINE_IO_WRAPPER(SSL_write_early_data, |
412 | | (SSL * ssl, const void *buffer, size_t length, |
413 | | size_t *written), |
414 | | (ssl, buffer, length, written)) |
415 | | DEFINE_ZERO_NONERROR_WRAPPER(SSL_shutdown, (SSL * ssl), (ssl)) |
416 | | DEFINE_ZERO_NONERROR_WRAPPER(SSL_shutdown_ex, |
417 | | (SSL * ssl, uint64_t flags, |
418 | | const SSL_SHUTDOWN_EX_ARGS *args, |
419 | | size_t args_length), |
420 | | (ssl, flags, args, args_length)) |
421 | | |
422 | | #undef DEFINE_ZERO_NONERROR_WRAPPER |
423 | | #undef DEFINE_IO_WRAPPER |
424 | | #undef DEFINE_IO_WRAPPER_WITH_POLICY |