-
Notifications
You must be signed in to change notification settings - Fork 243
Expand file tree
/
Copy pathstruct_catalog.c
More file actions
8771 lines (8330 loc) · 343 KB
/
Copy pathstruct_catalog.c
File metadata and controls
8771 lines (8330 loc) · 343 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
/*
* Struct catalog and offset mapping for CMP-guided struct filling.
*
* Provides a static catalog of known struct types (with per-field offset
* and size), a table mapping syscall args to those struct types, and a
* fast nr-indexed lookup built at init time.
*
* The field-for-CMP heuristic uses value magnitude to narrow which field
* a kernel CMP constant was most likely comparing against.
*/
#include <stddef.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <sys/resource.h>
#include <sys/epoll.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sched.h>
#include <time.h>
#include <utime.h>
#include <netinet/in.h>
#include <linux/filter.h>
#include <linux/netlink.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/tipc.h>
#include <linux/capability.h>
#include <linux/netfilter.h>
#include <linux/futex.h>
#include <linux/rseq.h>
#include <linux/sched.h>
#include <linux/sched/types.h>
#include <linux/io_uring.h>
#include <linux/landlock.h>
#include <linux/mman.h>
#include <linux/mount.h>
#include <linux/fs.h>
#include <linux/quota.h>
#include <linux/dqblk_xfs.h>
#include <mqueue.h>
#include "config.h"
/*
* linux/if_pppox.h pulls in linux/l2tp.h, whose enum declares
* L2TP_ATTR_IP6_SADDR / RX_COOKIE_DISCARDS / ... as identifiers.
* compat.h defines those same names as fallback numeric macros for
* older kernel-headers packages, so the include must precede compat.h;
* otherwise the macro expansion turns the enum members into integer
* literals and -Werror trips.
*/
#ifdef USE_PPPOX
#include <linux/if_pppox.h>
#endif
#include "compat.h"
#ifdef USE_BPF
#include <linux/bpf.h>
#endif
#ifdef USE_VSOCK
#include <linux/vm_sockets.h>
#endif
#ifdef USE_CAIF
#include <linux/caif/caif_socket.h>
#endif
#ifdef USE_CAN
#include <linux/can.h>
#endif
#ifdef USE_RXRPC
#include <linux/rxrpc.h>
#endif
#ifdef USE_X25
#include <linux/x25.h>
#endif
#ifdef USE_PHONET
#include <linux/phonet.h>
#endif
#ifdef USE_AX25
#include <linux/ax25.h>
#endif
#ifdef USE_ROSE
#include <linux/rose.h>
#endif
#ifdef USE_ATALK
#include <linux/atalk.h>
#endif
#ifdef USE_ATM
#include <linux/atm.h>
#endif
#ifdef USE_LLC
#include <linux/llc.h>
#endif
#ifdef USE_MCTP
#include <linux/mctp.h>
#endif
#ifdef USE_IF_ALG
#include <linux/if_alg.h>
#endif
#ifdef USE_XDP
#include <linux/if_xdp.h>
/*
* XDP_USE_NEED_WAKEUP landed in 5.4 (commit 77cd0d7b3f25); older
* toolchain headers won't carry it even when the rest of the
* sockaddr_xdp definitions are present. Fall back to the upstream
* bit value so the FT_FLAGS mask stays the same on either side.
*/
#ifndef XDP_USE_NEED_WAKEUP
#define XDP_USE_NEED_WAKEUP (1 << 3)
#endif
#endif
#ifdef USE_XATTR_ARGS
#include <linux/xattr.h>
#endif
#ifdef USE_SCTP
#include <linux/sctp.h>
#endif
#ifdef USE_TCP_REPAIR_OPT
#include <linux/tcp.h>
#endif
#include "argtype-ops.h"
#include "struct_catalog.h"
#include "arch.h"
#ifdef X86
#include <asm/ldt.h> /* struct user_desc -- modify_ldt arg2 */
#endif
#include "debug.h"
#include "perf_event.h"
#include "random.h"
#include "tables.h"
#include "trinity.h"
#include "utils.h"
/*
* FIELD(S, m): the FT_RAW shortcut. Tag, weight, and the .u payload
* stay zero-initialised, so the field falls through to the historical
* per-field random splat. Existing entries keep this form.
*/
#define FIELD(S, m) \
{ .name = #m, \
.offset = offsetof(S, m), \
.size = sizeof(((S *)NULL)->m) }
/*
* FIELDX(S, m, TAG, ...): the semantic form. Trailing __VA_ARGS__
* carries the tag-specific designated initialisers, typically
* .u.<arm> = { ... } and/or .mutate_weight = N.
*/
#define FIELDX(S, m, TAG, ...) \
{ .name = #m, \
.offset = offsetof(S, m), \
.size = sizeof(((S *)NULL)->m), \
.tag = (TAG), \
__VA_ARGS__ }
/* ------------------------------------------------------------------ */
/* struct timex (adjtimex, clock_adjtime) */
/* ------------------------------------------------------------------ */
/*
* ADJ_* mode-bit vocabulary for timex.modes. Anything outside the
* mask causes the kernel to reject the call before any clock state is
* read, so an FT_RAW splat almost never reaches the do_adjtimex()
* dispatch. Mask values are stable in linux/timex.h; new ADJ_* bits
* are rare and caught by reviewer reading the uapi diff.
*/
#define TIMEX_MODES_MASK \
(ADJ_OFFSET | ADJ_FREQUENCY | ADJ_MAXERROR | ADJ_ESTERROR | \
ADJ_STATUS | ADJ_TIMECONST | ADJ_TAI | ADJ_SETOFFSET | \
ADJ_MICRO | ADJ_NANO | ADJ_TICK)
static const struct struct_field timex_fields[] = {
FIELDX(struct timex, modes, FT_FLAGS,
.u.flags.mask = TIMEX_MODES_MASK,
.mutate_weight = 80),
FIELD(struct timex, offset),
FIELD(struct timex, freq),
FIELD(struct timex, maxerror),
FIELD(struct timex, esterror),
FIELD(struct timex, status),
FIELD(struct timex, constant),
FIELD(struct timex, precision),
FIELD(struct timex, tolerance),
FIELD(struct timex, tick),
FIELD(struct timex, ppsfreq),
FIELD(struct timex, jitter),
FIELD(struct timex, shift),
FIELD(struct timex, stabil),
FIELD(struct timex, jitcnt),
FIELD(struct timex, calcnt),
FIELD(struct timex, errcnt),
FIELD(struct timex, stbcnt),
};
/* ------------------------------------------------------------------ */
/* struct sched_attr (sched_setattr, sched_getattr) */
/* ------------------------------------------------------------------ */
static const struct struct_field sched_attr_fields[] = {
FIELD(struct sched_attr, size),
FIELD(struct sched_attr, sched_policy),
FIELD(struct sched_attr, sched_flags),
FIELD(struct sched_attr, sched_nice),
FIELD(struct sched_attr, sched_priority),
FIELD(struct sched_attr, sched_runtime),
FIELD(struct sched_attr, sched_deadline),
FIELD(struct sched_attr, sched_period),
FIELD(struct sched_attr, sched_util_min),
FIELD(struct sched_attr, sched_util_max),
};
/* ------------------------------------------------------------------ */
/* struct clone_args (clone3) */
/* ------------------------------------------------------------------ */
static const struct struct_field clone_args_fields[] = {
FIELD(struct clone_args, flags),
FIELD(struct clone_args, pidfd),
FIELD(struct clone_args, child_tid),
FIELD(struct clone_args, parent_tid),
FIELD(struct clone_args, exit_signal),
FIELD(struct clone_args, stack),
FIELD(struct clone_args, stack_size),
FIELD(struct clone_args, tls),
FIELD(struct clone_args, set_tid),
FIELD(struct clone_args, set_tid_size),
FIELD(struct clone_args, cgroup),
};
/* ------------------------------------------------------------------ */
/* struct io_uring_params (io_uring_setup) */
/* ------------------------------------------------------------------ */
/*
* IORING_SETUP_* vocabulary for io_uring_params.flags. Mirrors the
* curated set in io_uring_setup.c's set_rand_bitmask() array — kept in
* sync by reviewer reading the uapi diff. Compat #ifndef arms cover
* bits the system header may pre-date; newer bits (CQE_MIXED, SQE_MIXED,
* SQ_REWIND in io_uring_setup.c) are deliberately omitted here since
* neither <linux/io_uring.h> nor the upstream uapi exposes them yet.
*/
#ifndef IORING_SETUP_NO_MMAP
#define IORING_SETUP_NO_MMAP (1U << 14)
#define IORING_SETUP_REGISTERED_FD_ONLY (1U << 15)
#endif
#ifndef IORING_SETUP_NO_SQARRAY
#define IORING_SETUP_NO_SQARRAY (1U << 16)
#endif
#ifndef IORING_SETUP_HYBRID_IOPOLL
#define IORING_SETUP_HYBRID_IOPOLL (1U << 17)
#endif
#define IORING_SETUP_MASK \
(IORING_SETUP_IOPOLL | IORING_SETUP_SQPOLL | \
IORING_SETUP_SQ_AFF | IORING_SETUP_CQSIZE | \
IORING_SETUP_CLAMP | IORING_SETUP_ATTACH_WQ | \
IORING_SETUP_R_DISABLED | IORING_SETUP_SUBMIT_ALL | \
IORING_SETUP_COOP_TASKRUN | IORING_SETUP_TASKRUN_FLAG | \
IORING_SETUP_SQE128 | IORING_SETUP_CQE32 | \
IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN | \
IORING_SETUP_NO_MMAP | IORING_SETUP_REGISTERED_FD_ONLY | \
IORING_SETUP_NO_SQARRAY | IORING_SETUP_HYBRID_IOPOLL)
/*
* sq_entries / cq_entries: the kernel rounds up to power-of-two via
* roundup_pow_of_two() regardless of the value passed, so FT_RANGE would
* only obscure the rare interesting cases (zero -> -EINVAL; values above
* IORING_MAX_ENTRIES -> capped). Leave FT_RAW and lean on the mutate
* weight to shake those edges out; cq_entries is also gated by SETUP_CQSIZE
* so the field is silently ignored most of the time.
*
* features is kernel-written output; sq_off / cq_off are
* io_sqring_offsets / io_cqring_offsets, also output-only, and stay
* uncataloged until an OUTPUT-fill mode exists. resv[3] is rejected by
* the kernel's memchr_inv() check on non-zero, so FT_RAW on a zeroed
* buffer is the right answer.
*/
static const struct struct_field io_uring_params_fields[] = {
FIELDX(struct io_uring_params, sq_entries, FT_RAW,
.mutate_weight = 60),
FIELDX(struct io_uring_params, cq_entries, FT_RAW,
.mutate_weight = 60),
FIELDX(struct io_uring_params, flags, FT_FLAGS,
.u.flags.mask = IORING_SETUP_MASK,
.mutate_weight = 100),
FIELD(struct io_uring_params, sq_thread_cpu),
FIELD(struct io_uring_params, sq_thread_idle),
FIELD(struct io_uring_params, features),
FIELDX(struct io_uring_params, wq_fd, FT_FD,
.mutate_weight = 80),
};
/* ------------------------------------------------------------------ */
/* struct rlimit (setrlimit, getrlimit, prlimit64) */
/* ------------------------------------------------------------------ */
static const struct struct_field rlimit_fields[] = {
FIELD(struct rlimit, rlim_cur),
FIELD(struct rlimit, rlim_max),
};
/* ------------------------------------------------------------------ */
/* struct itimerspec (timer_settime, timerfd_settime) */
/* ------------------------------------------------------------------ */
static const struct struct_field itimerspec_fields[] = {
FIELD(struct itimerspec, it_interval.tv_sec),
FIELD(struct itimerspec, it_interval.tv_nsec),
FIELD(struct itimerspec, it_value.tv_sec),
FIELD(struct itimerspec, it_value.tv_nsec),
};
/* ------------------------------------------------------------------ */
/* struct timespec (clock_nanosleep, nanosleep, utimensat) */
/* ------------------------------------------------------------------ */
/*
* tv_nsec is rejected by the kernel for values outside [0, 1e9) before
* the syscall does any real work, so an FT_RAW splat almost never lands
* on the wait/update path. Keep tv_sec as an unbounded FT_RANGE so
* absolute / past / future buckets stay reachable; pin tv_nsec to the
* legal nanosecond range so the request actually clears the kernel's
* input check. Callers that want UTIME_NOW / UTIME_OMIT (utimensat)
* still construct those values in their own sanitise callback.
*/
static const struct struct_field timespec_fields[] = {
FIELDX(struct timespec, tv_sec, FT_RANGE,
.u.range = { 0, 4000000000UL },
.mutate_weight = 60),
FIELDX(struct timespec, tv_nsec, FT_RANGE,
.u.range = { 0, 999999999UL },
.mutate_weight = 60),
};
/* ------------------------------------------------------------------ */
/* struct cachestat_range (cachestat) */
/* ------------------------------------------------------------------ */
/*
* cachestat's input range struct: a (off, len) byte pair the kernel
* walks across the file's address_space. cachestat already carries a
* strong bespoke sanitiser (pick_range() in syscalls/cachestat.c) that
* picks a file-size-aware off/len -- the registration here is
* attribution-only: cachestat's argtype slot is not ARG_STRUCT_PTR_*,
* so the schema-aware fill path never fires and pick_range() continues
* to own the live values. FT_RANGE annotations exist so KCOV CMP
* constants can be attributed to off or len rather than landing on a
* coincidentally-same-width slot. Bounds mirror the timespec
* precedent's u32-fitting ceiling so the catalog stays portable on
* 32-bit unsigned long builds.
*/
static const struct struct_field cachestat_range_fields[] = {
FIELDX(struct cachestat_range, off, FT_RANGE,
.u.range = { 0, 4000000000UL },
.mutate_weight = 60),
FIELDX(struct cachestat_range, len, FT_RANGE,
.u.range = { 0, 4000000000UL },
.mutate_weight = 60),
};
/* ------------------------------------------------------------------ */
/* struct mount_attr (mount_setattr, open_tree_attr) */
/* ------------------------------------------------------------------ */
/*
* MOUNT_ATTR_* bit vocabulary. ifndef-guarded to match the pattern in
* syscalls/mount.c and syscalls/fsmount.c -- older toolchain headers
* may pre-date the IDMAP / NOSYMFOLLOW additions. struct mount_attr
* itself is presumed available via <linux/mount.h>; if the host header
* is too old to carry the struct the build already fails in
* open_tree_attr.c, not here.
*/
#ifndef MOUNT_ATTR_RDONLY
#define MOUNT_ATTR_RDONLY 0x00000001
#define MOUNT_ATTR_NOSUID 0x00000002
#define MOUNT_ATTR_NODEV 0x00000004
#define MOUNT_ATTR_NOEXEC 0x00000008
#define MOUNT_ATTR_NOATIME 0x00000010
#define MOUNT_ATTR_STRICTATIME 0x00000020
#define MOUNT_ATTR_NODIRATIME 0x00000080
#define MOUNT_ATTR_IDMAP 0x00100000
#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000
#endif
#define MOUNT_ATTR_ALL_MASK \
(MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | \
MOUNT_ATTR_NOEXEC | MOUNT_ATTR_NOATIME | MOUNT_ATTR_STRICTATIME | \
MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_IDMAP | MOUNT_ATTR_NOSYMFOLLOW)
/*
* propagation is effectively a 4-valued enum: do_change_type() EINVALs
* the moment two propagation bits appear together, so FT_FLAGS would
* be wrong here -- the mutator would happily OR a second bit in and
* trip the validator. FT_ENUM over the four MS_* propagation
* constants keeps the mutator inside the legal one-bit shape.
*/
static const unsigned long mount_attr_propagation_values[] = {
MS_SHARED, MS_PRIVATE, MS_SLAVE, MS_UNBINDABLE,
};
/*
* mount_setattr / open_tree_attr already carry strong bespoke
* sanitisers (build_mount_attr() in syscalls/open_tree_attr.c, mirrored
* by sanitise_mount_setattr) that pick coherent attr_set / attr_clr /
* propagation / userns_fd buckets and respect the kernel's mutually-
* exclusive ATIME-mode and propagation rules. Those sanitisers
* overwrite rec->a4 wholesale after gen_arg_struct_ptr_in's schema-
* aware fill, so the registration here is attribution-only --
* struct_field_for_cmp() uses the FT_FLAGS / FT_ENUM / FT_FD tags to
* steer KCOV-CMP learned constants at the right field rather than at a
* coincidentally-same-width slot. The bespoke fill stays live; this
* entry never displaces it. Same shape as cachestat_range above and
* the io_uring_register_args entry below.
*/
static const struct struct_field mount_attr_fields[] = {
FIELDX(struct mount_attr, attr_set, FT_FLAGS,
.u.flags.mask = MOUNT_ATTR_ALL_MASK,
.mutate_weight = 100),
FIELDX(struct mount_attr, attr_clr, FT_FLAGS,
.u.flags.mask = MOUNT_ATTR_ALL_MASK,
.mutate_weight = 80),
FIELDX(struct mount_attr, propagation, FT_ENUM,
.u.enum_ = { mount_attr_propagation_values,
ARRAY_SIZE(mount_attr_propagation_values) },
.mutate_weight = 80),
FIELDX(struct mount_attr, userns_fd, FT_FD,
.mutate_weight = 60),
};
/* ------------------------------------------------------------------ */
/* struct sembuf (semop, semtimedop) */
/* ------------------------------------------------------------------ */
/*
* sem{,timed}op pass an ARRAY of sembuf at a2 (nsops in a3), not a
* single struct, and the arg slot is ARG_ADDRESS rather than
* ARG_STRUCT_PTR_*. The bespoke fill_sembuf_array() helpers in
* syscalls/semop.c and syscalls/semtimedop.c allocate the buffer,
* pick a per-element (sem_num, sem_op, sem_flg) triple respecting
* the kernel's nsems / IPC_NOWAIT / SEM_UNDO semantics, and overwrite
* rec->a2 -- the schema-aware fill path never runs for this slot.
*
* Registration is attribution-only, mirroring cachestat_range /
* mount_attr above: struct_field_for_cmp() uses the FT_RANGE /
* FT_FLAGS tags to steer KCOV-CMP learned constants at sem_num or
* sem_flg rather than at a coincidentally-same-width slot. sem_op
* stays FT_RAW: its kernel semantics are arithmetic
* (sma->sem_base[].semval + sem_op) rather than a vocab CMP, so no
* gate-tag lift would help attribution. sem_num's range upper bound
* mirrors syscalls/semop.c's pick_sem_num() worst-case
* (SEMOP_FALLBACK_NSEMS + 63 = 95) so future schema consumers stay
* inside the same in-range / out-of-range envelope the bespoke
* sanitiser already explores.
*/
static const struct struct_field sembuf_fields[] = {
FIELDX(struct sembuf, sem_num, FT_RANGE,
.u.range = { 0, 95 },
.mutate_weight = 60),
FIELD(struct sembuf, sem_op),
FIELDX(struct sembuf, sem_flg, FT_FLAGS,
.u.flags.mask = IPC_NOWAIT | SEM_UNDO,
.mutate_weight = 80),
};
/* ------------------------------------------------------------------ */
/* struct pollfd (poll, ppoll) */
/* ------------------------------------------------------------------ */
/*
* poll and ppoll pass an ARRAY of pollfd at a1 (nfds in a2), not a
* single struct, and the arg slot is ARG_ADDRESS rather than
* ARG_STRUCT_PTR_*. The bespoke alloc_pollfds() helper in
* syscalls/poll.c allocates the buffer, picks each entry's
* (fd, events) tuple from the pollable-fd pool plus a curated event
* vocabulary, and overwrites rec->a1 -- the schema-aware fill path
* never runs for this slot.
*
* Registration is attribution-only, mirroring sembuf above:
* struct_field_for_cmp() uses the FT_FD / FT_FLAGS tags to steer
* KCOV-CMP learned constants at the fd or events slot rather than at
* a coincidentally-same-width slot. revents is the kernel-written
* output half of this value-result buffer and stays FT_RAW: no
* userspace-side vocab applies, and FT_FLAGS attribution against the
* kernel-chosen revents bitmask would mislead the heuristic.
*/
#define POLLFD_EVENTS_MASK \
(POLLIN | POLLOUT | POLLPRI | POLLERR | \
POLLHUP | POLLNVAL | POLLRDHUP)
static const struct struct_field pollfd_fields[] = {
FIELDX(struct pollfd, fd, FT_FD,
.mutate_weight = 80),
FIELDX(struct pollfd, events, FT_FLAGS,
.u.flags.mask = POLLFD_EVENTS_MASK,
.mutate_weight = 80),
FIELD(struct pollfd, revents),
};
/* ------------------------------------------------------------------ */
/* struct open_how (openat2) */
/* ------------------------------------------------------------------ */
/*
* struct open_how / RESOLVE_* may not be present in every host's
* <linux/openat2.h>; mirror the trinity-local fallback already used
* by syscalls/open.c so this TU compiles on toolchains that pre-date
* the openat2 uapi. The ifndef guard hands off to the host header
* (or to whichever earlier-included TU has already pulled the symbols
* in) when it is present.
*/
#ifndef RESOLVE_NO_XDEV
struct open_how {
__u64 flags;
__u64 mode;
__u64 resolve;
};
#define RESOLVE_NO_XDEV 0x01
#define RESOLVE_NO_MAGICLINKS 0x02
#define RESOLVE_NO_SYMLINKS 0x04
#define RESOLVE_BENEATH 0x08
#define RESOLVE_IN_ROOT 0x10
#define RESOLVE_CACHED 0x20
#endif
/*
* openat2 passes struct open_how at a3 with a usize at a4
* (copy_struct_from_user semantics). The slot is ARG_ADDRESS rather
* than ARG_STRUCT_PTR_*, so the schema-aware fill path never runs
* against it -- the bespoke sanitise_openat2() in syscalls/open.c
* continues to own the live (flags, mode, resolve) layout, including
* the O_CREAT / __O_TMPFILE-gated mode write and the curated
* openat2_resolve_combos[] table that walks the namei RESOLVE_*
* paths the kernel actually branches on.
*
* Registration is attribution-only, mirroring pollfd / sembuf above:
* struct_field_for_cmp() uses the FT_FLAGS tags to steer KCOV-CMP
* learned constants at the flags or resolve slot rather than at a
* coincidentally-same-width slot. mode stays FT_RAW: the kernel
* only honours it (masked to S_IALLUGO) when O_CREAT / __O_TMPFILE
* is set, otherwise a non-zero mode trips the -EINVAL gate before
* any per-bit CMP fires -- no single-field vocab maps cleanly.
*/
#define OPEN_HOW_FLAGS_MASK \
(O_ACCMODE | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | O_APPEND | \
O_NONBLOCK | O_DSYNC | O_SYNC | O_ASYNC | O_DIRECTORY | \
O_NOFOLLOW | O_CLOEXEC | O_DIRECT | O_NOATIME | O_PATH | \
O_LARGEFILE | O_TMPFILE)
#define OPEN_HOW_RESOLVE_MASK \
(RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS |\
RESOLVE_BENEATH | RESOLVE_IN_ROOT | RESOLVE_CACHED)
static const struct struct_field open_how_fields[] = {
FIELDX(struct open_how, flags, FT_FLAGS,
.u.flags.mask = OPEN_HOW_FLAGS_MASK,
.mutate_weight = 100),
FIELD(struct open_how, mode),
FIELDX(struct open_how, resolve, FT_FLAGS,
.u.flags.mask = OPEN_HOW_RESOLVE_MASK,
.mutate_weight = 80),
};
/* ------------------------------------------------------------------ */
/* struct sigevent (timer_create) */
/* ------------------------------------------------------------------ */
/*
* timer_create(clockid_t, struct sigevent *, timer_t *) passes the
* sigevent at a2 with argtype ARG_ADDRESS (not ARG_STRUCT_PTR_*), so
* the schema-aware fill path never runs against it -- the bespoke
* timer_create_sanitise() in syscalls/timer_create.c continues to own
* the live (sigev_value, sigev_signo, sigev_notify, _sigev_un._tid)
* layout, including the SIGEV_NONE / SIGEV_SIGNAL / SIGEV_THREAD_ID /
* (SIGEV_SIGNAL | SIGEV_THREAD_ID) notify-mode distribution and the
* gettid-derived _tid fill on the THREAD_ID arms.
*
* Registration is attribution-only, mirroring pollfd / sembuf /
* open_how above: struct_field_for_cmp() uses the FT_ENUM tag to
* steer KCOV-CMP learned constants at sigev_notify (a 4-valued
* discrete vocab the kernel branches on in do_timer_create) and the
* FT_RANGE tag to attribute small ints at sigev_signo rather than at
* a coincidentally-same-width slot. sigev_value and the _sigev_un
* union stay FT_RAW: sigev_value is an opaque cookie the kernel
* stores and replays without any per-bit CMP, and the union arms are
* a tagged-by-sigev_notify payload (a thread tid, or a pair of
* user-space pointers) with no useful CMP vocab -- no single-field
* vocab maps cleanly across the arms, so attribution-only with no
* invented tag is the right call. sigev_signo upper bound is _NSIG
* (64 on Linux); the bespoke pick_signo_avoiding_sigint() already
* draws from rnd_modulo_u32(_NSIG) so the range envelope matches.
*/
static const unsigned long sigevent_notify_values[] = {
SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD, SIGEV_THREAD_ID,
};
static const struct struct_field sigevent_fields[] = {
FIELD(struct sigevent, sigev_value),
FIELDX(struct sigevent, sigev_signo, FT_RANGE,
.u.range = { 1, 64 },
.mutate_weight = 60),
FIELDX(struct sigevent, sigev_notify, FT_ENUM,
.u.enum_ = { sigevent_notify_values,
ARRAY_SIZE(sigevent_notify_values) },
.mutate_weight = 80),
FIELD(struct sigevent, _sigev_un),
};
/* ------------------------------------------------------------------ */
/* struct robust_list_head (set_robust_list) */
/* ------------------------------------------------------------------ */
/*
* set_robust_list(struct robust_list_head __user *head, size_t len)
* passes the head pointer at a1 with argtype ARG_ADDRESS (not
* ARG_STRUCT_PTR_*), so the schema-aware fill path never runs against
* it -- the bespoke sanitise_set_robust_list() in
* syscalls/set_robust_list.c continues to own the live (list.next,
* futex_offset, list_op_pending) layout: it zmalloc_tracked()s a head,
* self-points list.next, zeros futex_offset, and NULLs list_op_pending
* before each call.
*
* Registration is attribution-only, mirroring pollfd / sembuf /
* open_how / sigevent above: struct_field_for_cmp() uses the FT_RANGE
* tag to attribute small-int CMP constants at futex_offset rather than
* at a coincidentally-same-width slot, and FT_ADDRESS on the embedded
* list (whose first member is a __user "next" pointer) and on
* list_op_pending documents the kernel-dereferenced slots for any
* downstream nested-scrub walker. futex_offset bounds envelope the
* page-sized window the kernel walks across the robust list node.
*
* get_robust_list's robust_list_head is an OUTPUT (its a2 is a double
* pointer the kernel writes), so the syscall_struct_args[] mapping
* below names set_robust_list a1 only.
*/
static const struct struct_field robust_list_head_fields[] = {
FIELDX(struct robust_list_head, list, FT_ADDRESS,
.mutate_weight = 100),
FIELDX(struct robust_list_head, futex_offset, FT_RANGE,
.u.range = { 0, 4096 },
.mutate_weight = 60),
FIELDX(struct robust_list_head, list_op_pending, FT_ADDRESS,
.mutate_weight = 100),
};
/* ------------------------------------------------------------------ */
/* struct rseq (rseq) */
/* ------------------------------------------------------------------ */
/*
* rseq(struct rseq __user *rseq, u32 rseq_len, int flags, u32 sig)
* passes the rseq pointer at a1. The bespoke sanitise_rseq() in
* syscalls/rseq.c continues to own the live fill: it allocates a
* 32-byte-aligned struct rseq via get_writable_address(),
* memset()s it to zero, routes a1 through avoid_shared_buffer_inout(),
* cycles a2 through the rseq_len validation buckets (zero / undersized
* / current ABI / oversized), and pins a4 to a fixed signature.
*
* Registration is attribution-only, mirroring robust_list_head /
* pollfd / sembuf / open_how / sigevent above: struct_field_for_cmp()
* uses the FT_RANGE tags to attribute small-int CMP constants at the
* cpu_id / node_id / mm_cid slots rather than at a coincidentally-
* same-width slot; FT_FLAGS on flags carries the RSEQ_CS_FLAG_*
* vocabulary the kernel reads at critical-section abort; FT_ADDRESS
* on rseq_cs documents the __user pointer slot the kernel
* dereferences to reach the active struct rseq_cs. cpu_id_start /
* cpu_id / node_id / mm_cid are kernel-written outputs whose userspace
* envelope still benefits from CMP attribution; the bounds mirror the
* page-sized walk envelopes the kernel uses to validate them. The
* abort signature is the syscall's a4 argument, not a struct member,
* so it has no field here. The trailing flexible char end[] member
* has no fixed offset/size and is not registered.
*/
static const struct struct_field rseq_fields[] = {
FIELDX(struct rseq, cpu_id_start, FT_RANGE,
.u.range = { 0, 4096 },
.mutate_weight = 60),
FIELDX(struct rseq, cpu_id, FT_RANGE,
.u.range = { 0, 4096 },
.mutate_weight = 60),
FIELDX(struct rseq, rseq_cs, FT_ADDRESS,
.mutate_weight = 100),
FIELDX(struct rseq, flags, FT_FLAGS,
.u.flags.mask = RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT |
RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL |
RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE,
.mutate_weight = 80),
FIELDX(struct rseq, node_id, FT_RANGE,
.u.range = { 0, 1024 },
.mutate_weight = 60),
FIELDX(struct rseq, mm_cid, FT_RANGE,
.u.range = { 0, 4096 },
.mutate_weight = 60),
};
/* ------------------------------------------------------------------ */
/* struct itimerval (setitimer) */
/* ------------------------------------------------------------------ */
/*
* setitimer(int which, const struct itimerval __user *value,
* struct itimerval __user *ovalue) passes the input itimerval
* at a2. The bespoke sanitise_setitimer() in syscalls/setitimer.c
* continues to own the live fill: it get_writable_address()es a struct
* itimerval, walks both embedded timevals through fill_timeval() (zero
* / sub-second / small-positive / random tv_sec buckets paired with a
* legal tv_usec), half the time disarms the timer by zeroing it_value,
* routes a2 to the writable buffer, and runs a3 through
* avoid_shared_buffer_out(). setitimer's argtype[1] is not
* ARG_STRUCT_PTR_*, so the schema-aware fill path never runs against
* it -- mirrors itimerspec / robust_list_head / rseq / pollfd / sembuf
* / open_how / sigevent above.
*
* Registration is attribution-only: struct_field_for_cmp() uses the
* FT_RANGE tags to attribute small-int CMP constants at the named
* tv_sec / tv_usec slots rather than at a coincidentally-same-width
* slot. Bounds mirror the timespec_fields[] precedent: tv_sec is left
* unbounded so absolute / past / future buckets stay reachable; tv_usec
* is pinned to the legal microsecond range so the request actually
* clears timeval_valid() inside the kernel's setitimer entry. Only
* setitimer's INPUT a2 is mapped below -- a3 (ovalue) is a kernel-
* written output, and getitimer's a2 is likewise an output, so neither
* is mapped.
*/
static const struct struct_field itimerval_fields[] = {
FIELDX(struct itimerval, it_interval.tv_sec, FT_RANGE,
.u.range = { 0, 4000000000UL },
.mutate_weight = 60),
FIELDX(struct itimerval, it_interval.tv_usec, FT_RANGE,
.u.range = { 0, 999999UL },
.mutate_weight = 60),
FIELDX(struct itimerval, it_value.tv_sec, FT_RANGE,
.u.range = { 0, 4000000000UL },
.mutate_weight = 60),
FIELDX(struct itimerval, it_value.tv_usec, FT_RANGE,
.u.range = { 0, 999999UL },
.mutate_weight = 60),
};
/* ------------------------------------------------------------------ */
/* struct utimbuf (utime) */
/* ------------------------------------------------------------------ */
/*
* utime(const char *filename, const struct utimbuf __user *times) passes
* the utimbuf at a2. utime has no bespoke .sanitise -- argtype[1] was
* ARG_ADDRESS, so the times buffer was filled as an undifferentiated
* address slot with no schema path of its own. Flipping argtype[1] to
* ARG_STRUCT_PTR_IN routes the slot through the schema-aware fill so it
* lands on a dedicated sized buffer, and the catalog entry below names
* the (actime, modtime) layout for CMP attribution.
*
* Both members are time_t and currently FT_RAW: the bytes match the
* historical random splat -- the win is the dedicated sized buffer and
* letting struct_field_for_cmp attribute KCOV CMP constants at the named
* actime / modtime fields rather than at a coincidentally-same-width
* slot. No FT_TIME tag exists in the catalog vocabulary today; adding
* one is deferred until a precedent for time_t-shaped semantic tagging
* lands across the other timespec / timeval consumers.
*/
static const struct struct_field utimbuf_fields[] = {
FIELD(struct utimbuf, actime),
FIELD(struct utimbuf, modtime),
};
/* ------------------------------------------------------------------ */
/* struct timeval (settimeofday, select) */
/* ------------------------------------------------------------------ */
/*
* struct timeval is the (tv_sec, tv_usec) pair the kernel takes at
* settimeofday's a1 (INPUT wall-clock value) and at select's a5
* (INOUT timeout). Both syscalls already carry a bespoke .sanitise
* that owns the live fill via get_writable_address(): settimeofday
* biases 70% near-now / 30% random with an explicit invalid-tv_usec
* leg, and select stamps a deterministic short {0, 10us} timeout.
* Without a catalog entry the slots were filled but had no schema
* path of their own, so struct_field_for_cmp() had nothing to hang
* KCOV-CMP attribution against and learned constants fell at a
* coincidentally-same-width slot rather than at a named field.
*
* Registration is attribution-only, mirroring the in-tree timespec /
* utimensat handling and the landed utimbuf / flock / sigevent
* commits: the bespoke sanitisers keep owning the fill -- this only
* feeds the CMP-attribution path. tv_sec stays FT_RAW so the
* near-now / random / wraparound bytes the bespoke fills already
* produce are preserved; tv_usec is pinned to the legal microsecond
* range so attribution at the named tv_usec slot lines up with the
* kernel's timeval_valid() check rather than landing on a
* coincidentally-same-width neighbour. Bound mirrors the
* itimerval_fields[] tv_usec precedent (0..999999).
*/
static const struct struct_field timeval_fields[] = {
FIELD(struct timeval, tv_sec),
FIELDX(struct timeval, tv_usec, FT_RANGE,
.u.range = { 0, 999999UL },
.mutate_weight = 60),
};
/* ------------------------------------------------------------------ */
/* struct timezone (settimeofday) */
/* ------------------------------------------------------------------ */
/*
* struct timezone is the (tz_minuteswest, tz_dsttime) pair settimeofday
* takes at a2. The bespoke sanitise_settimeofday() owns the live fill
* via get_writable_address(): a 50/50 zero-vs-random leg producing
* tz_minuteswest in [-780, +780] (-13h..+13h in minutes) and tz_dsttime
* in [0, 3]. Without a catalog entry the slot was filled but had no
* schema path of its own, so struct_field_for_cmp() had nothing to
* hang KCOV-CMP attribution against and learned constants fell at a
* coincidentally-same-width slot rather than at a named field.
*
* Registration is attribution-only, mirroring the in-tree timespec /
* utimensat handling and the landed timeval / utimbuf / flock commits:
* the bespoke sanitiser keeps owning the fill -- this only feeds the
* CMP-attribution path. tz_minuteswest is left FT_RAW: the live fill
* spans a signed window [-780, +780] and the FT_RANGE union carries
* unsigned bounds (struct { unsigned long lo, hi; } range), so a
* literal {-780, 780} would wrap to a garbage upper bound; the signed
* bytes the bespoke fill produces are preserved verbatim. tz_dsttime
* is all-positive [0, 3] and pins cleanly to FT_RANGE so attribution
* at the named slot lines up with the kernel's narrow legal window.
*/
static const struct struct_field timezone_fields[] = {
/*
* FT_RANGE's bounds are unsigned long, so this signed
* [-780, +780] minutes-west window cannot be expressed as a
* range; keep FT_RAW and let the bespoke fill own the value.
*/
FIELD(struct timezone, tz_minuteswest),
FIELDX(struct timezone, tz_dsttime, FT_RANGE,
.u.range = { 0, 3 },
.mutate_weight = 40),
};
/* ------------------------------------------------------------------ */
/* struct flock (fcntl) */
/* ------------------------------------------------------------------ */
/*
* fcntl's lock-pointer arg (F_GETLK / F_SETLK / F_SETLKW and the
* F_OFD_* variants) carries a struct flock at a3. The bespoke
* sanitise_fcntl() keeps owning the live fill via build_flock(): it
* picks an l_type / l_whence vocab member, a bounded l_start and
* l_len, and zeroes l_pid (F_OFD_SETLK requires it).
*
* Attribution-only registration, mirroring the mq_notify / sigevent
* pattern: struct_field_for_cmp() uses the FT_ENUM tags to steer
* KCOV-CMP learned constants at l_type (a 3-valued vocab the kernel
* branches on in posix_lock_inode) and l_whence (a 3-valued vocab the
* kernel uses to resolve the start offset), and FT_RAW on l_start /
* l_len / l_pid keeps attribution at the named range / pid slots
* rather than at a coincidentally-same-width slot. Without the
* registration the slot fell through with no schema-aware attribution
* even though the bespoke sanitiser already produced a plausible
* payload, so per-field CMP steering at l_type / l_whence had nothing
* to hang against.
*
* Resolution to this descriptor is now gated on the F_*LK / F_OFD_*LK
* / F_CANCELLK cmds via the discriminator-aware syscall_struct_args[]
* entry below; for non-lock cmds the kernel doesn't read a struct
* flock at a3 (it reads an fd or an integer flag word that sanitise_
* fcntl writes through rec->a3), so attribution at the flock fields
* would be meaningless.
*/
static const unsigned long flock_l_type_values[] = {
F_RDLCK, F_WRLCK, F_UNLCK,
};
static const unsigned long flock_l_whence_values[] = {
SEEK_SET, SEEK_CUR, SEEK_END,
};
static const struct struct_field flock_fields[] = {
FIELDX(struct flock, l_type, FT_ENUM,
.u.enum_ = { flock_l_type_values,
ARRAY_SIZE(flock_l_type_values) },
.mutate_weight = 80),
FIELDX(struct flock, l_whence, FT_ENUM,
.u.enum_ = { flock_l_whence_values,
ARRAY_SIZE(flock_l_whence_values) },
.mutate_weight = 80),
FIELD(struct flock, l_start),
FIELD(struct flock, l_len),
FIELD(struct flock, l_pid),
};
/* ------------------------------------------------------------------ */
/* struct f_owner_ex (fcntl F_GETOWN_EX / F_SETOWN_EX) */
/* ------------------------------------------------------------------ */
/*
* fcntl's a3 for F_GETOWN_EX / F_SETOWN_EX is a pointer to struct
* f_owner_ex. The bespoke sanitise_fcntl() keeps owning the live
* fill: it allocates the buffer via get_writable_struct(), picks
* type from {F_OWNER_TID, F_OWNER_PID, F_OWNER_PGRP}, and stamps
* get_pid() into pid before overwriting rec->a3.
*
* Attribution-only registration, same shape as the struct flock
* entry above: struct_field_for_cmp() uses the FT_ENUM tag on type
* (a 3-valued vocab the kernel branches on in f_setown_ex) to steer
* KCOV-CMP learned constants at the named slot rather than at a
* coincidentally-same-width slot. pid stays FT_RAW: the bespoke
* sanitiser stamps a getpid()-shaped value and the kernel treats it
* as an opaque process / thread id with no vocab to attribute
* against.
*
* Resolution to this descriptor is gated on cmd ∈ {F_GETOWN_EX,
* F_SETOWN_EX} via the discriminator-aware syscall_struct_args[]
* entry below; this is the first proof of the new mechanism. Same
* (name, arg_idx) -> different desc by sibling-arg value -- the
* existing single-desc table couldn't represent it.
*/
static const unsigned long f_owner_ex_type_values[] = {
F_OWNER_TID, F_OWNER_PID, F_OWNER_PGRP,
};
static const struct struct_field f_owner_ex_fields[] = {
FIELDX(struct f_owner_ex, type, FT_ENUM,
.u.enum_ = { f_owner_ex_type_values,
ARRAY_SIZE(f_owner_ex_type_values) },
.mutate_weight = 80),
FIELD(struct f_owner_ex, pid),
};
/* ------------------------------------------------------------------ */
/* struct epoll_event (epoll_ctl) */
/* ------------------------------------------------------------------ */
/*
* EPOLL* event-bit vocabulary for epoll_event.events. EPOLLEXCLUSIVE
* and EPOLLWAKEUP postdate older glibc vintages; compat.h declares
* EPOLLWAKEUP unconditionally and the local #ifdef arm covers
* EPOLLEXCLUSIVE. Bits outside the mask either fail the kernel's
* EP_PRIVATE_BITS check or get silently masked, so a uniform-byte
* splat almost never produces a useful (op, events) combination.
*/
#ifndef EPOLLEXCLUSIVE
# define EPOLLEXCLUSIVE_COMPAT (1u << 28)
#else
# define EPOLLEXCLUSIVE_COMPAT EPOLLEXCLUSIVE
#endif
#define EPOLL_EVENTS_MASK \
(EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLPRI | \
EPOLLERR | EPOLLHUP | EPOLLET | EPOLLONESHOT | \
EPOLLWAKEUP | EPOLLEXCLUSIVE_COMPAT | \
EPOLLRDNORM | EPOLLRDBAND | EPOLLWRNORM | EPOLLWRBAND | \
EPOLLMSG)
static const struct struct_field epoll_event_fields[] = {
FIELDX(struct epoll_event, events, FT_FLAGS,
.u.flags.mask = EPOLL_EVENTS_MASK,
.mutate_weight = 80),
};
/* ------------------------------------------------------------------ */
/* struct perf_event_attr (perf_event_open) */
/* ------------------------------------------------------------------ */
/*
* perf_event_attr is the rare cataloged struct whose live fill path
* the schema does NOT drive. sanitise_perf_event_open() in
* syscalls/perf_event_open.c hand-rolls a coherent (type, config)
* tuple via pick_perf_tuple() and overwrites rec->a1 with its own
* buffer; the schema-aware fill produced upstream is discarded on
* every iteration. The catalog therefore exists for two forward-
* infra purposes:
*
* 1. type-scoped CMP attribution. struct_field_for_cmp() prefers
* a same-width FT_ENUM / FT_FLAGS / FT_VERSION_MAGIC slot over
* an FT_RAW one, so a learned constant (KCOV CMP) lands on the
* named gate (type, size, sample_type, ...) rather than a
* coincidentally-same-width opaque slot. No live consumer is
* wired today; this awaits the cmp_hints recording-path lift.
* 2. per-type variant infra. Only type-independent shared fields
* are annotated here; per-PERF_TYPE_* sub-variants for
* config / bp_* / config1 / config2 land once the buffer-
* discriminator is wired and `type` (offset 0) becomes the
* desc-level discriminator.
*
* Bit-field flag group at offset 40 (disabled..sigtrap, ~36 single-
* bit flags + precise_ip:2 + __reserved_1:26) is annotated below via
* PERF_ATTR_FLAG_MASK; the explicit hand-built mask doesn't compose
* with offsetof so the field uses an explicit { .offset = 40 }.
*/
/*
* type (offset 0): PERF_TYPE_* major-type discriminator. Six legal
* values today; vendor PMU type IDs >= PERF_TYPE_MAX are dynamically
* registered and not enumerable at compile time. Buffer-discriminator