/src/curl_fuzzer/proto_fuzzer/allocation_fault_injection.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
3 | | * |
4 | | * SPDX-License-Identifier: curl |
5 | | */ |
6 | | |
7 | | #ifndef PROTO_FUZZER_ALLOCATION_FAULT_INJECTION_H_ |
8 | | #define PROTO_FUZZER_ALLOCATION_FAULT_INJECTION_H_ |
9 | | |
10 | | #include <cstddef> |
11 | | #include <cstdint> |
12 | | |
13 | | namespace proto_fuzzer::allocation_fault_injection { |
14 | | |
15 | | namespace internal { |
16 | | |
17 | | /// Nesting depth for harness-only allocations excluded from fault injection. |
18 | | inline thread_local std::size_t failure_suppression_depth = 0; |
19 | | /// Invalidates live guards when Reset() repairs state after a non-local exit. |
20 | | inline thread_local std::uint64_t failure_suppression_generation = 0; |
21 | | |
22 | | /// Enter one nestable suppression scope on the calling thread. |
23 | | /// @return Generation captured by the matching scope guard. |
24 | 225k | inline std::uint64_t BeginFailureSuppression() noexcept { |
25 | 225k | ++failure_suppression_depth; |
26 | 225k | return failure_suppression_generation; |
27 | 225k | } |
28 | | |
29 | | /// Leave a suppression scope unless Reset() already invalidated its guard. |
30 | | /// @param generation Generation captured when the guard was constructed. |
31 | 225k | inline void EndFailureSuppression(std::uint64_t generation) noexcept { |
32 | 225k | if (generation == failure_suppression_generation && failure_suppression_depth != 0) { |
33 | 225k | --failure_suppression_depth; |
34 | 225k | } |
35 | 225k | } |
36 | | |
37 | | /// @return Whether injected failures are suppressed on the calling thread. |
38 | 5.55M | inline bool FailuresSuppressed() noexcept { return failure_suppression_depth != 0; } |
39 | | |
40 | | /// Clear leaked suppression after a standalone-runner non-local exit. |
41 | 486k | inline void ResetFailureSuppression() noexcept { |
42 | 486k | failure_suppression_depth = 0; |
43 | 486k | ++failure_suppression_generation; |
44 | 486k | } |
45 | | |
46 | | } // namespace internal |
47 | | |
48 | | /** |
49 | | * Exclude a narrow harness-only operation from the allocation-failure plan. |
50 | | * |
51 | | * Scopes nest on the calling thread. Positive-size libcurl allocations made |
52 | | * inside them bypass injected failures without advancing the active ordinal |
53 | | * sequence, so failures remain pending for subsequent injectable allocations. |
54 | | * Construct and destroy each guard on the same thread. Reset() invalidates any |
55 | | * live guards after a non-local exit and makes their later destruction a no-op. |
56 | | */ |
57 | | class ScopedFailureSuppression final { |
58 | | public: |
59 | 225k | ScopedFailureSuppression() noexcept : generation_(internal::BeginFailureSuppression()) {} |
60 | 225k | ~ScopedFailureSuppression() { internal::EndFailureSuppression(generation_); } |
61 | | |
62 | | ScopedFailureSuppression(const ScopedFailureSuppression&) = delete; |
63 | | ScopedFailureSuppression& operator=(const ScopedFailureSuppression&) = delete; |
64 | | ScopedFailureSuppression(ScopedFailureSuppression&&) = delete; |
65 | | ScopedFailureSuppression& operator=(ScopedFailureSuppression&&) = delete; |
66 | | |
67 | | private: |
68 | | /// Suppression generation used to make destruction safe across Reset(). |
69 | | std::uint64_t generation_; |
70 | | }; |
71 | | |
72 | | /** Snapshot of the calling thread's current allocation-failure plan. */ |
73 | | struct Statistics { |
74 | | /// Positive-size allocation calls observed since the last Arm(). |
75 | | std::uint64_t allocation_attempts = 0; |
76 | | /// Selected calls rejected since the last Arm(). |
77 | | std::uint64_t injected_failures = 0; |
78 | | /// Distinct ordinals retained in the bounded plan. |
79 | | std::size_t configured_failures = 0; |
80 | | /// Whether allocation calls on this thread are currently observed. |
81 | | bool armed = false; |
82 | | }; |
83 | | |
84 | | bool InitializeCurl() noexcept; |
85 | | bool ReinitializeCurl() noexcept; |
86 | | bool CurlInitializationSucceeded() noexcept; |
87 | | bool ClaimCurlInitializationReference() noexcept; |
88 | | void Configure(const std::uint32_t* ordinals, std::size_t count) noexcept; |
89 | | void Arm() noexcept; |
90 | | void Disarm() noexcept; |
91 | | void Reset() noexcept; |
92 | | Statistics GetStatistics() noexcept; |
93 | | |
94 | | // These are the process-wide callbacks passed to curl_global_init_mem. They |
95 | | // remain visible so the focused unit test can verify the allocator contracts |
96 | | // without depending on a particular libcurl call's private allocation trace. |
97 | | namespace internal { |
98 | | |
99 | | void* Malloc(std::size_t size) noexcept; |
100 | | void Free(void* pointer) noexcept; |
101 | | void* Realloc(void* pointer, std::size_t size) noexcept; |
102 | | char* Strdup(const char* string) noexcept; |
103 | | void* Calloc(std::size_t count, std::size_t size) noexcept; |
104 | | |
105 | | } // namespace internal |
106 | | } // namespace proto_fuzzer::allocation_fault_injection |
107 | | |
108 | | #endif // PROTO_FUZZER_ALLOCATION_FAULT_INJECTION_H_ |