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