curl-fuzzer C++ reference
Loading...
Searching...
No Matches
allocation_fault_injection.cc
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
7#include "proto_fuzzer/allocation_fault_injection.h"
8
9#include <curl/curl.h>
10
11#include <algorithm>
12#include <array>
13#include <atomic>
14#include <cerrno>
15#include <cstdlib>
16#include <cstring>
17#include <mutex>
18
20
21namespace proto_fuzzer::allocation_fault_injection {
22namespace {
23
26struct ThreadState {
28 std::array<std::uint32_t, scenario_limits::kMaxAllocationFailures> ordinals{};
30 std::size_t ordinal_count = 0;
32 std::size_t next_ordinal = 0;
34 std::uint64_t allocation_attempts = 0;
36 std::uint64_t injected_failures = 0;
38 bool armed = false;
39};
40
42thread_local ThreadState state;
44std::once_flag initialize_once;
46bool callbacks_active = false;
48bool curl_initialized = false;
50std::atomic<bool> curl_initialization_claimed{false};
51
56std::uint32_t CanonicalOrdinal(std::uint32_t ordinal) noexcept {
57 if (ordinal == 0) {
58 return 0;
59 }
60 const std::uint32_t maximum = static_cast<std::uint32_t>(scenario_limits::kMaxAllocationOrdinal);
61 return 1U + ((ordinal - 1U) % maximum);
62}
63
68void InsertOrdinal(std::uint32_t ordinal) noexcept {
69 if (ordinal == 0) {
70 return;
71 }
72 const auto begin = state.ordinals.begin();
73 const auto end = begin + static_cast<std::ptrdiff_t>(state.ordinal_count);
74 const auto position = std::lower_bound(begin, end, ordinal);
75 if (position != end && *position == ordinal) {
76 return;
77 }
78 const std::size_t index = static_cast<std::size_t>(position - begin);
79 if (index >= state.ordinals.size()) {
80 return;
81 }
82 if (state.ordinal_count < state.ordinals.size()) {
83 ++state.ordinal_count;
84 }
85 for (std::size_t destination = state.ordinal_count - 1; destination > index; --destination) {
86 state.ordinals[destination] = state.ordinals[destination - 1];
87 }
88 state.ordinals[index] = ordinal;
89}
90
97bool ShouldFail(bool positive_size) noexcept {
98 if (!positive_size || !state.armed || internal::FailuresSuppressed()) {
99 return false;
100 }
101 ++state.allocation_attempts;
102 if (state.next_ordinal < state.ordinal_count && state.allocation_attempts == state.ordinals[state.next_ordinal]) {
103 ++state.next_ordinal;
104 ++state.injected_failures;
105 errno = ENOMEM;
106 return true;
107 }
108 return false;
109}
110
111} // namespace
112
119bool InitializeCurl() noexcept {
120 std::call_once(initialize_once, [] {
121 if (curl_global_init_mem(CURL_GLOBAL_ALL, internal::Malloc, internal::Free, internal::Realloc, internal::Strdup,
122 internal::Calloc) != CURLE_OK) {
123 return;
124 }
125 curl_initialized = true;
126
127 state.armed = true;
128 char* probe = curl_escape("allocator callback probe", 0);
129 callbacks_active = state.allocation_attempts != 0;
130 state.armed = false;
131 curl_free(probe);
132 state = ThreadState{};
133 });
134 return callbacks_active;
135}
136
140bool ReinitializeCurl() noexcept {
141 return curl_global_init_mem(CURL_GLOBAL_ALL, internal::Malloc, internal::Free, internal::Realloc, internal::Strdup,
142 internal::Calloc) == CURLE_OK;
143}
144
148bool CurlInitializationSucceeded() noexcept {
149 (void)InitializeCurl();
150 return curl_initialized;
151}
152
157bool ClaimCurlInitializationReference() noexcept {
158 (void)InitializeCurl();
159 if (!curl_initialized) {
160 return false;
161 }
162 bool expected = false;
163 return curl_initialization_claimed.compare_exchange_strong(expected, true, std::memory_order_relaxed,
164 std::memory_order_relaxed);
165}
166
173void Configure(const std::uint32_t* ordinals, std::size_t count) noexcept {
174 state = ThreadState{};
175 if (ordinals == nullptr) {
176 return;
177 }
178 for (std::size_t index = 0; index < count; ++index) {
179 InsertOrdinal(CanonicalOrdinal(ordinals[index]));
180 }
181}
182
186void Arm() noexcept {
187 state.next_ordinal = 0;
188 state.allocation_attempts = 0;
189 state.injected_failures = 0;
190 state.armed = state.ordinal_count != 0;
191}
192
196void ArmNextAllocationFailure() noexcept {
197 state = ThreadState{};
198 state.ordinals[0] = 1;
199 state.ordinal_count = 1;
200 state.armed = true;
201}
202
204void Disarm() noexcept { state.armed = false; }
205
209void Reset() noexcept {
210 state = ThreadState{};
211 internal::ResetFailureSuppression();
212}
213
216Statistics GetStatistics() noexcept {
217 return {state.allocation_attempts, state.injected_failures, state.ordinal_count,
218 state.armed && !internal::FailuresSuppressed()};
219}
220
221namespace internal {
222
224void* Malloc(std::size_t size) noexcept { return ShouldFail(size != 0) ? nullptr : std::malloc(size); }
225
227void Free(void* pointer) noexcept { std::free(pointer); }
228
231void* Realloc(void* pointer, std::size_t size) noexcept {
232 return ShouldFail(size != 0) ? nullptr : std::realloc(pointer, size);
233}
234
236char* Strdup(const char* string) noexcept {
237 if (string == nullptr) {
238 return nullptr;
239 }
240 const std::size_t length = std::strlen(string) + 1;
241 if (ShouldFail(length != 0)) {
242 return nullptr;
243 }
244 char* copy = static_cast<char*>(std::malloc(length));
245 if (copy != nullptr) {
246 std::memcpy(copy, string, length);
247 }
248 return copy;
249}
250
253void* Calloc(std::size_t count, std::size_t size) noexcept {
254 return ShouldFail(count != 0 && size != 0) ? nullptr : std::calloc(count, size);
255}
256
257} // namespace internal
258} // namespace proto_fuzzer::allocation_fault_injection
Shared complexity limits for structured scenarios.
Snapshot of the calling thread's current allocation-failure plan.