Coverage Report

Created: 2026-10-15 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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