1#include "internal.h"
2
3#include <cmdline.h>
4#include <global.h>
5#include <inject.h>
6#include <mem/alloc.h>
7#include <mem/alloc_or_die.h>
8#include <nightmare/perturb.h>
9#include <nightmare/record.h>
10#include <sch/sched.h>
11#include <string.h>
12#include <thread/apc.h>
13#include <thread/thread.h>
14#include <time/time.h>
15
16static struct nightmare_perturb_config perturb_configs[] = {
17 {.name = "migrator"}, {.name = "waker"}, {.name = "apc_spammer"},
18 {.name = "stutter"}, {.name = "alloc_pressure"}, {.name = "inject_armer"},
19};
20
21static struct nightmare_perturb_config *
22perturb_resolve_config(const char *path, size_t path_len) {
23 static const char prefix[] = "perturb.";
24 if (path_len <= sizeof(prefix) - 1 ||
25 strncmp(s1: path, s2: prefix, n: sizeof(prefix) - 1) != 0)
26 return NULL;
27
28 path += sizeof(prefix) - 1;
29 path_len -= sizeof(prefix) - 1;
30 for (size_t i = 0; i < sizeof(perturb_configs) / sizeof(perturb_configs[0]);
31 i++) {
32 if (strlen(str: perturb_configs[i].name) == path_len &&
33 strncmp(s1: perturb_configs[i].name, s2: path, n: path_len) == 0)
34 return &perturb_configs[i];
35 }
36 return NULL;
37}
38
39static void *perturb_interval_resolve(const char *path, size_t path_len) {
40 struct nightmare_perturb_config *config =
41 perturb_resolve_config(path, path_len);
42 return config && strcmp(str1: config->name, str2: "stutter") != 0 ? config : NULL;
43}
44
45static void *perturb_stutter_resolve(const char *path, size_t path_len) {
46 struct nightmare_perturb_config *config =
47 perturb_resolve_config(path, path_len);
48 return config && strcmp(str1: config->name, str2: "stutter") == 0 ? config : NULL;
49}
50
51CMDLINE_SCHEMA_DECLARE(
52 nightmare_perturb_interval, "nightmare", "perturb.<interval-svc>",
53 "Nightmare interval perturbation options", perturb_interval_resolve,
54 CMDLINE_SCHEMA_PROP(struct nightmare_perturb_config, interval_us,
55 .types = CMDLINE_TYPES(CMDLINE_TYPE_DURATION),
56 .range = RANGE(US_TO_NS(1), TIME_NS_MAX)));
57
58CMDLINE_SCHEMA_DECLARE(
59 nightmare_perturb_stutter, "nightmare", "perturb.stutter",
60 "Nightmare stutter perturbation options", perturb_stutter_resolve,
61 CMDLINE_SCHEMA_PROP(struct nightmare_perturb_config, period_ms,
62 .types = CMDLINE_TYPES(CMDLINE_TYPE_DURATION),
63 .range = RANGE(MS_TO_NS(1), TIME_NS_MAX)),
64 CMDLINE_SCHEMA_PROP(struct nightmare_perturb_config, gap_ms,
65 .types = CMDLINE_TYPES(CMDLINE_TYPE_DURATION),
66 .range = RANGE(MS_TO_NS(1), TIME_NS_MAX)));
67
68const struct nightmare_perturb_config *
69nightmare_perturb_config_lookup(const char *name) {
70 for (size_t i = 0; i < sizeof(perturb_configs) / sizeof(perturb_configs[0]);
71 i++) {
72 if (strcmp(str1: perturb_configs[i].name, str2: name) == 0)
73 return &perturb_configs[i];
74 }
75 return NULL;
76}
77
78static inline void nightmare_perturb_delay(time_us_t interval_us) {
79 thread_sleep_for_us(us: interval_us);
80}
81
82void nightmare_perturb_migrator(struct nightmare_ctx *ctx,
83 struct nightmare_worker *worker) {
84 const struct nightmare_perturb_config *cfg =
85 nightmare_perturb_config_lookup(name: "migrator");
86 time_us_t interval_us =
87 (cfg && cfg->interval_us) ? NS_TO_US(cfg->interval_us) : 200;
88
89 while (!nightmare_must_stop()) {
90 if (nightmare_must_park()) {
91 nightmare_park(worker);
92 continue;
93 }
94
95 if (ctx->worker_count > 0 && global.core_count > 1) {
96 size_t idx = nightmare_rand(rng: &worker->rng) % ctx->worker_count;
97 struct nightmare_worker *target_worker =
98 &nightmare_runtime.workers[idx];
99 struct thread *target_th =
100 atomic_load_explicit(&target_worker->th, memory_order_acquire);
101 if (target_th) {
102 uint64_t target_cpu =
103 nightmare_rand(rng: &worker->rng) % global.core_count;
104 thread_set_migration_target(t: target_th, new: (int64_t) target_cpu);
105 }
106 }
107
108 nightmare_perturb_delay(interval_us);
109 }
110}
111
112void nightmare_perturb_waker(struct nightmare_ctx *ctx,
113 struct nightmare_worker *worker) {
114 const struct nightmare_perturb_config *cfg =
115 nightmare_perturb_config_lookup(name: "waker");
116 time_us_t interval_us =
117 (cfg && cfg->interval_us) ? NS_TO_US(cfg->interval_us) : 250;
118
119 while (!nightmare_must_stop()) {
120 if (nightmare_must_park()) {
121 nightmare_park(worker);
122 continue;
123 }
124
125 if (ctx->worker_count > 0) {
126 size_t idx = nightmare_rand(rng: &worker->rng) % ctx->worker_count;
127 struct nightmare_worker *target_worker =
128 &nightmare_runtime.workers[idx];
129 struct thread *target_th =
130 atomic_load_explicit(&target_worker->th, memory_order_acquire);
131 if (target_th) {
132 thread_wake(t: target_th, reason: THREAD_WAKE_REASON_SLEEP_MANUAL,
133 prio: THREAD_PRIO_CLASS_TIMESHARE, NULL);
134 }
135 }
136
137 nightmare_perturb_delay(interval_us);
138 }
139}
140
141static void nightmare_apc_probe(void *arg) {
142 (void) arg;
143}
144
145void nightmare_perturb_apc_spammer(struct nightmare_ctx *ctx,
146 struct nightmare_worker *worker) {
147 const struct nightmare_perturb_config *cfg =
148 nightmare_perturb_config_lookup(name: "apc_spammer");
149 time_us_t interval_us =
150 (cfg && cfg->interval_us) ? NS_TO_US(cfg->interval_us) : 300;
151
152 while (!nightmare_must_stop()) {
153 if (nightmare_must_park()) {
154 nightmare_park(worker);
155 continue;
156 }
157
158 if (ctx->worker_count > 0) {
159 size_t idx = nightmare_rand(rng: &worker->rng) % ctx->worker_count;
160 struct nightmare_worker *target_worker =
161 &nightmare_runtime.workers[idx];
162 struct thread *target_th =
163 atomic_load_explicit(&target_worker->th, memory_order_acquire);
164 if (target_th && thread_get(obj: target_th)) {
165 struct apc *apc = apc_create();
166 if (apc) {
167 apc_init(a: apc, fn: nightmare_apc_probe, arg1: ctx, destroy: apc_destroy_free);
168 apc_enqueue(t: target_th, a: apc, type: APC_TYPE_KERNEL);
169 apc_put(a: apc);
170 }
171 thread_put(t: target_th);
172 }
173 }
174
175 nightmare_perturb_delay(interval_us);
176 }
177}
178
179void nightmare_perturb_stutter(struct nightmare_ctx *ctx,
180 struct nightmare_worker *worker) {
181 (void) worker;
182 const struct nightmare_perturb_config *cfg =
183 nightmare_perturb_config_lookup(name: "stutter");
184 time_ms_t period_ms =
185 (cfg && cfg->period_ms) ? NS_TO_MS(cfg->period_ms) : 100;
186 time_ms_t gap_ms = (cfg && cfg->gap_ms) ? NS_TO_MS(cfg->gap_ms) : 5;
187
188 while (!nightmare_must_stop()) {
189 thread_sleep_for_ms(ms: period_ms);
190 if (nightmare_must_stop())
191 break;
192
193 /* Request quiesce */
194 atomic_store_explicit(&nightmare_runtime.quiesce_requested, true,
195 memory_order_release);
196
197 /* Wait for all subject workers to park. */
198 time_ms_t deadline = time_get_ms() + (gap_ms ? gap_ms : 10);
199 bool all_subjects_parked = false;
200 while (time_get_ms() < deadline && !nightmare_must_stop()) {
201 all_subjects_parked = true;
202 for (size_t i = 0; i < ctx->worker_count; i++) {
203 if (!atomic_load_explicit(&nightmare_runtime.workers[i].parked,
204 memory_order_acquire)) {
205 all_subjects_parked = false;
206 break;
207 }
208 }
209 if (all_subjects_parked)
210 break;
211 scheduler_yield();
212 }
213
214 if (nightmare_must_stop()) {
215 nightmare_record_quiesce(record: &(struct nightmare_quiesce_record){
216 .result = "aborted",
217 .checks = 0,
218 });
219 atomic_store_explicit(&nightmare_runtime.quiesce_requested, false,
220 memory_order_release);
221 break;
222 }
223
224 struct nightmare_verdict verdict = NIGHTMARE_OK;
225 uint64_t checks = 0;
226 if (!all_subjects_parked) {
227 verdict = NIGHTMARE_FAIL(
228 "quiesce_timeout",
229 "not all subject workers parked before the stutter deadline");
230 } else if (ctx->nm && ctx->nm->ops && ctx->nm->ops->quiesce_check) {
231 verdict = ctx->nm->ops->quiesce_check(ctx);
232 checks = 1;
233 }
234
235 nightmare_record_quiesce(record: &(struct nightmare_quiesce_record){
236 .result = nightmare_result_string(result: verdict.result),
237 .checks = checks,
238 });
239
240 if (verdict.result != NIGHTMARE_RESULT_OK)
241 nightmare_publish_perturb_verdict(verdict);
242
243 /* Release the herd */
244 atomic_store_explicit(&nightmare_runtime.quiesce_requested, false,
245 memory_order_release);
246
247 if (verdict.result != NIGHTMARE_RESULT_OK) {
248 nightmare_publish_stop(reason: NM_STOP_FAIL);
249 break;
250 }
251 }
252}
253
254#define ALLOC_PRESSURE_SLOTS 64
255
256void nightmare_perturb_alloc_pressure(struct nightmare_ctx *ctx,
257 struct nightmare_worker *worker) {
258 (void) ctx;
259 const struct nightmare_perturb_config *cfg =
260 nightmare_perturb_config_lookup(name: "alloc_pressure");
261 time_us_t interval_us =
262 (cfg && cfg->interval_us) ? NS_TO_US(cfg->interval_us) : 200;
263
264 void *slots[ALLOC_PRESSURE_SLOTS] = {0};
265 size_t slot_sizes[ALLOC_PRESSURE_SLOTS] = {0};
266 uint32_t slot_patterns[ALLOC_PRESSURE_SLOTS] = {0};
267
268 while (!nightmare_must_stop()) {
269 if (nightmare_must_park()) {
270 nightmare_park(worker);
271 continue;
272 }
273
274 size_t slot = nightmare_rand(rng: &worker->rng) % ALLOC_PRESSURE_SLOTS;
275 if (slots[slot]) {
276 uint32_t *p = slots[slot];
277 size_t words = slot_sizes[slot] / sizeof(uint32_t);
278 uint32_t expected = slot_patterns[slot];
279 for (size_t w = 0; w < words; w++) {
280 if (p[w] != (expected ^ (uint32_t) w)) {
281 NIGHTMARE_FINDING(
282 "alloc_corruption",
283 "heap memory corruption in alloc_pressure slot");
284 break;
285 }
286 }
287 kfree(slots[slot]);
288 slots[slot] = NULL;
289 slot_sizes[slot] = 0;
290 slot_patterns[slot] = 0;
291 } else {
292 size_t size = 16 + (nightmare_rand(rng: &worker->rng) % 8176);
293 uint32_t pat = (uint32_t) nightmare_rand(rng: &worker->rng);
294 void *ptr = kmalloc(size, ALLOC_FLAGS_ZERO);
295 if (ptr) {
296 uint32_t *p = ptr;
297 size_t words = size / sizeof(uint32_t);
298 for (size_t w = 0; w < words; w++)
299 p[w] = pat ^ (uint32_t) w;
300 slots[slot] = ptr;
301 slot_sizes[slot] = size;
302 slot_patterns[slot] = pat;
303 }
304 }
305
306 nightmare_perturb_delay(interval_us);
307 }
308
309 for (size_t i = 0; i < ALLOC_PRESSURE_SLOTS; i++) {
310 if (slots[i]) {
311 kfree(slots[i]);
312 slots[i] = NULL;
313 }
314 }
315}
316
317void nightmare_perturb_inject_armer(struct nightmare_ctx *ctx,
318 struct nightmare_worker *worker) {
319 (void) ctx;
320 const struct nightmare_perturb_config *cfg =
321 nightmare_perturb_config_lookup(name: "inject_armer");
322 time_us_t interval_us =
323 (cfg && cfg->interval_us) ? NS_TO_US(cfg->interval_us) : 500;
324
325 size_t site_count = __ekernel_inject_sites - __skernel_inject_sites;
326 if (site_count == 0) {
327 while (!nightmare_must_stop()) {
328 if (nightmare_must_park()) {
329 nightmare_park(worker);
330 continue;
331 }
332 thread_sleep_for_ms(ms: 50);
333 }
334 return;
335 }
336
337 while (!nightmare_must_stop()) {
338 if (nightmare_must_park()) {
339 nightmare_park(worker);
340 continue;
341 }
342
343 size_t idx = nightmare_rand(rng: &worker->rng) % site_count;
344 struct inject_site *site = &__skernel_inject_sites[idx];
345 uint32_t seed = (uint32_t) nightmare_rand(rng: &worker->rng);
346 uint32_t nth = 1 + (nightmare_rand(rng: &worker->rng) % 10);
347
348 inject_arm(s: site, seed, nth);
349 nightmare_perturb_delay(interval_us);
350 inject_disarm(s: site);
351 }
352}
353
354static const struct nightmare_perturb_desc perturb_descs[] = {
355 {.name = "migrator", .thread = nightmare_perturb_migrator},
356 {.name = "waker", .thread = nightmare_perturb_waker},
357 {.name = "apc_spammer", .thread = nightmare_perturb_apc_spammer},
358 {.name = "stutter", .thread = nightmare_perturb_stutter},
359 {.name = "alloc_pressure", .thread = nightmare_perturb_alloc_pressure},
360 {.name = "inject_armer", .thread = nightmare_perturb_inject_armer},
361};
362
363const struct nightmare_perturb_desc *
364nightmare_perturb_lookup(const char *name) {
365 if (!name)
366 return NULL;
367 for (size_t i = 0; i < sizeof(perturb_descs) / sizeof(perturb_descs[0]);
368 i++) {
369 if (strcmp(str1: perturb_descs[i].name, str2: name) == 0)
370 return &perturb_descs[i];
371 }
372 return NULL;
373}
374