-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathsequence.c
More file actions
1858 lines (1659 loc) · 61 KB
/
Copy pathsequence.c
File metadata and controls
1858 lines (1659 loc) · 61 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
/*
* Sequence-aware fuzzing — chain executor and chain corpus.
*
* Phase 1 dispatches a short chain of random syscalls per fuzzer iteration
* and threads each call's return value into the next call's args with a
* tunable probability. Phase 2 (this file) mines productive chains into a
* global ring of saved chains, and replays them on a fraction of future
* iterations with the per-arg mutator chain that the per-call mini-corpus
* already runs (cross-arg splice + weighted-stack mutate + fd safety).
* Phase 3 (deferred) will add resource-type dependency tracking so chains
* are generated with structural awareness of which calls produce and
* consume which kinds of resource.
*
* Chain length is drawn from pick_chain_length()'s discrete
* distribution centred on 3: P(2)=30%, P(3)=40%, P(4)=30%. Two-call
* chains remain a common setup-then-use shape (open then ioctl,
* socket then sendmsg) but the rebalanced weights -- moved here
* from an earlier 50/30/20 bias toward 2 -- give length-3
* setup-then-use-then-tear sequences the largest share, which is
* where the chain corpus saw most of its productive replays. Four
* remains a backstop for the longer-tail patterns at the same 30%
* rate; lengths beyond 4 are out of scope for this phase.
*
* Substitution-vs-failure: if a step's retval is negative (errno-style
* failure) the next step is dispatched without a substitute, since
* passing -EBADF as an fd to the following call wastes the slot. The
* chain itself continues — a single mid-chain failure does not abort
* the remaining steps.
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/bpf.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
#include "child.h"
#include "fd.h"
#include "kcov.h"
#include "minicorpus.h"
#include "params.h"
#include "persist-util.h"
#include "random.h"
#include "rnd.h"
#include "sequence.h"
#include "shm.h"
#include "syscall.h"
#include "tables.h"
#include "trinity.h"
#include "utils.h"
/*
* Probability (as 1/N) that an iteration replays a saved chain instead
* of generating a fresh one.
*
* 4 == 25%. Same starting point as minicorpus_replay's per-call replay
* rate, picked so the two replay paths sit at the same baseline and
* any divergence in coverage productivity between per-call and per-
* chain replay is attributable to the structural difference rather
* than to a sampling rate gap. Lower than 50/50 because fresh
* generation is still where new chain shapes are discovered — a
* replay-dominated mix would saturate on the seed distribution that
* Phase 1's random chain length and arg generation produce. The
* gating is in run_sequence_chain so the replay rate can be tuned
* here without touching the dispatch code.
*/
#define CHAIN_REPLAY_RATIO 4
/*
* Probability divisor (1/N) applied to replay picks whose source chain
* was admitted under a non-PC reason (TRANSITION / CMP). The non-PC save
* reasons exist to grow the corpus on the warm-PC plateau where the PC-
* only gate produces ~zero saves; until per-reason replay productivity
* data is in (chain_replay_win_by_reason[] vs chain_save_by_reason[]) we
* deliberately pull non-PC replays at half the PC-saved rate. Anchored
* to PC-saved replays at the full CHAIN_REPLAY_RATIO so the comparison
* stays controlled: any divergence in coverage productivity between PC-
* saved and non-PC-saved chain replay is attributable to the source
* signal rather than to a sampling rate gap.
*/
#define CHAIN_REPLAY_NONPC_DOWNSAMPLE 2
struct chain_corpus_ring *chain_corpus_shm = NULL;
/*
* Resource-type dependency table for --chain-resource-typing.
*
* Small on purpose: one row per fd-family with a clean producer /
* consumer split we already ship coverage for. The whole point of
* the row is to measure per-kind productivity via
* chain_restype_replay_win[] BEFORE deciding which families deserve
* a more elaborate schema. A universal resource model is out of
* scope here.
*
* Entries are syscall NAMES, not compile-time NRs; the numeric slot
* is resolved at chain_restype_init() time via search_syscall_table
* against the active table set (biarch-aware). Names that fail to
* resolve on the current arch drop out silently -- a compat gap that
* removes a producer just leaves that row's producer slot at the
* -1 sentinel, and classify_producer skips it.
*
* PIDFD: clone3 is included as a producer even though it only
* produces a pidfd when its user-side struct clone_args carries the
* CLONE_PIDFD flag. Verifying that would require dereferencing
* args[0] which may point at a fuzzed / unmapped user buffer.
* Classifying by NR alone means clone3 without CLONE_PIDFD is a
* false-positive producer -- but the whole point of the row is to
* MEASURE per-kind productivity, and the resulting near-zero
* chain_restype_replay_win[PIDFD] would surface exactly that
* mismatch. Same rationale for socket-tcp keying on (a1, a2 & 0xff)
* -- we can inspect scalar arg values cheaply, but not deref a
* pointer.
*/
#define CHAIN_RESTYPE_MAX_PRODUCERS 4
#define CHAIN_RESTYPE_MAX_CONSUMERS 6
struct chain_restype_row {
const char *producers[CHAIN_RESTYPE_MAX_PRODUCERS];
const char *consumers[CHAIN_RESTYPE_MAX_CONSUMERS];
};
static const struct chain_restype_row chain_restype_table[CHAIN_RESTYPE_NR] = {
[CHAIN_RESTYPE_EPOLL_FD] = {
.producers = { "epoll_create1", NULL, NULL, NULL },
.consumers = { "epoll_ctl", "epoll_wait", "epoll_pwait",
"epoll_pwait2", NULL, NULL },
},
[CHAIN_RESTYPE_TIMERFD] = {
.producers = { "timerfd_create", NULL, NULL, NULL },
.consumers = { "timerfd_settime", "timerfd_gettime", "read",
NULL, NULL, NULL },
},
[CHAIN_RESTYPE_EVENTFD] = {
.producers = { "eventfd2", NULL, NULL, NULL },
.consumers = { "poll", "read", "write",
NULL, NULL, NULL },
},
[CHAIN_RESTYPE_IO_URING_FD] = {
.producers = { "io_uring_setup", NULL, NULL, NULL },
.consumers = { "io_uring_register", "io_uring_enter",
NULL, NULL, NULL, NULL },
},
[CHAIN_RESTYPE_PIDFD] = {
.producers = { "pidfd_open", "clone3", NULL, NULL },
.consumers = { "pidfd_send_signal", "pidfd_getfd", "waitid",
"process_mrelease", NULL, NULL },
},
[CHAIN_RESTYPE_SOCKET_TCP] = {
.producers = { "socket", NULL, NULL, NULL },
.consumers = { "bind", "connect", "setsockopt", "sendmsg",
NULL, NULL },
},
[CHAIN_RESTYPE_BPF_MAP_FD] = {
.producers = { "bpf", NULL, NULL, NULL },
.consumers = { "bpf", NULL, NULL, NULL, NULL, NULL },
},
};
/*
* Resolved NR tables. [biarch_slot] indexes are (0 = uniarch or
* 64-bit, 1 = 32-bit under biarch). Slot value -1 means "no NR
* resolved" -- either the name is not in the compiled table for
* this arch or biarch is off and we never populate slot 1.
*/
static int chain_restype_producer_nr[CHAIN_RESTYPE_NR][2]
[CHAIN_RESTYPE_MAX_PRODUCERS];
static int chain_restype_consumer_nr[CHAIN_RESTYPE_NR][2]
[CHAIN_RESTYPE_MAX_CONSUMERS];
static void chain_restype_resolve_slot(const char *name, int slot,
int *dst64, int *dst32)
{
int nr;
if (name == NULL) {
*dst64 = -1;
if (dst32 != NULL)
*dst32 = -1;
return;
}
if (biarch == true) {
nr = search_syscall_table(syscalls_64bit,
max_nr_64bit_syscalls, name);
*dst64 = (nr >= 0 && (unsigned int)nr < MAX_NR_SYSCALL) ?
nr : -1;
nr = search_syscall_table(syscalls_32bit,
max_nr_32bit_syscalls, name);
if (dst32 != NULL)
*dst32 = (nr >= 0 && (unsigned int)nr < MAX_NR_SYSCALL) ?
nr : -1;
} else {
nr = search_syscall_table(syscalls, max_nr_syscalls, name);
*dst64 = (nr >= 0 && (unsigned int)nr < MAX_NR_SYSCALL) ?
nr : -1;
if (dst32 != NULL)
*dst32 = -1;
}
(void)slot;
}
void chain_restype_init(void)
{
unsigned int k, i;
for (k = 0; k < CHAIN_RESTYPE_NR; k++) {
const struct chain_restype_row *row = &chain_restype_table[k];
for (i = 0; i < CHAIN_RESTYPE_MAX_PRODUCERS; i++)
chain_restype_resolve_slot(row->producers[i], (int)i,
&chain_restype_producer_nr[k][0][i],
&chain_restype_producer_nr[k][1][i]);
for (i = 0; i < CHAIN_RESTYPE_MAX_CONSUMERS; i++)
chain_restype_resolve_slot(row->consumers[i], (int)i,
&chain_restype_consumer_nr[k][0][i],
&chain_restype_consumer_nr[k][1][i]);
}
}
/*
* NR match against the producer slot for @kind. Biarch-aware: 32-bit
* dispatches match against the biarch [1] slot, everything else
* matches [0].
*/
static bool chain_restype_nr_matches_producer(enum chain_resource_kind kind,
unsigned int nr, bool do32bit)
{
unsigned int slot = do32bit ? 1u : 0u;
unsigned int i;
for (i = 0; i < CHAIN_RESTYPE_MAX_PRODUCERS; i++) {
int cand = chain_restype_producer_nr[kind][slot][i];
if (cand < 0)
continue;
if ((unsigned int)cand == nr)
return true;
}
return false;
}
static bool chain_restype_nr_matches_consumer(enum chain_resource_kind kind,
unsigned int nr, bool do32bit)
{
unsigned int slot = do32bit ? 1u : 0u;
unsigned int i;
for (i = 0; i < CHAIN_RESTYPE_MAX_CONSUMERS; i++) {
int cand = chain_restype_consumer_nr[kind][slot][i];
if (cand < 0)
continue;
if ((unsigned int)cand == nr)
return true;
}
return false;
}
int chain_restype_classify_producer(unsigned int nr, bool do32bit,
const unsigned long args[6],
unsigned long retval)
{
/* Filter errno-style returns. A producer that failed did not
* produce a resource; feeding a -EBADF into a consumer next
* step wastes the bias budget on a downstream -EBADF -- exactly
* the same rationale execute_chain_steps uses to gate retval
* substitution on (long)rv >= 0. */
if ((long)retval < 0)
return -1;
/* Simple-NR kinds: any producer NR in the row matches. */
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_EPOLL_FD,
nr, do32bit))
return CHAIN_RESTYPE_EPOLL_FD;
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_TIMERFD,
nr, do32bit))
return CHAIN_RESTYPE_TIMERFD;
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_EVENTFD,
nr, do32bit))
return CHAIN_RESTYPE_EVENTFD;
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_IO_URING_FD,
nr, do32bit))
return CHAIN_RESTYPE_IO_URING_FD;
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_PIDFD,
nr, do32bit))
return CHAIN_RESTYPE_PIDFD;
/* socket-tcp keys on (family, type & 0xff). socket()'s a1 is
* the address family, a2 is (type | flags) so mask out
* SOCK_NONBLOCK / SOCK_CLOEXEC before comparing to SOCK_STREAM. */
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_SOCKET_TCP,
nr, do32bit)) {
unsigned long fam = args[0];
unsigned long type = args[1] & 0xffUL;
if ((fam == AF_INET || fam == AF_INET6) &&
type == (unsigned long)SOCK_STREAM)
return CHAIN_RESTYPE_SOCKET_TCP;
}
/* bpf-map-fd keys on cmd == BPF_MAP_CREATE (a1). */
if (chain_restype_nr_matches_producer(CHAIN_RESTYPE_BPF_MAP_FD,
nr, do32bit)) {
if (args[0] == (unsigned long)BPF_MAP_CREATE)
return CHAIN_RESTYPE_BPF_MAP_FD;
}
return -1;
}
/*
* Classify a step as a consumer. Used by record_chain_outcome() to
* detect producer->consumer PAIRS inside a saved / replayed chain, so
* chain_restype_save / chain_restype_replay_win only bump for chains
* that actually carried the pair (a producer-only chain isn't the
* signal we're trying to reward).
*
* bpf is both the producer and the consumer for BPF_MAP_FD; the
* consumer arm is BPF_MAP_UPDATE_ELEM / BPF_MAP_LOOKUP_ELEM. The NR
* table alone is not enough there -- BPF_MAP_FD is the one row whose
* consumer syscall NR is the same as the producer NR, so an "any
* later bpf()" match would count every unrelated cmd (PROG_LOAD,
* OBJ_PIN, LINK_CREATE ...) as a map-fd consumer and inflate the
* pair signal above what the fd coupling actually earned. Gate the
* BPF row on a1 == BPF_MAP_{UPDATE,LOOKUP}_ELEM so the pair credit
* lines up with real map-fd use. a1 is a scalar cmd enum, not the
* bpf_attr pointer, so this stays within the "inspect scalars, do
* not deref user memory" rule the producer classifier already sets.
*/
static int chain_restype_classify_consumer(enum chain_resource_kind kind,
unsigned int nr, bool do32bit,
const unsigned long args[6])
{
if (kind >= CHAIN_RESTYPE_NR)
return -1;
if (!chain_restype_nr_matches_consumer(kind, nr, do32bit))
return -1;
if (kind == CHAIN_RESTYPE_BPF_MAP_FD) {
unsigned long cmd = args[0];
if (cmd != (unsigned long)BPF_MAP_UPDATE_ELEM &&
cmd != (unsigned long)BPF_MAP_LOOKUP_ELEM)
return -1;
}
return (int)kind;
}
/*
* True iff kind has at least one resolved consumer NR in the table
* for @do32bit_hint. Cheap short-circuit for the SHADOW/LIVE hook:
* a kind whose consumer table came up empty on this arch cannot bias.
*/
static bool chain_restype_has_consumer(enum chain_resource_kind kind,
bool do32bit_hint)
{
unsigned int slot = do32bit_hint ? 1u : 0u;
unsigned int i;
if (kind >= CHAIN_RESTYPE_NR)
return false;
for (i = 0; i < CHAIN_RESTYPE_MAX_CONSUMERS; i++) {
if (chain_restype_consumer_nr[kind][slot][i] >= 0)
return true;
}
return false;
}
int chain_restype_pick_consumer(enum chain_resource_kind kind,
bool do32bit_hint)
{
unsigned int slot = do32bit_hint ? 1u : 0u;
int live[CHAIN_RESTYPE_MAX_CONSUMERS];
unsigned int nlive = 0;
unsigned int i;
if (kind >= CHAIN_RESTYPE_NR)
return -1;
for (i = 0; i < CHAIN_RESTYPE_MAX_CONSUMERS; i++) {
int cand = chain_restype_consumer_nr[kind][slot][i];
struct syscallentry *entry;
if (cand < 0)
continue;
/* The resolved NR set is frozen at init time, but a NR
* can go inert mid-run via the self-deactivation path
* (ENOSYS strike-out, capability loss, TO_BE_DEACTIVATED
* sweep). entry->active_number == 0 marks that state.
* validate_specific_syscall_silent() only rejects the
* static flags (AVOID/NI/NEEDS_ROOT) and will happily
* pass a deactivated entry, so check active_number
* directly here — biasing to a deactivated NR wastes the
* bias budget on a step the dispatch path is going to
* refuse anyway. */
entry = get_syscall_entry((unsigned int)cand, do32bit_hint);
if (entry == NULL)
continue;
if (entry->active_number == 0)
continue;
live[nlive++] = cand;
}
if (nlive == 0)
return -1;
return live[rnd_modulo_u32(nlive)];
}
void chain_corpus_init(void)
{
/* Resolve the resource-typing table once, alongside the ring
* allocation. Runs unconditionally on both the kcov-shm and
* no-kcov-shm paths so an operator can pass
* --chain-resource-typing=shadow even on a build without KCOV
* for the classify counters alone (the ring is what needs
* kcov for save triggers -- classify itself does not). Safe
* to call more than once; the tables are pure writes to
* static slots. */
chain_restype_init();
/* No coverage signal means no save trigger; skip the allocation
* and let chain_corpus_save / dump_stats fall through their NULL
* guards. Mirrors the same kcov_shm gate that minicorpus_init
* uses for the same reason. */
if (kcov_shm == NULL)
return;
/*
* Wild-write risk: a child syscall buffer pointer aliasing into a
* slot could corrupt a stored chain (next replay dispatches garbage
* syscalls — bounded by replay_syscall_step's deactivation /
* sanitise checks, which drop the chain on first unsafe step) or
* stick the ring lock (chain saves and replays stall fleet-wide
* until a kernel-side timeout reaps the holder). No parent crash
* surface.
*
* Route through alloc_shared_pool so the default --guard-shared
* scope (pools) wraps this long-lived single-region ring in
* PROT_NONE guard pages. A stray writer that over- or under-runs
* the region then faults at the write PC instead of silently
* corrupting a stored chain (next replay tail-truncates on the
* sanitise check but the scribble site is already lost) or stalling
* the ring lock until the kernel-side timeout fires. Sibling
* minicorpus_shm, which is the analogous corpus-side wild-writer
* target, has been pool-routed; this lifts the same coverage to
* the chain-corpus ring without dragging a per-child VMA tail in
* (allocation is once, no per-fork multiplication).
*/
chain_corpus_shm = alloc_shared_pool(sizeof(struct chain_corpus_ring));
memset(chain_corpus_shm, 0, sizeof(struct chain_corpus_ring));
output(0, "Sequence chain corpus allocated (%u slots, %lu B per entry)\n",
CHAIN_CORPUS_RING_SIZE,
(unsigned long) sizeof(struct chain_entry));
/*
* Cross-run warm-start. Load any previously-saved chain corpus into
* the freshly-allocated ring before children fork so the load lands
* without racing the producers. Failures (missing file, header
* mismatch, every entry rejected by re-validation) are silent -- a
* missing or stale image just means we boot cold, same policy the
* per-syscall minicorpus warm-start uses. Gated on
* --no-chain-warm-start so an operator can opt out of the chain
* carrier independently of the other cross-run caches.
*/
if (!no_chain_warm_start) {
const char *path = chain_corpus_default_path();
if (path != NULL) {
unsigned int loaded = 0, discarded = 0;
if (chain_corpus_load_file(path, &loaded, &discarded))
output(0, "chain corpus: warm-started %u chains from %s (%u discarded)\n",
loaded, path, discarded);
else if (discarded != 0)
output(0, "chain corpus: %u chains discarded from %s -- cold start\n",
discarded, path);
/* Same path is reused as the mid-run snapshot target so
* a crash between warm-start and clean shutdown does
* not lose every chain admitted during the run. The
* clean-exit save in trinity.c still fires on top of
* this to capture the trailing window of admits the
* periodic cadence had not yet flushed. */
chain_corpus_enable_snapshots(path);
}
}
}
/*
* Replay-safety filter for chain corpus entries.
*
* Returns true if every step in @steps could be replayed without
* feeding stale heap pointers, stale pids, or sanitise-stashed
* pointers to the kernel. Same exclusions as minicorpus_save (which
* treats these arg types as poison) plus the entry->sanitise gate
* that random-syscall.c applies before it calls minicorpus_save.
*
* The check happens at save time so the corpus only ever contains
* chains that are themselves replay-safe. Saving an unsafe chain and
* filtering at replay time would let unsafe entries displace safe ones
* out of the ring as it wraps, and would shrink the effective corpus
* size whenever the unsafe fraction was non-trivial.
*/
static bool chain_is_replay_safe(const struct chain_step *steps,
unsigned int len)
{
unsigned int i, j;
for (i = 0; i < len; i++) {
struct syscallentry *entry = get_syscall_entry(steps[i].nr,
steps[i].do32bit);
if (entry == NULL || entry->sanitise != NULL)
return false;
for (j = 0; j < entry->num_args && j < 6; j++) {
switch (entry->argtype[j]) {
case ARG_IOVEC:
case ARG_IOVEC_IN:
case ARG_PATHNAME:
case ARG_SOCKADDR:
case ARG_MMAP:
case ARG_PID:
return false;
default:
break;
}
}
}
return true;
}
/*
* Push a fresh chain into the ring, overwriting the oldest slot in place
* when the ring is full. Slots are inline structs in shm (no separate
* allocation), so the write is a memcpy under the ring lock and there is
* no eviction free path to defer.
*
* Per-(reason, trigger_nr) admission cap: at most one admit per rotation
* window. The chain corpus is a single fleet-wide pool, so a hot syscall
* that earns a non-PC novelty signal on every other call (CMP / transition
* floods are the realistic shape) could otherwise sweep the ring's PC-
* saved entries out inside one window. Reading shm->syscalls_at_last_switch
* and comparing against the per-(reason, nr) stamp turns that into "first
* winner this window admits, the rest are dropped" without needing a
* separate per-window counter that has to be reset on rotation.
*/
/* lookback depth for chain_corpus_save's duplicate-shape scan */
#define CHAIN_CORPUS_DUP_LOOKBACK 8
/* shape-hash for a chain: FNV-1a over (nr, do32bit) tuples,
* length-included to avoid prefix aliasing */
static uint32_t chain_shape_hash(const struct chain_step *steps,
unsigned int len)
{
uint32_t h = 0x811c9dc5u;
unsigned int i;
h ^= len;
h *= 0x01000193u;
for (i = 0; i < len; i++) {
uint32_t v = (uint32_t)steps[i].nr;
if (steps[i].do32bit)
v |= 0x80000000u;
h ^= v;
h *= 0x01000193u;
}
return h;
}
void chain_corpus_save(const struct chain_step *steps, unsigned int len,
unsigned int reason, unsigned int trigger_nr)
{
struct chain_corpus_ring *ring = chain_corpus_shm;
struct chain_entry tmp;
unsigned int slot, head, count;
unsigned long window_id, prev_window;
uint32_t incoming_hash;
bool dup_seen = false;
if (ring == NULL || len == 0 || len > MAX_SEQ_LEN)
return;
if (reason >= CHAIN_SAVE_NR_REASONS || trigger_nr >= MAX_NR_SYSCALL)
return;
if (!chain_is_replay_safe(steps, len))
return;
/* Per-(reason, nr) per-window cap. Racing children in the same
* window may both observe a stale stamp and both admit -- the cap is
* a flood ceiling, not an exact-one-admit invariant, and the lock
* cost of CAS-tightening it would dwarf the avoided ring churn. */
window_id = __atomic_load_n(&shm->syscalls_at_last_switch,
__ATOMIC_RELAXED);
prev_window = __atomic_load_n(
&ring->chain_save_window_id[reason][trigger_nr],
__ATOMIC_RELAXED);
if (prev_window == window_id && window_id != 0)
return;
__atomic_store_n(&ring->chain_save_window_id[reason][trigger_nr],
window_id, __ATOMIC_RELAXED);
memset(&tmp, 0, sizeof(tmp));
tmp.len = len;
tmp.save_reason = reason;
memcpy(tmp.steps, steps, len * sizeof(struct chain_step));
incoming_hash = chain_shape_hash(steps, len);
lock(&ring->lock);
/* Walk up to CHAIN_CORPUS_DUP_LOOKBACK of the
* most-recent saved slots (excluding the still-empty
* incoming slot) and compare shape hashes. Bounded by
* min(count, lookback) so a warm corpus pays the full
* lookback while a cold corpus pays only its filled depth.
* Done under ring->lock so the slot reads can't tear
* against a concurrent save publishing into the same slot
* range. */
{
unsigned int lookback = ring->count;
unsigned int j;
if (lookback > CHAIN_CORPUS_DUP_LOOKBACK)
lookback = CHAIN_CORPUS_DUP_LOOKBACK;
for (j = 0; j < lookback; j++) {
unsigned int prev_slot =
(ring->head - 1u - j) % CHAIN_CORPUS_RING_SIZE;
const struct chain_entry *p = &ring->slots[prev_slot];
if (p->len == 0 || p->len > MAX_SEQ_LEN)
continue;
if (chain_shape_hash(p->steps, p->len) == incoming_hash) {
dup_seen = true;
break;
}
}
}
head = ring->head;
slot = head % CHAIN_CORPUS_RING_SIZE;
ring->slots[slot] = tmp;
/* Publish head/count with release semantics so the lockless
* chain_corpus_pick reader, which loads them with acquire, sees the
* slot writes that produced this entry. The lock still serialises
* concurrent writers; the atomic stores only exist to give the
* lockless reader a well-defined view of the sequence fields. */
__atomic_store_n(&ring->head, head + 1, __ATOMIC_RELEASE);
count = ring->count;
if (count < CHAIN_CORPUS_RING_SIZE)
__atomic_store_n(&ring->count, count + 1, __ATOMIC_RELEASE);
unlock(&ring->lock);
__atomic_fetch_add(&ring->save_count, 1UL, __ATOMIC_RELAXED);
__atomic_fetch_add(&ring->chain_save_by_reason[reason], 1UL,
__ATOMIC_RELAXED);
if (dup_seen)
__atomic_fetch_add(&shm->stats.chain_corpus_save_dup_shape,
1UL, __ATOMIC_RELAXED);
else
__atomic_fetch_add(&shm->stats.chain_corpus_save_unique_shape,
1UL, __ATOMIC_RELAXED);
}
bool chain_corpus_pick(struct chain_entry *out)
{
struct chain_corpus_ring *ring = chain_corpus_shm;
unsigned int count, head, slot;
if (ring == NULL || out == NULL)
return false;
/*
* Lockless reader. Atomic-load a snapshot of count and head, then
* struct-copy the chosen slot without holding ring->lock. The
* picker used to take ring->lock for the full chain_entry memcpy
* (~MAX_SEQ_LEN * sizeof(struct chain_step)), and CHAIN_REPLAY_RATIO
* routes ~25% of fuzzer iterations through here, so the lock used
* to be a non-trivial contention point with both child producers
* (chain_corpus_save) and child consumers fighting for it.
*
* Race tolerance: a concurrent chain_corpus_save can overwrite the
* slot we are mid-copy on, leaving @out with fields mixed from the
* old and the new chain. The same risk is already documented in
* chain_corpus_init for wild-write corruption — replay_syscall_step
* drops the chain on the first deactivated/sanitise-tagged step,
* and a torn chain that survives those checks just dispatches one
* iteration's worth of slightly-corrupted args to the kernel, which
* is exactly what the fuzzer is doing on its other 75% of iterations
* anyway. No reader-side validity invariant is broken: count is
* monotonic non-decreasing up to CHAIN_CORPUS_RING_SIZE so once we
* observe count > 0 the slot range is well-defined, and len is
* always written in [1, MAX_SEQ_LEN] so even a torn-read len can't
* walk @out->steps past its fixed-size array.
*/
count = __atomic_load_n(&ring->count, __ATOMIC_ACQUIRE);
if (count == 0)
return false;
head = __atomic_load_n(&ring->head, __ATOMIC_ACQUIRE);
/* Pick uniformly across the live entries. The newest entry is
* at (head - 1), the oldest at (head - count); both wrap mod
* CHAIN_CORPUS_RING_SIZE. */
slot = (head - count + rnd_modulo_u32(count)) % CHAIN_CORPUS_RING_SIZE;
*out = ring->slots[slot];
return true;
}
#if ENABLE_SEQUENCE_CHAIN
static unsigned int pick_chain_length(void)
{
unsigned int r = rnd_modulo_u32(10);
if (r < 3)
return 2;
if (r < 7)
return 3;
return 4;
}
/*
* Cross-phase state for one run_sequence_chain() iteration.
*
* Filled by select_chain_source() (source pick + length), grown by
* execute_chain_steps() (steps[] + per-chain novelty), then consumed by
* record_chain_outcome() (save decision + replay-win credit).
*
* Per-chain novelty accounting tracks each save-reason category
* separately so the chain admission decision can prefer PC over
* TRANSITION over CMP -- the priority mirrors the per-call minicorpus
* save tag (PC wins on calls where both PC and CMP fire), keeping the
* chain corpus's reason mix interpretable alongside the per-call
* corpus's saves_by_reason[] for the same event class. trigger_nr_*
* captures the syscall_nr of the FIRST step that fired each signal, so
* chain_corpus_save's per-(reason, nr) per-window cap is bounded by the
* actual triggering syscall and not the last step that happened to run.
*/
struct chain_run_state {
struct chain_step steps[MAX_SEQ_LEN];
unsigned int steps_recorded;
struct chain_entry replay;
unsigned int len;
bool replaying;
bool chain_found_new;
bool chain_new_transition;
bool chain_new_cmp;
unsigned int trigger_nr_pc;
unsigned int trigger_nr_transition;
unsigned int trigger_nr_cmp;
/* Producer-kind mask carried across steps of the same chain.
* Bit k set means at least one step so far in this chain
* classify_producer'd as kind k with a non-negative retval.
* Consulted by the next step's chain-restype hook (drives the
* consumer-bias override) and by record_chain_outcome's
* per-kind save/replay-win accounting. */
unsigned int producer_kinds_seen;
/* Producer-followed-by-consumer pair mask. Bit k set means
* some step in this chain classify_producer'd as kind k AND a
* strictly-later step classify_consumer'd as kind k. This is
* the mask record_chain_outcome uses to decide which
* chain_restype_save[k] / chain_restype_replay_win[k] slots to
* bump: a producer-only chain isn't the signal we're trying
* to reward. */
unsigned int pair_kinds_seen;
};
/*
* Chain-restype hook, run before dispatching step i (>= 1) whenever
* some earlier step in this chain was classified as a resource
* producer (producer_kinds_seen != 0). Returns the biased NR to use
* for this step (via @bias_nr_out) plus its do32bit flag; returns
* false when either the mode does not permit an override, no kind in
* the mask has a resolved consumer for the current dispatch arch, or
* the accept-probability roll (LIVE only) rejected the override.
*
* The per-kind chain_restype_would_bias / chain_restype_biased
* counters are bumped INSIDE this helper so the classifier's
* observability lands whether or not the caller ultimately overrides
* -- consistent with the "always-on classify counters" contract in
* the mode-enum comment. SHADOW never consumes RNG (the pick stream
* stays byte-identical to OFF); LIVE consumes exactly two draws when
* the accept passes (kind selection + consumer selection).
*/
static bool chain_restype_apply_bias(const struct chain_run_state *s,
bool do32bit_hint,
unsigned int *bias_nr_out,
bool *bias_do32_out)
{
unsigned int mask = s->producer_kinds_seen;
unsigned int k;
int available[CHAIN_RESTYPE_NR];
unsigned int navailable = 0;
if (chain_resource_typing_mode == CHAIN_RESTYPE_MODE_OFF)
return false;
if (mask == 0)
return false;
/* Collect the kinds that (a) are set in mask and (b) have at
* least one resolved consumer NR for this arch. A kind with
* an empty consumer table on the current arch cannot bias --
* counting it toward would_bias would inflate the measurement
* with picks we could never dispatch. */
for (k = 0; k < CHAIN_RESTYPE_NR; k++) {
if ((mask & (1u << k)) == 0)
continue;
if (!chain_restype_has_consumer((enum chain_resource_kind)k,
do32bit_hint))
continue;
available[navailable++] = (int)k;
}
if (navailable == 0)
return false;
if (chain_resource_typing_mode == CHAIN_RESTYPE_MODE_SHADOW) {
/* Shadow: count every available kind as "would_bias"
* without consuming RNG. The pick stream stays
* identical to OFF; the counter only says "the LIVE arm
* would have had these kinds available to override
* with", which is the SHADOW mode's whole contract. */
unsigned int i;
for (i = 0; i < navailable; i++)
__atomic_fetch_add(
&shm->stats.chain_restype_would_bias[available[i]],
1UL, __ATOMIC_RELAXED);
return false;
}
/* LIVE. Probabilistic bias: 50% of the time (ONE_IN(2)) we
* override, otherwise we let the plain random_syscall_step run
* so other links stay possible. Rationale for 50%: we want
* strong enough steering that the productivity signal in
* chain_restype_replay_win is legible above the fresh-chain
* noise floor, without pinning the mid-chain slot to the
* consumer table -- 100% override would starve the discovery
* paths that Phase 1's uniform pick relies on. */
if (!ONE_IN(2))
return false;
{
enum chain_resource_kind chosen_kind =
(enum chain_resource_kind)
available[rnd_modulo_u32(navailable)];
int consumer_nr = chain_restype_pick_consumer(chosen_kind,
do32bit_hint);
if (consumer_nr < 0)
return false;
*bias_nr_out = (unsigned int)consumer_nr;
*bias_do32_out = do32bit_hint;
__atomic_fetch_add(
&shm->stats.chain_restype_biased[chosen_kind],
1UL, __ATOMIC_RELAXED);
return true;
}
}
static void select_chain_source(struct chain_run_state *s)
{
/* With CHAIN_REPLAY_RATIO probability, try to replay a saved chain
* rather than generate a fresh one. Falls back to fresh if the
* corpus is empty (warm-start) or if the picked chain is rejected
* mid-replay by replay_syscall_step's safety checks (deactivated
* syscall, sanitise that wasn't there at save time, etc.). */
if (chain_corpus_shm != NULL && ONE_IN(CHAIN_REPLAY_RATIO) &&
chain_corpus_pick(&s->replay)) {
/* chain_corpus_pick() is intentionally lockless and the
* ring lives in shared memory that fuzzed syscalls can
* scribble. A torn read or a wild write into the slot's
* len field would let the loop below index past the
* fixed-size replay.steps[MAX_SEQ_LEN] before the per-
* step safety checks in replay_syscall_step ever ran.
* Reject the picked entry and fall back to a fresh chain
* if len escapes the [1, MAX_SEQ_LEN] range. */
if (s->replay.len == 0 || s->replay.len > MAX_SEQ_LEN) {
__atomic_fetch_add(&shm->stats.chain_replay_len_corrupt,
1UL, __ATOMIC_RELAXED);
s->len = pick_chain_length();
} else if (s->replay.save_reason != CHAIN_SAVE_PC &&
s->replay.save_reason < CHAIN_SAVE_NR_REASONS &&
!ONE_IN(CHAIN_REPLAY_NONPC_DOWNSAMPLE)) {
/* Non-PC-saved chains replay at a lower rate than
* PC-saved ones until per-reason productivity data
* exists (chain_replay_win_by_reason). Fall back to
* a fresh chain on the down-sampled half so the
* iteration still does useful work. */
s->len = pick_chain_length();
} else {
s->replaying = true;
s->len = s->replay.len;
__atomic_fetch_add(&chain_corpus_shm->replay_count, 1UL,
__ATOMIC_RELAXED);
}
} else {
s->len = pick_chain_length();
}
}
static bool execute_chain_steps(struct childdata *child,
struct chain_run_state *s)
{
struct syscallrecord *rec = &child->syscall;
bool have_substitute = false;
unsigned long substitute_retval = 0;
unsigned int i;
for (i = 0; i < s->len; i++) {
bool step_ret;
bool step_found_new = false;
unsigned long step_new_transition = 0;
unsigned long step_new_cmp = 0;
unsigned long rv;
unsigned int bias_nr = 0;
bool bias_do32 = false;
bool use_bias = false;
/* Mark steps i >= 1 of a fresh-generation chain as mid-chain
* so anything that wants to distinguish a chained dispatch
* from a standalone call can do so. Step 0 and replay steps
* leave the flag clear. */
child->in_chain_mid_step = (i > 0) && !s->replaying;
/* Chain-restype hook. Only fires for mid-chain fresh
* generation steps (i >= 1, !replaying) where a prior step
* classified as a producer. In SHADOW/LIVE this bumps the
* per-kind observability counters; LIVE additionally hands
* back a specific consumer NR to override the picker with.
* Replay steps take the saved (nr, args) verbatim and are
* outside the bias contract -- overriding a replayed step's
* NR would break the replay contract with the corpus. */
if (i > 0 && !s->replaying && s->producer_kinds_seen != 0) {
bool do32_hint = biarch ? rec->do32bit : false;
use_bias = chain_restype_apply_bias(s, do32_hint,
&bias_nr,
&bias_do32);
}
if (s->replaying) {
step_ret = replay_syscall_step(child,
&s->replay.steps[i],
have_substitute,
substitute_retval,
&step_found_new,
&step_new_transition,
&step_new_cmp);
if (step_ret == FAIL) {
/* Replay safety check failed (saved syscall
* has been deactivated or otherwise become
* unreplayable since save). Drop into fresh
* generation for the rest of the chain so the
* iteration still does useful work. The
* fallthrough fresh call is still step i, so
* re-evaluate the mid-chain flag after clearing
* replaying. */
s->replaying = false;
child->in_chain_mid_step = (i > 0);
step_ret = random_syscall_step(child,
have_substitute,
substitute_retval,
&step_found_new,
&step_new_transition,
&step_new_cmp);
}
} else if (use_bias) {
step_ret = random_syscall_step_biased(child,
bias_nr, bias_do32,
have_substitute,
substitute_retval,
&step_found_new,
&step_new_transition,
&step_new_cmp);
if (step_ret == FAIL) {
/* Biased NR became uncallable between the