1 // Copyright 2021 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <grpc/event_engine/memory_allocator.h>
16 #include <grpc/event_engine/memory_request.h>
17 #include <grpc/support/log.h>
18 #include <stdint.h>
19 #include <sys/types.h>
20
21 #include <functional>
22 #include <limits>
23 #include <map>
24 #include <memory>
25 #include <utility>
26
27 #include "absl/status/status.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/types/optional.h"
30 #include "src/core/lib/debug/trace.h"
31 #include "src/core/lib/experiments/config.h"
32 #include "src/core/lib/iomgr/closure.h"
33 #include "src/core/lib/iomgr/error.h"
34 #include "src/core/lib/iomgr/exec_ctx.h"
35 #include "src/core/lib/resource_quota/memory_quota.h"
36 #include "src/core/util/debug_location.h"
37 #include "src/core/util/useful.h"
38 #include "src/libfuzzer/libfuzzer_macro.h"
39 #include "test/core/resource_quota/call_checker.h"
40 #include "test/core/resource_quota/memory_quota_fuzzer.pb.h"
41 #include "test/core/test_util/fuzz_config_vars.h"
42 #include "test/core/test_util/test_config.h"
43
44 bool squelch = true;
45 bool leak_check = true;
46
47 namespace grpc_core {
48 namespace testing {
49 namespace {
MapReclamationPass(memory_quota_fuzzer::Reclaimer::Pass pass)50 ReclamationPass MapReclamationPass(memory_quota_fuzzer::Reclaimer::Pass pass) {
51 switch (pass) {
52 case memory_quota_fuzzer::Reclaimer::BENIGN:
53 return ReclamationPass::kBenign;
54 case memory_quota_fuzzer::Reclaimer::IDLE:
55 return ReclamationPass::kIdle;
56 case memory_quota_fuzzer::Reclaimer::DESTRUCTIVE:
57 return ReclamationPass::kDestructive;
58 default:
59 return ReclamationPass::kBenign;
60 }
61 }
62
63 class Fuzzer {
64 public:
Run(const memory_quota_fuzzer::Msg & msg)65 void Run(const memory_quota_fuzzer::Msg& msg) {
66 ExecCtx exec_ctx;
67 RunMsg(msg);
68 do {
69 memory_quotas_.clear();
70 memory_allocators_.clear();
71 allocations_.clear();
72 exec_ctx.Flush();
73 } while (!memory_quotas_.empty() || !memory_allocators_.empty() ||
74 !allocations_.empty());
75 }
76
77 private:
RunMsg(const memory_quota_fuzzer::Msg & msg)78 void RunMsg(const memory_quota_fuzzer::Msg& msg) {
79 for (int i = 0; i < msg.actions_size(); ++i) {
80 const auto& action = msg.actions(i);
81 switch (action.action_type_case()) {
82 case memory_quota_fuzzer::Action::kFlushExecCtx:
83 ExecCtx::Get()->Flush();
84 break;
85 case memory_quota_fuzzer::Action::kCreateQuota:
86 memory_quotas_.emplace(action.quota(),
87 MemoryQuota(absl::StrCat("quota-step-", i)));
88 break;
89 case memory_quota_fuzzer::Action::kDeleteQuota:
90 memory_quotas_.erase(action.quota());
91 break;
92 case memory_quota_fuzzer::Action::kCreateAllocator:
93 WithQuota(action.quota(), [this, action](MemoryQuota* q) {
94 memory_allocators_.emplace(action.allocator(),
95 q->CreateMemoryOwner());
96 });
97 break;
98 case memory_quota_fuzzer::Action::kDeleteAllocator:
99 memory_allocators_.erase(action.allocator());
100 break;
101 case memory_quota_fuzzer::Action::kSetQuotaSize:
102 WithQuota(action.quota(), [action](MemoryQuota* q) {
103 q->SetSize(Clamp(action.set_quota_size(), uint64_t{0},
104 uint64_t{std::numeric_limits<ssize_t>::max()}));
105 });
106 break;
107 case memory_quota_fuzzer::Action::kCreateAllocation: {
108 auto min = action.create_allocation().min();
109 auto max = action.create_allocation().max();
110 if (min > max) break;
111 if (max > MemoryRequest::max_allowed_size()) break;
112 MemoryRequest req(min, max);
113 WithAllocator(
114 action.allocator(), [this, action, req](MemoryOwner* a) {
115 auto alloc = a->MakeReservation(req);
116 allocations_.emplace(action.allocation(), std::move(alloc));
117 });
118 } break;
119 case memory_quota_fuzzer::Action::kDeleteAllocation:
120 allocations_.erase(action.allocation());
121 break;
122 case memory_quota_fuzzer::Action::kPostReclaimer: {
123 std::function<void(absl::optional<ReclamationSweep>)> reclaimer;
124 auto cfg = action.post_reclaimer();
125 if (cfg.synchronous()) {
126 reclaimer = [this, cfg](absl::optional<ReclamationSweep>) {
127 RunMsg(cfg.msg());
128 };
129 } else {
130 reclaimer = [cfg, this](absl::optional<ReclamationSweep> sweep) {
131 struct Args {
132 absl::optional<ReclamationSweep> sweep;
133 memory_quota_fuzzer::Msg msg;
134 Fuzzer* fuzzer;
135 };
136 auto* args = new Args{std::move(sweep), cfg.msg(), this};
137 auto* closure = GRPC_CLOSURE_CREATE(
138 [](void* arg, grpc_error_handle) {
139 auto* args = static_cast<Args*>(arg);
140 args->fuzzer->RunMsg(args->msg);
141 delete args;
142 },
143 args, nullptr);
144 ExecCtx::Get()->Run(DEBUG_LOCATION, closure, absl::OkStatus());
145 };
146 auto pass = MapReclamationPass(cfg.pass());
147 WithAllocator(
148 action.allocator(), [pass, reclaimer](MemoryOwner* a) {
149 // ensure called exactly once
150 auto call_checker = CallChecker::MakeOptional();
151 a->PostReclaimer(pass,
152 [reclaimer, call_checker](
153 absl::optional<ReclamationSweep> sweep) {
154 call_checker->Called();
155 reclaimer(std::move(sweep));
156 });
157 });
158 }
159 } break;
160 case memory_quota_fuzzer::Action::ACTION_TYPE_NOT_SET:
161 break;
162 }
163 }
164 }
165
166 template <typename F>
WithQuota(int quota,F f)167 void WithQuota(int quota, F f) {
168 auto it = memory_quotas_.find(quota);
169 if (it == memory_quotas_.end()) return;
170 f(&it->second);
171 }
172
173 template <typename F>
WithAllocator(int allocator,F f)174 void WithAllocator(int allocator, F f) {
175 auto it = memory_allocators_.find(allocator);
176 if (it == memory_allocators_.end()) return;
177 f(&it->second);
178 }
179
180 std::map<int, MemoryQuota> memory_quotas_;
181 std::map<int, MemoryOwner> memory_allocators_;
182 std::map<int, MemoryAllocator::Reservation> allocations_;
183 };
184
185 } // namespace
186 } // namespace testing
187 } // namespace grpc_core
188
DEFINE_PROTO_FUZZER(const memory_quota_fuzzer::Msg & msg)189 DEFINE_PROTO_FUZZER(const memory_quota_fuzzer::Msg& msg) {
190 if (squelch) {
191 grpc_disable_all_absl_logs();
192 }
193 grpc_core::ApplyFuzzConfigVars(msg.config_vars());
194 grpc_core::TestOnlyReloadExperimentsFromConfigVariables();
195 gpr_log_verbosity_init();
196 grpc_tracer_init();
197 grpc_core::testing::Fuzzer().Run(msg);
198 }
199