-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathchild.c
More file actions
1472 lines (1305 loc) · 47.6 KB
/
child.c
File metadata and controls
1472 lines (1305 loc) · 47.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Each process that gets forked runs this code.
*/
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include "arch.h"
#include "child.h"
#include "fd.h"
#include "futex.h"
#include "fd-event.h"
#include "kcov.h"
#include "list.h"
#include "maps.h"
#include "params.h"
#include "pids.h"
#include "pre_crash_ring.h"
#include "random.h"
#include "shm.h"
#include "signals.h"
#include "stats.h"
#include "syscall.h"
#include "tables.h"
#include "trinity.h" // ARRAY_SIZE
#include "uid.h"
#include "deferred-free.h"
#include "sanitise.h"
#include "sequence.h"
#include "utils.h" // zmalloc
/*
* Pin op_nr — the trailing field of the per-syscall hot block — to an
* offset under 64 so a future field reorder that moves any of the hot
* block (kcov, last_syscall_nr, last_group, op_nr, local_op_count) past
* the leading cacheline boundary fails the build instead of silently
* regressing the per-call cache-miss budget the layout was tuned for.
*/
_Static_assert(offsetof(struct childdata, op_nr) < 64,
"struct childdata: op_nr (per-syscall hot field) escaped the leading cacheline");
/* Set to true once we detect that unprivileged pidns isn't available.
* Lives in shared memory (shm->no_pidns) so the flag propagates across
* fork() — see init_child() below. */
/*
* Provide temporary immunity from the reaper
* This is useful if we're going to do something that might take
* longer than the time the reaper is prepared to wait, especially if
* we're doing something critical, like handling a lock, or dumping a log.
*/
void set_dontkillme(struct childdata *child, bool state)
{
if (child == NULL) /* possible, we might be the mainpid */
return;
child->dontkillme = state;
/* bump the progress indicator */
clock_gettime(CLOCK_MONOTONIC, &child->tp);
}
void child_fd_ring_push(struct child_fd_ring *ring, int fd)
{
ring->fds[ring->head % CHILD_FD_RING_SIZE] = fd;
ring->head++;
}
/*
* Single-producer push: extract the structured fields the post-mortem
* reader consumes into the chronicle slot, then publish the new head
* with a release-store so the reader observes a fully-written entry
* when it sees the matching head value. Field-by-field instead of a
* struct copy because struct syscallrecord is dominated by the 4 KiB
* pre-rendered prebuffer the post-mortem path doesn't need.
*/
void child_syscall_ring_push(struct child_syscall_ring *ring,
const struct syscallrecord *rec)
{
struct chronicle_slot *slot;
uint32_t head;
head = atomic_load_explicit(&ring->head, memory_order_relaxed);
slot = &ring->recent[head & (CHILD_SYSCALL_RING_SIZE - 1)];
slot->tp = rec->tp;
slot->a1 = rec->a1;
slot->a2 = rec->a2;
slot->a3 = rec->a3;
slot->a4 = rec->a4;
slot->a5 = rec->a5;
slot->a6 = rec->a6;
slot->retval = rec->retval;
slot->nr = rec->nr;
slot->errno_post = rec->errno_post;
slot->do32bit = rec->do32bit;
slot->valid = true;
atomic_store_explicit(&ring->head, head + 1, memory_order_release);
}
/*
* For the child processes, we don't want core dumps (unless we're running with -D)
* This is because it's not uncommon for us to get segfaults etc when we're doing
* syscalls with garbage for arguments.
*/
static void disable_coredumps(void)
{
struct rlimit limit = { .rlim_cur = 0, .rlim_max = 0 };
if (shm->debug == true) {
struct sigaction sa;
struct rlimit unlim = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY
};
sa.sa_handler = SIG_DFL;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
(void)sigaction(SIGABRT, &sa, NULL);
(void)sigaction(SIGSEGV, &sa, NULL);
/*
* Force core dumps on regardless of inherited RLIMIT_CORE.
* Without this, a parent shell with the typical `ulimit -c 0`
* silently propagates to children — segfaults appear in dmesg
* (which always logs SIGSEGV) but no core file lands, defeating
* the whole point of -D for post-mortem debugging.
*/
if (setrlimit(RLIMIT_CORE, &unlim) != 0)
perror("setrlimit(RLIMIT_CORE)");
prctl(PR_SET_DUMPABLE, true);
return;
}
if (setrlimit(RLIMIT_CORE, &limit) != 0)
perror( "setrlimit(RLIMIT_CORE)" );
prctl(PR_SET_DUMPABLE, false);
}
static void enable_coredumps(void)
{
struct rlimit limit = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY
};
if (shm->debug == true)
return;
prctl(PR_SET_DUMPABLE, true);
(void) setrlimit(RLIMIT_CORE, &limit);
}
/*
* Enable the kernels fault-injection code for our child process.
* (Assumes you've set everything else up by hand).
*/
static void set_make_it_fail(void)
{
int fd;
const char *buf = "1";
/* If we failed last time, it's probably because we don't
* have fault-injection enabled, so don't bother trying in future.
*/
if (__atomic_load_n(&shm->dont_make_it_fail, __ATOMIC_RELAXED))
return;
fd = open("/proc/self/make-it-fail", O_WRONLY);
if (fd == -1) {
__atomic_store_n(&shm->dont_make_it_fail, true, __ATOMIC_RELAXED);
return;
}
if (write(fd, buf, 1) == -1) {
if (errno != EPERM)
outputerr("writing to /proc/self/make-it-fail failed! (%s)\n", strerror(errno));
__atomic_store_n(&shm->dont_make_it_fail, true, __ATOMIC_RELAXED);
}
close(fd);
}
/*
* Open /proc/self/fail-nth so we can later arm allocation-failure injection
* for individual syscalls. Requires CONFIG_FAULT_INJECTION (and typically
* CONFIG_FAILSLAB / CONFIG_FAIL_PAGE_ALLOC) on the running kernel; the
* actual failslab=N tunable must be set up out-of-band via debugfs.
*
* If the open fails (kernel built without fault injection, perms, etc.)
* leave fail_nth_fd at -1 so all later code becomes a no-op, and remember
* the result in shm so siblings stop probing too.
*/
static void open_fail_nth(struct childdata *child)
{
int fd;
if (shm->no_fail_nth == true)
return;
fd = open("/proc/self/fail-nth", O_WRONLY);
if (fd == -1) {
shm->no_fail_nth = true;
return;
}
child->fail_nth_fd = fd;
}
/*
* Read /proc/sys/kernel/tainted via a cached fd. Procfs returns the
* mask as ASCII decimal followed by '\n'. lseek(0) is required because
* the procfs handler reports "no more data" on a second read of the
* same open without a rewind. Errors return 0 (mask unknown) so the
* caller's XOR delta degrades to "no change" rather than spuriously
* firing the watcher.
*/
static unsigned long read_tainted_mask(int fd)
{
char buf[32];
ssize_t n;
if (fd < 0)
return 0;
if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
return 0;
n = read(fd, buf, sizeof(buf) - 1);
if (n <= 0)
return 0;
buf[n] = '\0';
return strtoul(buf, NULL, 10);
}
/*
* Cache an fd to /proc/sys/kernel/tainted for the per-childop taint
* watcher. -1 disables the watcher (e.g. on kernels where the file is
* unreadable). Sibling probes don't share state via shm because the
* file is world-readable on every supported kernel — a per-child failure
* is almost certainly local (fd exhaustion) and not worth latching off
* fleet-wide.
*/
static void open_tainted_fd(struct childdata *child)
{
int fd;
fd = open("/proc/sys/kernel/tainted", O_RDONLY);
if (fd == -1) {
child->tainted_fd = -1;
child->last_tainted = 0;
return;
}
child->tainted_fd = fd;
child->last_tainted = read_tainted_mask(fd);
}
/*
* We call this occasionally to set some FPU state, in the hopes that we
* might tickle some weird FPU/scheduler related bugs
*/
static void use_fpu(void)
{
double x = 0;
asm volatile("":"+m" (x));
x += 1;
asm volatile("":"+m" (x));
}
/*
* Tweak the oom_score_adj setting for our child so that there's a higher
* chance that the oom-killer kills our processes rather than something
* more important.
*/
void oom_score_adj(int adj)
{
FILE *fp;
fp = fopen("/proc/self/oom_score_adj", "w");
if (!fp)
return;
fprintf(fp, "%d", adj);
fclose(fp);
}
/*
* Wipe out any state left from a previous child running in this slot.
*/
void clean_childdata(struct childdata *child)
{
memset(&child->syscall, 0, sizeof(struct syscallrecord));
child->seed = 0;
child->kill_count = 0;
child->dontkillme = false;
child->xcpu_count = 0;
child->op_nr = 0;
child->local_op_count = 0;
child->current_fd = -1;
child->fd_lifetime = 0;
child->cached_fd_generation = 0;
child->last_group = GROUP_NONE;
child->last_syscall_nr = EDGEPAIR_NO_PREV;
child->dropped_privs = false;
child->op_type = CHILD_OP_SYSCALL;
child->stall_count = 0;
child->stall_last = 0;
child->fd_created = 0;
child->fd_closed = 0;
memset(child->fd_created_by_group, 0, sizeof(child->fd_created_by_group));
clock_gettime(CLOCK_MONOTONIC, &child->tp);
/* Reset live fd ring: -1 marks all slots as empty. */
for (int i = 0; i < CHILD_FD_RING_SIZE; i++)
child->live_fds.fds[i] = -1;
child->live_fds.head = 0;
/* Reset syscall ring; UNKNOWN state in zeroed slots is filtered
* by the post-mortem reader so a freshly-spawned child contributes
* nothing until it has actually completed a syscall. */
memset(child->syscall_ring.recent, 0, sizeof(child->syscall_ring.recent));
atomic_store_explicit(&child->syscall_ring.head, 0,
memory_order_relaxed);
child->fail_nth_fd = -1;
child->tainted_fd = -1;
child->last_tainted = 0;
child->current_recipe_name = NULL;
/* Drop any sentinel reading from the previous occupant of this slot
* so the first periodic_work tick re-populates without comparing
* against state captured under a different child's environment. */
child->sentinel_prev.valid = false;
/* Clear any __BUG() stamp left by the prior occupant of this slot
* so the parent's zombie-pending warning doesn't mis-attribute the
* fresh child's eventual exit to the previous one's assertion. */
child->hit_bug = false;
child->bug_text = NULL;
child->bug_func = NULL;
child->bug_lineno = 0;
if (child->fd_event_ring)
fd_event_ring_init(child->fd_event_ring);
}
static void bind_child_to_cpu(struct childdata *child)
{
cpu_set_t set;
unsigned int cpudest;
pid_t pid = __atomic_load_n(&pids[child->num], __ATOMIC_RELAXED);
if (no_bind_to_cpu == true)
return;
if (sched_getaffinity(pid, sizeof(set), &set) != 0)
return;
if (child->num >= num_online_cpus)
cpudest = child->num % num_online_cpus;
else
cpudest = child->num;
CPU_ZERO(&set);
CPU_SET(cpudest, &set);
sched_setaffinity(pid, sizeof(set), &set);
}
/*
* Randomise process context before the child starts fuzzing syscalls.
* Called once per child from init_child(). Best-effort — errors are
* silently ignored so a failed operation never wedges the child.
*
* Deliberately omits CLONE_NEWPID (doesn't move us, affects future forks
* unpredictably) and CLONE_NEWUSER (drops caps, breaks privileged paths).
*/
static void munge_process(void)
{
static const int extra_ns_flags[] = {
CLONE_NEWUTS,
CLONE_SYSVSEM,
#ifdef CLONE_NEWCGROUP
CLONE_NEWCGROUP,
#endif
#ifdef CLONE_NEWTIME
CLONE_NEWTIME,
#endif
};
static const unsigned long personas[] = {
PER_LINUX,
PER_LINUX | ADDR_NO_RANDOMIZE,
PER_LINUX | READ_IMPLIES_EXEC,
PER_LINUX | ADDR_COMPAT_LAYOUT,
PER_LINUX | MMAP_PAGE_ZERO,
PER_LINUX32,
};
static const int rlim_resources[] = {
RLIMIT_DATA,
RLIMIT_FSIZE,
RLIMIT_MSGQUEUE,
RLIMIT_NICE,
};
char cgpath[64];
unsigned int i;
int fd;
/* Additional namespace diversity on top of what init_child already does. */
for (i = 0; i < ARRAY_SIZE(extra_ns_flags); i++) {
if (RAND_BOOL())
unshare(extra_ns_flags[i]);
}
/* Random personality — stay within PER_LINUX family to remain sane. */
personality(RAND_ARRAY(personas));
/*
* Best-effort cgroup migration. Trinity can pre-create numbered
* cgroups (/sys/fs/cgroup/trinity0..7) as writable directories;
* if they don't exist we skip silently.
*/
snprintf(cgpath, sizeof(cgpath), "/sys/fs/cgroup/trinity%d/cgroup.procs",
rand() % 8);
fd = open(cgpath, O_WRONLY);
if (fd >= 0) {
char pidbuf[16];
int len = snprintf(pidbuf, sizeof(pidbuf), "%d", getpid());
ssize_t ret __attribute__((unused));
ret = write(fd, pidbuf, (size_t) len);
close(fd);
}
/* Randomly tighten a subset of resource limits. */
for (i = 0; i < ARRAY_SIZE(rlim_resources); i++) {
struct rlimit lim;
if (!RAND_BOOL())
continue;
if (getrlimit(rlim_resources[i], &lim) != 0)
continue;
if (lim.rlim_cur == RLIM_INFINITY || lim.rlim_cur < 2)
continue;
/* Reduce to a random value in [50%, 100%) of current soft limit. */
lim.rlim_cur = lim.rlim_cur / 2 + rand() % (lim.rlim_cur / 2);
(void) setrlimit(rlim_resources[i], &lim);
}
/* Random umask. */
umask((mode_t)(rand() & 0777));
}
/*
* Mprotect every sibling's childdata to PROT_READ in our address space.
*
* Called from init_child for the initial sweep, and from the top of the
* child_process loop as a catch-up sweep when shm->sibling_freeze_gen
* has bumped (a new sibling joined since we last ran). Idempotent:
* mprotect on an already-PROT_READ region is a cheap no-op for slots
* that haven't changed protection.
*
* Uses my_childno (caller's stack value) rather than child->num so a
* sibling's stray write that corrupted our own num field can't trick
* us into mprotecting our own region and then SIGSEGV'ing on the next
* write.
*
* mprotect can return -ENOMEM if the kernel runs out of VMA slots
* splitting the mapping that covers a sibling's childdata. Best-effort
* hardening — count the failure and keep going rather than aborting,
* which would turn a transient kernel limit into a fleet-wide outage.
*/
static void freeze_sibling_childdata(int my_childno)
{
unsigned int i;
for_each_child(i) {
if ((unsigned int)my_childno == i)
continue;
if (children[i] == NULL)
continue;
if (mprotect(children[i], sizeof(struct childdata), PROT_READ) != 0) {
outputerr("freeze_sibling_childdata: mprotect(sibling %u childdata) failed: %s\n",
i, strerror(errno));
__atomic_add_fetch(&shm->stats.sibling_mprotect_failed, 1,
__ATOMIC_RELAXED);
}
}
}
/*
* Called from the fork_children loop in the main process.
*/
static void init_child(struct childdata *child, int childno)
{
pid_t pid = getpid();
unsigned int new_gen;
int devnull;
/* Redirect stdin/stdout/stderr to /dev/null so no syscall
* (splice, sendfile, vmsplice, write to fd 0, etc.) can spew to
* the operator's terminal. fd 0 must be redirected too: ptys
* are bidirectional and writing to the inherited stdin (which
* is the operator's pty) lands on their shell. Open O_RDWR so
* fuzzed reads against fd 0 also succeed (with EOF) instead of
* EBADF'ing — keeps the syscall behaviour realistic. */
devnull = open("/dev/null", O_RDWR);
if (devnull >= 0) {
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
if (devnull > STDERR_FILENO)
close(devnull);
}
/* Detach from the controlling terminal so a fuzzed
* open("/dev/tty", O_WRONLY) followed by write() can't reach the
* operator's shell. The dup2 above only covers fds 0/1/2; this
* closes the wider class of paths that re-acquire the tty (open of
* /dev/tty itself, ioctl(TIOCSCTTY), etc.). setsid() makes us our
* own session leader without a controlling terminal — subsequent
* /dev/tty opens fail with ENXIO. */
(void) setsid();
/* Re-set num from the stack-based childno in case shared memory
* was corrupted by a sibling's stray write. */
child->num = childno;
/* Initial sibling-childdata freeze. See freeze_sibling_childdata
* for the per-mprotect rationale. After it returns we publish a
* fresh sibling_freeze_gen so existing siblings refreeze on their
* next loop top check and pull our own region into PROT_READ —
* closing the startup-race window where a faster sibling's value-
* result kernel write could land in our not-yet-frozen childdata.
*
* RELEASE on the bump pairs with the ACQUIRE load on the loop top
* check so any sibling that observes the new gen also observes the
* children[] entries this child relies on. Cache last_seen with
* the just-bumped value so we don't immediately self-trigger a
* refreeze on our first loop iteration. */
freeze_sibling_childdata(childno);
new_gen = __atomic_add_fetch(&shm->sibling_freeze_gen, 1, __ATOMIC_RELEASE);
child->last_seen_freeze_gen = new_gen;
/* Same rationale for the shared pids[] array: a stray sibling write
* into pids[] could spoof a child's pid, breaking pid_alive() / the
* watchdog reaper. Done here (not in freeze_sibling_childdata)
* because pids[] is a single allocation that doesn't grow — one
* mprotect at init time is enough; the per-loop refreeze path only
* needs to chase newly-spawned childdata regions. */
if (mprotect(pids, max_children * sizeof(int), PROT_READ) != 0) {
outputerr("init_child: mprotect(pids[]) failed: %s\n", strerror(errno));
__atomic_add_fetch(&shm->stats.sibling_mprotect_failed, 1,
__ATOMIC_RELAXED);
}
/* Wait for parent to set our childno */
while (__atomic_load_n(&pids[childno], __ATOMIC_ACQUIRE) != pid) {
sched_yield();
/* Make sure parent is actually alive to wait for us. */
if (pid_alive(mainpid) == false) {
panic(EXIT_SHM_CORRUPTION);
outputerr("BUG!: parent (%d) went away!\n", mainpid);
_exit(EXIT_SHM_CORRUPTION);
}
}
/* Cache our childno/pid for O(1) lookups in this_child()/find_childno().
* Pass the child pointer directly — don't re-derive it from
* children[] which sits in mprotected shared memory but accessing
* via the cached argument avoids the indirection on the hot path. */
set_child_cache(childno, pid, child);
output_set_pid(pid);
set_seed(child);
init_object_lists(OBJ_LOCAL, child);
init_child_mappings();
init_child_futexes();
dirty_random_mapping();
if (RAND_BOOL())
bind_child_to_cpu(child);
/* Wait for all the children to start up. */
while (!__atomic_load_n(&shm->ready, __ATOMIC_ACQUIRE))
sleep(1);
set_make_it_fail();
open_fail_nth(child);
open_tainted_fd(child);
if (RAND_BOOL())
use_fpu();
mask_signals_child();
if (RAND_BOOL()) {
/* unshare(CLONE_NEWNS) gives this child its own mount namespace,
* but the new ns inherits propagation mode from the parent. On
* most distros / is MS_SHARED, so without an explicit MS_PRIVATE
* remount any mount() this child later issues — including the
* random ones from the syscall fuzzer — propagates back into the
* host's mount tree. Make the new ns recursively private so
* downstream mount churn stays contained. If the remount is
* rejected (EPERM in some sandboxed configs) we can't undo the
* unshare, so latch shm->no_private_ns to skip future attempts
* and log only the first failure: the child is still usable,
* just not isolated for mount fuzzing. */
if (!__atomic_load_n(&shm->no_private_ns, __ATOMIC_RELAXED)) {
if (unshare(CLONE_NEWNS) == 0) {
if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) {
if (!__atomic_exchange_n(&shm->no_private_ns, true, __ATOMIC_RELAXED))
output(0, "child %d: MS_PRIVATE remount failed (errno=%d) "
"after unshare(CLONE_NEWNS); mounts in this child "
"may propagate to host mount table\n",
childno, errno);
}
}
}
unshare(CLONE_NEWIPC);
unshare(CLONE_IO);
unshare(CLONE_NEWNET);
}
/*
* Optionally enter a new PID namespace. unshare(CLONE_NEWPID)
* doesn't move *us* into the new namespace — it means our next
* fork() creates pid 1 in a fresh pidns. This exercises kernel
* pidns code paths when EXTRA_FORK syscalls (like execve) run.
*
* Skip if we already know it'll fail (EPERM on unprivileged
* kernels without user_namespaces, or missing CONFIG_PID_NS).
*/
#ifdef CLONE_NEWPID
if (RAND_BOOL() && !__atomic_load_n(&shm->no_pidns, __ATOMIC_RELAXED)) {
if (unshare(CLONE_NEWPID) == -1) {
if (errno == EPERM || errno == EINVAL)
__atomic_store_n(&shm->no_pidns, true, __ATOMIC_RELAXED);
}
}
#endif
if (orig_uid == 0)
child->dropped_privs = false;
munge_process();
kcov_init_child(&child->kcov, child->num);
/* Uniarch: pin the active-syscalls pointer once. Biarch leaves
* this NULL — the first choose_syscall_table call refreshes it. */
if (!biarch)
child->active_syscalls = shm->active_syscalls;
}
/*
* Sanity check to make sure that the main process is still around
* to wait for us.
*/
static void check_parent_pid(void)
{
pid_t pid, ppid;
ppid = getppid();
if (ppid == mainpid)
return;
pid = getpid();
/*
* Inside a PID namespace our parent may legitimately be pid 1
* (the namespace init) or we ourselves may be pid 1. Either
* case is expected when CLONE_NEWPID is in play — just bail
* out of this child quietly rather than triggering a panic.
*/
if (pid == 1 || ppid == 1) {
debugf("pidns detected (pid=%d ppid=%d), exiting child.\n", pid, ppid);
_exit(EXIT_REPARENT_PROBLEM);
}
if (pid == ppid) {
debugf("pid became ppid! exiting child.\n");
_exit(EXIT_REPARENT_PROBLEM);
}
if (ppid < 2) {
debugf("ppid == %d. pidns? exiting child.\n", ppid);
_exit(EXIT_REPARENT_PROBLEM);
}
lock(&shm->buglock);
if (__atomic_load_n(&shm->exit_reason, __ATOMIC_RELAXED) == EXIT_REPARENT_PROBLEM)
goto out;
output(0, "BUG!: CHILD (pid:%d) GOT REPARENTED! "
"main pid:%d. ppid=%d\n",
pid, mainpid, ppid);
if (pid_alive(mainpid) == false)
output(0, "main pid %d is dead.\n", mainpid);
panic(EXIT_REPARENT_PROBLEM);
out:
unlock(&shm->buglock);
_exit(EXIT_REPARENT_PROBLEM);
}
/*
* Here we call various functions that perform checks/changes that
* we don't want to happen on every iteration of the child loop.
*
* The caller gates entry on (op_nr & 15) == 0, so reaching here is
* already the "every 16 iterations" event — check_parent_pid and the
* divergence sentinel run unconditionally. The deeper 128-iteration
* gate is folded into the op_nr argument so this function carries no
* static state at all.
*/
static void periodic_work(struct childdata *child, unsigned long op_nr)
{
check_parent_pid();
divergence_sentinel_tick(child);
/* Every 128 iterations. */
if ((op_nr & 127) == 0) {
dirty_random_mapping();
run_fd_provider_child_ops();
}
}
/*
* Per-op-type stall thresholds. Syscalls are fast, so 10 missed
* progress checks means something is stuck. Future op types that do
* heavier work (fault injection, fd lifecycle stress) get more slack.
*/
static unsigned int stall_threshold(enum child_op_type op_type)
{
switch (op_type) {
case CHILD_OP_MMAP_LIFECYCLE: return 30;
case CHILD_OP_MPROTECT_SPLIT: return 30;
case CHILD_OP_MLOCK_PRESSURE: return 50;
case CHILD_OP_INODE_SPEWER: return 40;
case CHILD_OP_PROCFS_WRITER: return 60;
case CHILD_OP_MEMORY_PRESSURE: return 30;
case CHILD_OP_USERNS_FUZZER: return 60;
case CHILD_OP_SCHED_CYCLER: return 30;
case CHILD_OP_BARRIER_RACER: return 30;
case CHILD_OP_GENETLINK_FUZZER: return 30;
case CHILD_OP_PERF_CHAINS: return 30;
case CHILD_OP_TRACEFS_FUZZER: return 60;
case CHILD_OP_BPF_LIFECYCLE: return 40;
case CHILD_OP_FAULT_INJECTOR: return 20;
case CHILD_OP_RECIPE_RUNNER: return 40;
case CHILD_OP_IOURING_RECIPES: return 40;
case CHILD_OP_FD_STRESS: return 30;
case CHILD_OP_FS_LIFECYCLE: return 60;
case CHILD_OP_FLOCK_THRASH: return 30;
case CHILD_OP_PIDFD_STORM: return 30;
case CHILD_OP_MADVISE_CYCLER: return 30;
case CHILD_OP_KEYRING_SPAM: return 30;
case CHILD_OP_VDSO_MREMAP_RACE: return 30;
case CHILD_OP_NUMA_MIGRATION: return 40;
case CHILD_OP_CPU_HOTPLUG_RIDER: return 50;
case CHILD_OP_CGROUP_CHURN: return 30;
case CHILD_OP_MOUNT_CHURN: return 40;
case CHILD_OP_UFFD_CHURN: return 30;
case CHILD_OP_IOURING_FLOOD: return 30;
case CHILD_OP_CLOSE_RACER: return 30;
case CHILD_OP_XATTR_THRASH: return 30;
case CHILD_OP_EPOLL_VOLATILITY: return 30;
case CHILD_OP_SLAB_CACHE_THRASH: return 30;
default: return 10;
}
}
/*
* Check if a SIGALRM timeout indicates a stuck-on-fd situation.
* If so, evict the fd and notify the parent.
* Only meaningful for CHILD_OP_SYSCALL — other op types don't use the
* syscall record, so skip the fd-eviction logic for them.
*/
static void handle_alarm_timeout(struct childdata *child)
{
struct syscallrecord *rec = &child->syscall;
if (child->op_type != CHILD_OP_SYSCALL)
return;
if (rec->state != BEFORE)
return;
if (check_if_fd(rec) == true) {
child->fd_lifetime = 0;
if (child->fd_event_ring != NULL)
fd_event_enqueue(child->fd_event_ring, FD_EVENT_CLOSE,
(int) rec->a1, -1, 0, 0, 0);
}
}
/*
* Stall detection: count consecutive alarm timeouts without the child
* making forward progress (op_nr advancing). If the child is stuck,
* exit it so the parent can respawn a fresh one.
*/
static bool check_stall(struct childdata *child)
{
if (child->op_nr == child->stall_last) {
child->stall_count++;
} else {
child->stall_count = 0;
child->stall_last = child->op_nr;
}
if (child->stall_count == stall_threshold(child->op_type)) {
output(1, "no progress for %u tries (op_type=%d), exiting child.\n",
child->stall_count, child->op_type);
return true;
}
return false;
}
#define FD_LEAK_THRESHOLD 50
static void check_fd_leaks(struct childdata *child)
{
static const char * const group_names[NR_GROUPS] = {
[GROUP_NONE] = "none",
[GROUP_VM] = "vm",
[GROUP_VFS] = "vfs",
[GROUP_NET] = "net",
[GROUP_IPC] = "ipc",
[GROUP_PROCESS] = "process",
[GROUP_SIGNAL] = "signal",
[GROUP_IO_URING] = "io_uring",
[GROUP_BPF] = "bpf",
[GROUP_SCHED] = "sched",
[GROUP_TIME] = "time",
};
long delta;
unsigned int i;
if (child->fd_created < child->fd_closed)
return;
delta = (long)(child->fd_created - child->fd_closed);
if (delta <= FD_LEAK_THRESHOLD)
return;
output(0, "fd leak: child %d created %lu closed %lu (delta %ld, %lu ops)\n",
child->num, child->fd_created, child->fd_closed,
delta, child->op_nr);
for (i = 0; i < NR_GROUPS; i++) {
if (child->fd_created_by_group[i] > 0)
output(0, " group %-10s: %lu fds created\n",
group_names[i], child->fd_created_by_group[i]);
}
}
/*
* Pick an op type for this iteration. Syscalls dominate (~95%),
* with the remaining ~5% spread across the alternative ops.
* This gives the VM-stress and inode paths occasional exercise
* without starving the main syscall fuzzer.
*
* Cases 5-18 are gated here: they are structurally reachable (the r%19 bug
* is fixed) but their throughput cost is unknown. procfs_writer (case 4)
* crashed iters/s 8x at default rate before its discovery path was hoisted.
* Enable the dormant ops one at a time once each has been load-tested.
* To enable an op: set its entry below to 0.
*/
static const int dormant_op_disabled[39] = {
0, 0, 0, 0, 0, /* 0-4: active: mmap_lifecycle, mprotect_split, mlock_pressure, inode_spewer, procfs_writer */
0, 1, 1, 1, 1, /* 5-9: memory_pressure active (first dormant-op enable); dormant: userns_fuzzer, sched_cycler, barrier_racer, genetlink_fuzzer */
1, 1, 1, 0, 1, /* 10-14: fault_injector active; dormant: perf_chains, tracefs_fuzzer, bpf_lifecycle, recipe_runner */
1, 1, 0, 1, 1, /* 15-19: refcount_auditor active; dormant: iouring_recipes, fd_stress, fs_lifecycle, signal_storm */
1, 1, 1, 1, 1, /* 20-24: dormant: futex_storm, pipe_thrash, fork_storm, flock_thrash, cgroup_churn */
1, 1, 1, 1, 1, /* 25-29: dormant: mount_churn, uffd_churn, iouring_flood, close_racer, socket_family_chain */
1, 1, 1, 1, 1, /* 30-34: dormant: xattr_thrash, pidfd_storm, madvise_cycler, epoll_volatility, keyring_spam */
1, 1, 1, 0, /* 35-38: slab_cache_thrash active; dormant: vdso_mremap_race, numa_migration, cpu_hotplug_rider */
};
/*
* Round-robin rotation for dedicated alt-op children. The slow,
* pressure-style ops are listed first (mmap_lifecycle, mprotect_split,
* mlock_pressure, inode_spewer) because those are the paths the design
* brief explicitly calls out as too expensive to mix into the syscall
* hot loop even at 1%. fork/futex/signal/pipe/flock storms come next,
* then the cgroup/mount/uffd/io_uring churners, and finally the heavier
* subsystem fuzzers (perf, tracefs, bpf, fault-injector, recipes). The
* dispatch in child_process() already has cases for every entry below,
* so a dedicated child stamped with any of these op types runs straight
* through the existing per-op function on every iteration.
*
* Bypasses the dormant_op_disabled[] gate by design: random pickers stay
* gated until each op has been load-tested, but a child reserved for a
* specific op runs it deliberately.
*/
static const enum child_op_type alt_op_rotation[] = {
CHILD_OP_MMAP_LIFECYCLE,
CHILD_OP_MPROTECT_SPLIT,
CHILD_OP_MADVISE_CYCLER,
CHILD_OP_NUMA_MIGRATION,
CHILD_OP_MLOCK_PRESSURE,
CHILD_OP_INODE_SPEWER,
CHILD_OP_FORK_STORM,
CHILD_OP_CPU_HOTPLUG_RIDER,
CHILD_OP_PIDFD_STORM,
CHILD_OP_FUTEX_STORM,
CHILD_OP_SIGNAL_STORM,
CHILD_OP_PIPE_THRASH,
CHILD_OP_FLOCK_THRASH,
CHILD_OP_XATTR_THRASH,
CHILD_OP_CGROUP_CHURN,
CHILD_OP_MOUNT_CHURN,
CHILD_OP_UFFD_CHURN,
CHILD_OP_IOURING_FLOOD,
CHILD_OP_CLOSE_RACER,
CHILD_OP_EPOLL_VOLATILITY,
CHILD_OP_KEYRING_SPAM,
CHILD_OP_VDSO_MREMAP_RACE,
CHILD_OP_MEMORY_PRESSURE,
CHILD_OP_SLAB_CACHE_THRASH,
CHILD_OP_USERNS_FUZZER,
CHILD_OP_SCHED_CYCLER,
CHILD_OP_BARRIER_RACER,
CHILD_OP_GENETLINK_FUZZER,
CHILD_OP_PERF_CHAINS,
CHILD_OP_TRACEFS_FUZZER,
CHILD_OP_BPF_LIFECYCLE,
CHILD_OP_FAULT_INJECTOR,
CHILD_OP_RECIPE_RUNNER,
CHILD_OP_IOURING_RECIPES,
CHILD_OP_FD_STRESS,
CHILD_OP_REFCOUNT_AUDITOR,
CHILD_OP_FS_LIFECYCLE,
CHILD_OP_PROCFS_WRITER,
CHILD_OP_SOCKET_FAMILY_CHAIN,
};
#define NR_ALT_OP_ROTATION ARRAY_SIZE(alt_op_rotation)
static const char *alt_op_name(enum child_op_type op)
{
switch (op) {
case CHILD_OP_SYSCALL: return "syscall";
case CHILD_OP_MMAP_LIFECYCLE: return "mmap_lifecycle";
case CHILD_OP_MPROTECT_SPLIT: return "mprotect_split";
case CHILD_OP_MLOCK_PRESSURE: return "mlock_pressure";
case CHILD_OP_INODE_SPEWER: return "inode_spewer";
case CHILD_OP_PROCFS_WRITER: return "procfs_writer";
case CHILD_OP_MEMORY_PRESSURE: return "memory_pressure";
case CHILD_OP_USERNS_FUZZER: return "userns_fuzzer";
case CHILD_OP_SCHED_CYCLER: return "sched_cycler";
case CHILD_OP_BARRIER_RACER: return "barrier_racer";
case CHILD_OP_GENETLINK_FUZZER: return "genetlink_fuzzer";
case CHILD_OP_PERF_CHAINS: return "perf_chains";
case CHILD_OP_TRACEFS_FUZZER: return "tracefs_fuzzer";
case CHILD_OP_BPF_LIFECYCLE: return "bpf_lifecycle";
case CHILD_OP_FAULT_INJECTOR: return "fault_injector";
case CHILD_OP_RECIPE_RUNNER: return "recipe_runner";
case CHILD_OP_IOURING_RECIPES: return "iouring_recipes";
case CHILD_OP_FD_STRESS: return "fd_stress";
case CHILD_OP_REFCOUNT_AUDITOR: return "refcount_auditor";
case CHILD_OP_FS_LIFECYCLE: return "fs_lifecycle";
case CHILD_OP_SIGNAL_STORM: return "signal_storm";
case CHILD_OP_FUTEX_STORM: return "futex_storm";
case CHILD_OP_PIPE_THRASH: return "pipe_thrash";
case CHILD_OP_FORK_STORM: return "fork_storm";
case CHILD_OP_FLOCK_THRASH: return "flock_thrash";
case CHILD_OP_CGROUP_CHURN: return "cgroup_churn";
case CHILD_OP_MOUNT_CHURN: return "mount_churn";
case CHILD_OP_UFFD_CHURN: return "uffd_churn";
case CHILD_OP_IOURING_FLOOD: return "iouring_flood";
case CHILD_OP_CLOSE_RACER: return "close_racer";
case CHILD_OP_SOCKET_FAMILY_CHAIN: return "socket_family_chain";
case CHILD_OP_XATTR_THRASH: return "xattr_thrash";
case CHILD_OP_PIDFD_STORM: return "pidfd_storm";
case CHILD_OP_MADVISE_CYCLER: return "madvise_cycler";
case CHILD_OP_EPOLL_VOLATILITY: return "epoll_volatility";
case CHILD_OP_KEYRING_SPAM: return "keyring_spam";
case CHILD_OP_VDSO_MREMAP_RACE: return "vdso_mremap_race";
case CHILD_OP_NUMA_MIGRATION: return "numa_migration";