-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathMakefile
More file actions
1283 lines (1138 loc) · 60.7 KB
/
Copy pathMakefile
File metadata and controls
1283 lines (1138 loc) · 60.7 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
# This file is part of KASLD - https://github.com/bcoles/kasld
# ---
# <bcoles@gmail.com>
SHELL = /bin/sh
# If a recipe fails after it has begun writing its target, delete the partial
# output so make does not treat a truncated file as up-to-date on the next run.
# (Component recipes exit 0 by design and are unaffected; this guards the object
# and link steps, which do fail on error.)
.DELETE_ON_ERROR:
CC = cc
# -O2 is safe for most components (pure C parsers, syscall wrappers).
# Side-channel components that rely on precise timing or speculative
# execution are compiled with -O0 below: the compiler may reorder memory
# operations around rdtsc/rdtscp timing, eliminate volatile accesses used
# for Flush+Reload cache probing, or reschedule instructions across
# mfence/lfence serialization barriers — destroying the timing signal.
CFLAGS = -g -O2 -Wall -Wextra -pedantic
# Diagnostics layer. Each flag is probed against $(CC) at make-time via
# cc-option; flags the compiler doesn't recognize simply drop out instead
# of generating per-file noise. This keeps the build portable across:
# - older gcc (pre-6 lacks -Wnull-dereference, -Wduplicated-cond,
# -Wrestrict; pre-7 lacks -Wduplicated-branches, -Walloca; pre-4.9
# lacks -fstack-protector-strong)
# - clang (lacks -Wlogical-op, -Wduplicated-cond, -Wduplicated-branches,
# -Wrestrict — gcc-only diagnostics)
# - musl (silently ignores -D_FORTIFY_SOURCE; gcc accepts the flag)
#
# Warning surface:
# -Wshadow / -Wcast-qual / -Wcast-align / -Wpointer-arith real-bug catches
# -Wstrict-prototypes / -Wmissing-prototypes /
# -Wmissing-declarations / -Wold-style-definition /
# -Wnested-externs / -Wredundant-decls / -Wbad-function-cast prototype hygiene
# -Wwrite-strings string-literal const
# -Wundef undefined macro in #if
# -Wnull-dereference / -Wlogical-op / -Wduplicated-cond /
# -Wduplicated-branches / -Wrestrict flow/aliasing bugs
# -Wfloat-equal == on floats
# -Wvla / -Walloca / -Wstack-protector no runtime-sized stack
#
# Promoted-to-error: catches real bugs that are easy to ignore as warnings
# (missing #include, mismatched pointer types, missing return, non-literal
# format with no args).
#
# Hardening: -fstack-protector-strong + -D_FORTIFY_SOURCE=2 add stack
# canaries and libc-side str/mem/printf runtime checks. _FORTIFY_SOURCE
# needs -O >= 1 (we have -O2).
# cc-option <flag>: emits <flag> if $(CC) accepts it on this toolchain, else
# nothing. Same shape as the kernel's cc-option. -Werror so a "unknown
# option" warning fails the probe; -x c /dev/null so neither preprocessor
# input nor a real file is needed. One $(CC) invocation per probed flag at
# make startup, once for the whole build (and skipped entirely for goals that
# never compile — see kasld_compiling below).
cc-option = $(shell $(CC) -Werror $(1) -E -x c /dev/null -o /dev/null \
>/dev/null 2>&1 && echo $(1))
KASLD_WARN_FLAGS_WANTED := \
-Wshadow -Wstrict-prototypes -Wmissing-prototypes \
-Wmissing-declarations -Wpointer-arith -Wcast-align \
-Wcast-qual -Wwrite-strings -Wundef \
-Wold-style-definition -Wredundant-decls -Wformat=2 \
-Wbad-function-cast -Wfloat-equal -Wnested-externs \
-Wnull-dereference -Wlogical-op -Wduplicated-cond \
-Wduplicated-branches -Wrestrict -Wvla -Walloca \
-Wstack-protector \
-Werror=implicit-function-declaration \
-Werror=incompatible-pointer-types \
-Werror=return-type \
-Werror=format-security \
-Werror=format \
-Werror=frame-larger-than=2097152
KASLD_HARDEN_FLAGS_WANTED := -fstack-protector-strong -D_FORTIFY_SOURCE=2
# 2 MiB frame cap: a single ~1.35 MiB `struct engine` on the stack is fine, but
# two or more (a multi-engine test) would overflow — those must be `static`
# (engine_init() resets each before use). Catches the engine, and any other
# oversized stack frame, at compile time. Dropped by cc-option on toolchains
# that lack the flag.
# The cc-option probes and the zlib/pthread feature tests below fork $(CC) at
# make startup — dozens of times. Targets that never compile (clean, help,
# uninstall) do not need any of it, so skip the whole lot when every requested
# goal is one of those. `$(or $(MAKECMDGOALS),build)` treats a bare `make` as a
# build. A slow or minimal host then runs `make clean`/`make help` without
# invoking the compiler at all.
kasld_compiling := 1
ifeq ($(filter-out clean help uninstall,$(or $(MAKECMDGOALS),build)),)
kasld_compiling :=
endif
ifdef kasld_compiling
KASLD_WARN_FLAGS := $(foreach f,$(KASLD_WARN_FLAGS_WANTED),$(call cc-option,$(f)))
KASLD_HARDEN_FLAGS := $(foreach f,$(KASLD_HARDEN_FLAGS_WANTED),$(call cc-option,$(f)))
endif
# Appended, not substituted: a caller adding a flag for one target must not have
# to restate CFLAGS and lose the warning and hardening sets with it. `make cross`
# uses these to carry a per-triple requirement.
EXTRA_CFLAGS =
EXTRA_LDFLAGS =
ALL_CFLAGS = -std=c99 $(CFLAGS) $(EXTRA_CFLAGS) $(KASLD_WARN_FLAGS) $(KASLD_HARDEN_FLAGS)
LDFLAGS =
ALL_LDFLAGS = $(LDFLAGS) $(EXTRA_LDFLAGS)
# Quiet build. The default prints a short kernel-style tag (" CC <path>")
# BEFORE each step runs, so any compiler diagnostics that follow are always
# attributable to a named target instead of appearing with no context.
# `make V=1` restores the full command lines.
# Q — prefixes every real command; '@' hides it in quiet mode, empty in V=1
# (where make echoes the command itself instead).
# ccv — prints the " TAG <path>" progress line; expands to nothing under
# V=1 so the echoed command is the only output.
# disp — drops the leading "./" from a build path for a cleaner tag.
disp = $(patsubst ./%,%,$(1))
# Colorized progress tags, on only when stdout is a terminal. GNU make sets
# MAKE_TERMOUT to the terminal name when its stdout is a tty and leaves it empty
# when output is piped or redirected (CI logs, `make | tee`, the `make cross`
# capture), so those stay plain automatically. NO_COLOR (present, any value)
# forces plain; COLOR=1 / COLOR=0 override the auto-detection either way. The
# codes are portable octal ESC sequences so the /bin/sh printf renders them.
# COLOR=0 (or empty) forces off; COLOR set to any other value forces on.
KASLD_COLOR :=
ifeq ($(origin COLOR),undefined)
ifndef NO_COLOR
ifneq ($(MAKE_TERMOUT),)
KASLD_COLOR := 1
endif
endif
else ifneq ($(filter-out 0,$(COLOR)),)
KASLD_COLOR := 1
endif
ifeq ($(KASLD_COLOR),1)
C_TAG := \033[32m
C_SKIP := \033[33m
C_RST := \033[0m
else
C_TAG :=
C_SKIP :=
C_RST :=
endif
ifeq ($(V),1)
Q :=
ccv =
else
Q := @
ccv = @printf ' $(C_TAG)%-5s$(C_RST) %s\n' '$(1)' '$(call disp,$(2))'
endif
VERSION := $(shell cat VERSION 2>/dev/null || echo unknown)
# Target triple ($(CC)) vs. the host's native triple. When they differ the build
# is a cross-compile, so link static — the target loader/libs are not on this host.
# $(_ARCH) also names the per-arch build subdirectory. The native triple comes
# from a native compiler, not from $(CC) (which may itself be a cross compiler):
# try `cc`, then gcc/clang, so a host with no `cc` symlink does not misread a
# native build as cross (and add a spurious -static) or print `cc: not found`.
HOST_ARCH := $(shell cc -dumpmachine 2>/dev/null || gcc -dumpmachine 2>/dev/null || clang -dumpmachine 2>/dev/null)
_ARCH := $(shell $(CC) -dumpmachine)
ifneq ($(_ARCH),$(HOST_ARCH))
ALL_LDFLAGS += -static
endif
BUILD_DIR := ./build
# The per-arch directory is the deployable product: the kasld binary plus the
# components/ subdir it discovers at runtime. Build intermediates (.o) go in a
# sibling obj/ subdir so they do not clutter that deployable tree — the same
# separation components/ already has.
ARCH_DIR := $(BUILD_DIR)/$(_ARCH)
# Cross-build dependencies, one prefix per target triple (see cross-deps).
# Defined here rather than beside that target because the proc_config rule
# expands it far earlier in this file, and a prerequisite is expanded when the
# rule is read.
DEPS_DIR := $(BUILD_DIR)/deps
OBJ_DIR := $(ARCH_DIR)/obj
COMP_DIR := $(ARCH_DIR)/components
# Test executables live apart from the deployable product (kasld + components)
# so `make install` never sees them and they are obviously not shippable.
TEST_OBJ_DIR := $(BUILD_DIR)/tests
SRC_DIR := ./src
# Header dependencies: rebuild when any header changes
HDRS := $(wildcard $(SRC_DIR)/include/*.h $(SRC_DIR)/include/kasld/*.h \
$(SRC_DIR)/include/kasld/arch/*.h)
# Detect zlib (optional, for native gzip decompression in proc_config) and
# pthread (optional, for the parallel inference worker pool in the orchestrator).
# Guarded by kasld_compiling so non-compiling goals (clean/help/uninstall) do not
# fork the compiler to link these probe programs.
ifdef kasld_compiling
HAVE_ZLIB := $(shell echo 'int main(void){return 0;}' | $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -xc - -lz -o /dev/null 2>/dev/null && echo 1)
HAVE_PTHREAD := $(shell echo 'int main(void){return 0;}' | $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -xc - -lpthread -o /dev/null 2>/dev/null && echo 1)
endif
ifeq ($(HAVE_PTHREAD),1)
PTHREAD_CFLAGS := -DHAVE_PTHREAD
PTHREAD_LIBS := -lpthread
else
PTHREAD_CFLAGS :=
PTHREAD_LIBS :=
endif
# kasld orchestrator (not a leak component)
KASLD_SRC := $(SRC_DIR)/orchestrator.c
# The observing environment (hardening settings + this process's vantage).
# Its own translation unit because it answers a different question from the
# rest: what can be seen from here, rather than where the kernel is.
ENV_SRC := $(SRC_DIR)/environment.c
RENDER_SRC := $(SRC_DIR)/render.c
# Per-output-mode renderer translation units. The wildcard means adding a new
# mode (e.g. src/render/yaml.c) needs no Makefile edit; the cross-file glue
# (shared helpers, per-mode entry points) lives in include/kasld/render_internal.h.
RENDER_MODE_SRCS := $(wildcard $(SRC_DIR)/render/*.c)
RENDER_MODE_OBJS := $(patsubst $(SRC_DIR)/render/%.c,$(OBJ_DIR)/render_%.o,$(RENDER_MODE_SRCS))
REGIONS_SRC := $(SRC_DIR)/region_info.c
KASLD_BIN := $(ARCH_DIR)/kasld
# Layered inference engine: core translation units + the pure rules, all linked
# into the orchestrator (the sole inference path). Declared once here so the
# engine test targets below reuse the same lists — no second copy to keep in
# sync. The rules are a wildcard, so adding a rule needs no Makefile edit.
ESTIMATE_SRC := $(SRC_DIR)/estimate.c
QUANTITIES_SRC := $(SRC_DIR)/quantities.c
EVIDENCE_SRC := $(SRC_DIR)/evidence.c
ENGINE_SRC := $(SRC_DIR)/engine.c
ENGINE_RULES_SRC := $(SRC_DIR)/engine_rules.c
RULE_SRCS := $(wildcard $(SRC_DIR)/rules/*.c)
# ENGINE_CORE = the engine minus its rule registry (estimate/quantities/
# evidence/engine); ENGINE_CORE_SRCS adds the registry — the full product path.
ENGINE_CORE := $(ESTIMATE_SRC) $(QUANTITIES_SRC) $(EVIDENCE_SRC) $(ENGINE_SRC)
ENGINE_CORE_SRCS := $(ENGINE_CORE) $(ENGINE_RULES_SRC)
ENGINE_OBJS := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(ENGINE_CORE_SRCS)) \
$(patsubst $(SRC_DIR)/rules/%.c,$(OBJ_DIR)/rule_%.o,$(RULE_SRCS))
# Leak components: standalone binaries in src/components/
COMP_SRC_DIR := $(SRC_DIR)/components
SRC_FILES := $(wildcard $(COMP_SRC_DIR)/*.c)
BIN_FILES := $(patsubst $(COMP_SRC_DIR)/%.c,$(COMP_DIR)/%,$(SRC_FILES))
# Side-channel components opt out of optimization by carrying the marker
# KASLD_BUILD_NO_OPTIMIZE in their source header; they are discovered by grep so
# adding one needs no Makefile edit (matching the drop-in component model). The
# same list feeds the build rule below and `make print-deps`. Whole-file -O0 is
# deliberate: it reliably stops the optimizer reordering or eliding the
# rdtsc/mfence timing loops, which a per-function attribute does not guarantee
# (gcc's optimize attribute is documented debugging-only).
SIDECHANNEL_COMPONENTS := $(patsubst $(COMP_SRC_DIR)/%.c,%,\
$(shell grep -l KASLD_BUILD_NO_OPTIMIZE $(COMP_SRC_DIR)/*.c 2>/dev/null))
SIDECHANNEL_BINS := $(addprefix $(COMP_DIR)/,$(SIDECHANNEL_COMPONENTS))
# cc-component <cmd...>: compile one leak component. One line per component.
# The compiler's output is captured so ordering is fully controlled, and the
# outcome decides what prints:
# - Arch-gate `#error "Architecture is not supported"` (and nothing else):
# print one " SKIP <path> (architecture-gated)" line and drop a
# non-executable stamp at $@. The source explicitly opts out for this arch,
# so it is not a failure — and the stamp makes the target up-to-date, so the
# (always-failing) compile is not re-run on the next build and an
# already-built tree stays silent. The orchestrator only runs executable
# regular files, so the stamp is invisible to it.
# - Success: print one " CC <path>" line.
# - Any other diagnostics (warnings, or a real error): print the " CC <path>"
# line and the captured output together in one write, so the diagnostic is
# always attributed to its component. A real failure is NOT stamped, so a
# genuine breakage keeps surfacing on every build instead of being memoised.
# The recipe always exits 0 so one broken component never halts the wider build.
# Under V=1 the raw command is echoed and run directly (error ignored via the
# leading '-'), so the full invocation is visible.
# - A real failure also REMOVES the target. The build still does not halt, but
# a broken component becomes absent rather than stale: whatever runs it next
# fails loudly instead of silently exercising the last binary that compiled.
# Without this a component can fail to build while `make` reports success and
# the previous binary keeps being tested, which is indistinguishable from the
# edit having worked.
ifeq ($(V),1)
define cc-component
-$(1) || rm -f '$@'
endef
else
define cc-component
@out=$$($(1) 2>&1); st=$$?; \
if [ $$st -ne 0 ] && printf '%s' "$$out" | grep -q '#error.*Architecture is not supported'; then \
printf ' $(C_SKIP)%-5s$(C_RST) %s (architecture-gated)\n' SKIP '$(call disp,$@)'; \
: > '$@'; \
elif [ -n "$$out" ]; then \
printf ' $(C_TAG)%-5s$(C_RST) %s\n%s\n' CC '$(call disp,$@)' "$$out" >&2; \
[ $$st -eq 0 ] || rm -f '$@'; \
else \
printf ' $(C_TAG)%-5s$(C_RST) %s\n' CC '$(call disp,$@)'; \
fi
endef
endif
PREFIX ?= /usr/local
.PHONY: all
all : build
# Create build directories (order-only prerequisites). mkdir -p also creates the
# parent $(ARCH_DIR), so making obj/ or components/ brings the arch dir with it.
$(COMP_DIR):
@echo "Building $(call disp,$(ARCH_DIR)) ..."
@mkdir -p "$(COMP_DIR)"
$(OBJ_DIR):
@mkdir -p "$(OBJ_DIR)"
$(TEST_OBJ_DIR):
@mkdir -p "$(TEST_OBJ_DIR)"
# Validate headers before building components. -Wno-unused-function because the
# point of this check is to compile the header as a translation unit of its own,
# where every static inline it defines is unused by construction; the check stays
# live for every real compile.
.PHONY: check-headers
check-headers: | $(COMP_DIR)
$(Q)$(CC) $(ALL_CFLAGS) -Wno-unused-function -xc -fsyntax-only \
$(SRC_DIR)/include/kasld/api.h
$(COMP_DIR)/%: $(COMP_SRC_DIR)/%.c $(HDRS) | $(COMP_DIR)
$(call cc-component, $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $< -o $@)
# Offset-table components #include a generated offsets/<name>.inc; add it as a
# prerequisite (the pattern rule above only sees the .c + $(HDRS)) so a regen
# rebuilds the component.
$(COMP_DIR)/bpf_verifier_ksym: $(COMP_SRC_DIR)/offsets/bpf_verifier_ksym.inc
$(COMP_DIR)/dmesg_ex_handler_msr: $(COMP_SRC_DIR)/offsets/dmesg_ex_handler_msr.inc
$(COMP_DIR)/entrybleed: $(COMP_SRC_DIR)/offsets/entrybleed.inc
$(COMP_DIR)/qemu_tcg_iret: $(COMP_SRC_DIR)/offsets/qemu_tcg_iret.inc
# proc_config: link with zlib when available for native gzip decompression.
#
# The cross-deps prefix is a prerequisite where it exists, so populating it
# rebuilds this component. Without that, gaining zlib changes which RULE applies
# and leaves the object alone: the build reports success while shipping the
# binary that shells out to zcat. $(wildcard) yields nothing when the prefix is
# absent, which is every host build and every cross build before cross-deps.
ZLIB_DEP := $(wildcard $(DEPS_DIR)/$(_ARCH)/lib/libz.a)
ifeq ($(HAVE_ZLIB),1)
$(COMP_DIR)/proc_config: $(COMP_SRC_DIR)/proc_config.c $(HDRS) $(ZLIB_DEP) | $(COMP_DIR)
$(call cc-component, $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) -DHAVE_ZLIB $< -lz -o $@)
endif
# Side-channel components: compile without optimization (-O0 overrides -O2).
# These rely on precise instruction ordering around timing measurements
# (rdtsc/rdtscp + mfence/lfence), speculative execution gadgets (asm goto),
# or Flush+Reload cache probing via volatile pointer accesses. The static
# pattern rule takes precedence over the generic $(COMP_DIR)/% rule above for
# the SIDECHANNEL_BINS (discovered by the KASLD_BUILD_NO_OPTIMIZE marker above).
# -U_FORTIFY_SOURCE drops the fortify define inherited from ALL_CFLAGS: it is a
# no-op at -O0 and glibc otherwise warns "_FORTIFY_SOURCE requires -O".
$(SIDECHANNEL_BINS): $(COMP_DIR)/%: $(COMP_SRC_DIR)/%.c $(HDRS) | $(COMP_DIR)
$(call cc-component, $(CC) $(ALL_CFLAGS) -O0 -U_FORTIFY_SOURCE $(ALL_LDFLAGS) -I$(SRC_DIR) $< -o $@)
# kernelsnitch: needs -lpthread (uses default -O2 for hash timing performance)
$(COMP_DIR)/kernelsnitch: $(COMP_SRC_DIR)/kernelsnitch.c $(HDRS) | $(COMP_DIR)
$(call cc-component, $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $< $(PTHREAD_LIBS) -o $@)
.PHONY: build
build : check-headers $(BIN_FILES) $(KASLD_BIN)
# -I$(SRC_DIR) so the orchestrator can include the component-side fact headers
# (task_size.h and target_width.h use the same "include/kasld/..." form the
# components do).
$(OBJ_DIR)/orchestrator.o: $(KASLD_SRC) $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) $(PTHREAD_CFLAGS) -I$(SRC_DIR) -DVERSION='"$(VERSION)"' -c $< -o $@
$(OBJ_DIR)/environment.o: $(ENV_SRC) $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) -I$(SRC_DIR) -c $< -o $@
$(OBJ_DIR)/render.o: $(RENDER_SRC) $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) -DVERSION='"$(VERSION)"' -I$(SRC_DIR) -c $< -o $@
# Per-mode render translation units (src/render/<mode>.c). Each gets its own
# object so editing one mode does not force the others to recompile.
$(OBJ_DIR)/render_%.o: $(SRC_DIR)/render/%.c $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) -DVERSION='"$(VERSION)"' -I$(SRC_DIR) -c $< -o $@
$(OBJ_DIR)/region_info.o: $(REGIONS_SRC) $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) -c $< -o $@
# Engine core (estimate/quantities/evidence/engine) and ported rules.
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) -I$(SRC_DIR) -c $< -o $@
$(OBJ_DIR)/rule_%.o: $(SRC_DIR)/rules/%.c $(HDRS) | $(OBJ_DIR)
$(call ccv,CC,$@)
$(Q)$(CC) $(ALL_CFLAGS) -I$(SRC_DIR) -c $< -o $@
$(KASLD_BIN): $(OBJ_DIR)/orchestrator.o $(OBJ_DIR)/environment.o $(OBJ_DIR)/render.o $(RENDER_MODE_OBJS) $(OBJ_DIR)/region_info.o $(ENGINE_OBJS) | $(OBJ_DIR)
$(call ccv,LD,$@)
$(Q)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) $^ $(PTHREAD_LIBS) -o $@
.PHONY: run
run : build
$(KASLD_BIN)
# Unit tests
TEST_DIR := ./tests
# Test binaries carry the hermeticity probe: kasld_resolve records any kernel
# fact path resolved with no KASLD_SYSROOT set, and the harness fails the binary
# on it. A test that reads the machine it runs on is asserting against that
# machine's contents -- or against what that machine happens to lack, which its
# source does not reveal. Never set for a shipped build.
TEST_ALL_CFLAGS = $(ALL_CFLAGS) -DKASLD_HERMETIC_PROBE
TEST_BIN := $(TEST_OBJ_DIR)/test_kasld
# Unit tests of orchestrator internals (parsing, merge, anchor selection,
# render helpers). The orchestrator's main() and engine hooks are excluded
# under -DKASLD_TESTING; engine-rule coverage lives in test_engine* below.
# test_kasld.c #includes render.c and each src/render/*.c so the renderer's
# static helpers (e.g. json_print_escaped, section_consensus) are reachable
# without exporting them across the public API.
$(TEST_BIN): $(TEST_DIR)/test_kasld.c $(KASLD_SRC) $(ENV_SRC) $(RENDER_SRC) $(RENDER_MODE_SRCS) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) $(PTHREAD_CFLAGS) -DKASLD_TESTING -I$(SRC_DIR) $(TEST_DIR)/test_kasld.c $(PTHREAD_LIBS) -o $@
# Renderer unit tests (split from test_kasld.c). Same single-TU model — it
# #includes the orchestrator + render translation units directly, hence
# -DKASLD_TESTING + the pthread flags — but exercises render.c / render/*.c.
TEST_RENDER_BIN := $(TEST_OBJ_DIR)/test_render
$(TEST_RENDER_BIN): $(TEST_DIR)/test_render.c $(KASLD_SRC) $(ENV_SRC) $(RENDER_SRC) $(RENDER_MODE_SRCS) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) $(PTHREAD_CFLAGS) -DKASLD_TESTING -I$(SRC_DIR) $(TEST_DIR)/test_render.c $(PTHREAD_LIBS) -o $@
# Estimate-core test (Stage A): standalone, links only estimate.c + quantities.c.
TEST_EST_BIN := $(TEST_OBJ_DIR)/test_estimate
$(TEST_EST_BIN): $(TEST_DIR)/test_estimate.c $(ESTIMATE_SRC) $(QUANTITIES_SRC) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_estimate.c $(ESTIMATE_SRC) $(QUANTITIES_SRC) -o $@
# Evidence-store test (Stage B): standalone, links only evidence.c.
TEST_EV_BIN := $(TEST_OBJ_DIR)/test_evidence
$(TEST_EV_BIN): $(TEST_DIR)/test_evidence.c $(EVIDENCE_SRC) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_evidence.c $(EVIDENCE_SRC) -o $@
# Align-helper test (header-only): exercises kasld_floor_text_base() and its
# pure core against every arch's sub-offset on the host. No .c sources to link.
TEST_ALIGN_BIN := $(TEST_OBJ_DIR)/test_align
$(TEST_ALIGN_BIN): $(TEST_DIR)/test_align.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_align.c -o $@
# Address-parser test (header-only): drives kasld_addr_parse()'s refusal paths,
# deriving the too-wide inputs from the build's own word so the same source is a
# real overflow on the 32-bit cross targets. No .c sources to link.
TEST_ADDRP_BIN := $(TEST_OBJ_DIR)/test_addr_parse
$(TEST_ADDRP_BIN): $(TEST_DIR)/test_addr_parse.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_addr_parse.c -o $@
# TASK_SIZE probe test (header-only): drives the boundary search and gap
# detection in task_size.h with a synthetic address space (an injected step, no
# mmap), so the pure logic runs on any host. Covers the porous / untrusted paths
# a normal-kernel VM boot cannot reach. No .c sources to link.
TEST_TS_BIN := $(TEST_OBJ_DIR)/test_task_size
$(TEST_TS_BIN): $(TEST_DIR)/test_task_size.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_task_size.c -o $@
# Prefetch scan edge-detection test (header-only): drives
# prefetch_scan_find_edge() with synthetic timing profiles. The x86_64-only
# header makes the suite inert on other hosts. No .c sources to link.
TEST_PREFETCH_SCAN_BIN := $(TEST_OBJ_DIR)/test_prefetch_scan
$(TEST_PREFETCH_SCAN_BIN): $(TEST_DIR)/test_prefetch_scan.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_prefetch_scan.c -o $@
# pin_cpu() cpuset-aware affinity test (header-only, x86_64-only cpu.h; inert
# elsewhere). No .c sources to link.
TEST_CPU_BIN := $(TEST_OBJ_DIR)/test_cpu
$(TEST_CPU_BIN): $(TEST_DIR)/test_cpu.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_cpu.c -o $@
# Component outcome classifier test (header-only): exercises
# kasld_classify_outcome() (outcome.h) — the reaped-status -> outcome mapping,
# incl. the SIGSYS-denial and exit-77/69 paths. No .c sources to link.
TEST_OUTCOME_BIN := $(TEST_OBJ_DIR)/test_outcome
$(TEST_OUTCOME_BIN): $(TEST_DIR)/test_outcome.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_outcome.c -o $@
# seccomp-exec: installs a minimal seccomp-BPF filter then execs its argv, so
# tests/container/run can run kasld under a container-shaped syscall gate
# (perf_event_open → EPERM or SIGSYS) without a container runtime. Standalone
# helper, no kasld sources to link.
SECCOMP_EXEC_BIN := $(TEST_OBJ_DIR)/seccomp-exec
$(SECCOMP_EXEC_BIN): $(TEST_DIR)/container/seccomp-exec.c | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) $(TEST_DIR)/container/seccomp-exec.c -o $@
# fork-fail.so: LD_PRELOAD shim that fails a fraction of fork() calls with
# EAGAIN, so tests/container/run can verify kasld stays coherent under a pids
# cgroup limit (docker --pids-limit / k8s pids.max) without a real cgroup.
FORK_FAIL_LIB := $(TEST_OBJ_DIR)/fork-fail.so
$(FORK_FAIL_LIB): $(TEST_DIR)/container/fork-fail.c | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) -O2 -fPIC -shared $(TEST_DIR)/container/fork-fail.c -o $@ -ldl
# Text-order classifier test (header-only): exercises classify_text_order().
TEST_TEXT_ORDER_BIN := $(TEST_OBJ_DIR)/test_text_order
$(TEST_TEXT_ORDER_BIN): $(TEST_DIR)/test_text_order.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_text_order.c -o $@
# Kernel image-size readers test (header-only): exercises the Image header / ELF
# / System.map / gzip-ISIZE parsers in kasld/kernel_image.h against crafted
# fixtures under a temporary KASLD_SYSROOT. No .c sources to link.
TEST_KIMG_BIN := $(TEST_OBJ_DIR)/test_kernel_image
$(TEST_KIMG_BIN): $(TEST_DIR)/test_kernel_image.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_kernel_image.c -o $@
# Engine test (Stage C/D): links the engine core + ALL ported rules. Linking the
# whole rules/ wildcard (rather than a hand-maintained subset) means adding a
# rule + its test needs no Makefile edit, and a rule can never be silently left
# out of the test build. Unreferenced rules just link unused.
TEST_ENG_BIN := $(TEST_OBJ_DIR)/test_engine
$(TEST_ENG_BIN): $(TEST_DIR)/test_engine.c $(ENGINE_CORE) $(RULE_SRCS) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_engine.c $(ENGINE_CORE) $(RULE_SRCS) -o $@
# Integration test: the FULL production rule registry (engine_rules.c + every
# rules/*.c) against leak-bearing synthetic evidence.
TEST_INT_BIN := $(TEST_OBJ_DIR)/test_engine_integration
$(TEST_INT_BIN): $(TEST_DIR)/test_engine_integration.c $(ENGINE_CORE) $(ENGINE_RULES_SRC) $(RULE_SRCS) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_engine_integration.c $(ENGINE_CORE) $(ENGINE_RULES_SRC) $(RULE_SRCS) -o $@
# Component parser test: dmesg_mem_init_kernel_layout's layout-dump parser,
# exercised by #including the component (its main renamed). No extra link inputs
# — the component pulls its helpers from headers.
TEST_DMESG_BIN := $(TEST_OBJ_DIR)/test_dmesg_layout
$(TEST_DMESG_BIN): $(TEST_DIR)/test_dmesg_layout.c $(SRC_DIR)/components/dmesg_mem_init_kernel_layout.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_dmesg_layout.c -o $@
# BTF reader parser test: btf_struct_page_size's struct-size parser, exercised
# by #including the component (its main renamed) against hand-built BTF blobs.
TEST_BTF_BIN := $(TEST_OBJ_DIR)/test_btf
$(TEST_BTF_BIN): $(TEST_DIR)/test_btf.c $(SRC_DIR)/components/btf_struct_page_size.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_btf.c -o $@
# dmesg_backtrace block parser: #includes the component (main renamed), driven
# over a staged KASLD_SYSROOT /var/log/dmesg covering the CR3 context tagging.
TEST_BACKTRACE_BIN := $(TEST_OBJ_DIR)/test_dmesg_backtrace
$(TEST_BACKTRACE_BIN): $(TEST_DIR)/test_dmesg_backtrace.c $(SRC_DIR)/components/dmesg_backtrace.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_dmesg_backtrace.c -o $@
# boot_config provenance: #includes the component (main renamed), driven over a
# staged KASLD_SYSROOT to assert keyed configs stay CONF_PARSED while the
# unkeyed /boot/config is demoted to CONF_HEURISTIC (and never shadows a keyed).
TEST_BOOTCFG_BIN := $(TEST_OBJ_DIR)/test_boot_config
$(TEST_BOOTCFG_BIN): $(TEST_DIR)/test_boot_config.c $(SRC_DIR)/components/boot_config.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_boot_config.c -o $@
# dmesg_kaslr_disabled classification: #includes the component (main renamed),
# driven over a staged KASLD_SYSROOT /var/log/dmesg to assert only whitelisted
# opt-out phrases pin to default and unrecognized lines emit nothing.
TEST_KASLRDIS_BIN := $(TEST_OBJ_DIR)/test_dmesg_kaslr_disabled
$(TEST_KASLRDIS_BIN): $(TEST_DIR)/test_dmesg_kaslr_disabled.c $(SRC_DIR)/components/dmesg_kaslr_disabled.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_dmesg_kaslr_disabled.c -o $@
# sysfs_devicetree_memory covering completeness: #includes the component (main
# renamed), driven over a staged KASLD_SYSROOT binary device tree to assert a
# complete map emits hull+extents, a buffer-filling reg withholds the map, and
# >64 banks falls back to hull-only.
TEST_DTMEM_BIN := $(TEST_OBJ_DIR)/test_sysfs_devicetree_memory
$(TEST_DTMEM_BIN): $(TEST_DIR)/test_sysfs_devicetree_memory.c $(SRC_DIR)/components/sysfs_devicetree_memory.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_sysfs_devicetree_memory.c -o $@
# proc_net_sock_ptr hashed-pointer rejection: the component is #included (main
# renamed) so its classify_sock_ptr() is unit-tested, and it is driven over a
# staged KASLD_SYSROOT /proc/net/unix to assert the batch-decline + real-emit.
TEST_SOCKPTR_BIN := $(TEST_OBJ_DIR)/test_proc_net_sock_ptr
$(TEST_SOCKPTR_BIN): $(TEST_DIR)/test_proc_net_sock_ptr.c $(SRC_DIR)/components/proc_net_sock_ptr.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_proc_net_sock_ptr.c -o $@
# ptdump_kernel_page_tables / kmemleak: each component is #included (main
# renamed) and driven over a staged KASLD_SYSROOT debugfs file to assert the
# page-table image-base recovery and the kmemleak direct-map witness.
TEST_PTDUMP_BIN := $(TEST_OBJ_DIR)/test_ptdump
$(TEST_PTDUMP_BIN): $(TEST_DIR)/test_ptdump.c $(SRC_DIR)/components/ptdump_kernel_page_tables.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_ptdump.c -o $@
TEST_KMEMLEAK_BIN := $(TEST_OBJ_DIR)/test_kmemleak
$(TEST_KMEMLEAK_BIN): $(TEST_DIR)/test_kmemleak.c $(SRC_DIR)/components/kmemleak.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_kmemleak.c -o $@
# proc_timer_list hashed-pointer rejection: same slab/pointer-alignment gate as
# proc_net_sock_ptr, unit-tested (classify_timer_base) + staged /proc/timer_list.
TEST_TIMERLIST_BIN := $(TEST_OBJ_DIR)/test_proc_timer_list
$(TEST_TIMERLIST_BIN): $(TEST_DIR)/test_proc_timer_list.c $(SRC_DIR)/components/proc_timer_list.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_proc_timer_list.c -o $@
# Build/target width check (header-only): the two mismatch signals and, mostly,
# the paths that must NOT report one. Driven over a staged KASLD_SYSROOT.
TEST_TWIDTH_BIN := $(TEST_OBJ_DIR)/test_target_width
$(TEST_TWIDTH_BIN): $(TEST_DIR)/test_target_width.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_target_width.c -o $@
# proc_kallsyms masked-probe + address width: the kptr_restrict all-zero
# detection and the refusal of a symbol address wider than this build's word,
# over a staged /proc/kallsyms (main renamed).
TEST_KALLSYMS_BIN := $(TEST_OBJ_DIR)/test_proc_kallsyms
$(TEST_KALLSYMS_BIN): $(TEST_DIR)/test_proc_kallsyms.c $(SRC_DIR)/components/proc_kallsyms.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_proc_kallsyms.c -o $@
# tracefs_available_filter_addrs bounding + record skip + address width: the
# lowest/highest kernel-text witnesses, the __ftrace_invalid_address___ skip,
# and the refusal of an over-wide address, over a staged KASLD_SYSROOT
# available_filter_functions_addrs (main renamed).
TEST_AVAILFILTER_BIN := $(TEST_OBJ_DIR)/test_tracefs_available_filter_addrs
$(TEST_AVAILFILTER_BIN): $(TEST_DIR)/test_tracefs_available_filter_addrs.c $(SRC_DIR)/components/tracefs_available_filter_addrs.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_tracefs_available_filter_addrs.c -o $@
# dmesg physical-reservation parsers: the four restructured components
# (reserved_mem / swiotlb / crashkernel / cma) #included (main renamed) and
# driven over a staged KASLD_SYSROOT /var/log/dmesg; asserts per-region ranges.
TEST_DMESG_RESV_SRCS := $(SRC_DIR)/components/dmesg_reserved_mem.c \
$(SRC_DIR)/components/dmesg_swiotlb.c \
$(SRC_DIR)/components/dmesg_crashkernel.c \
$(SRC_DIR)/components/dmesg_cma_reserved.c
TEST_DMESG_RESV_BIN := $(TEST_OBJ_DIR)/test_dmesg_reservations
$(TEST_DMESG_RESV_BIN): $(TEST_DIR)/test_dmesg_reservations.c $(TEST_DMESG_RESV_SRCS) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_dmesg_reservations.c -o $@
# boot_params_e820 RAM-covering test: the component #included (main renamed) and
# driven over a staged KASLD_SYSROOT zero-page; asserts the per-RAM-entry extents.
TEST_BPE820_BIN := $(TEST_OBJ_DIR)/test_boot_params_e820
$(TEST_BPE820_BIN): $(TEST_DIR)/test_boot_params_e820.c $(SRC_DIR)/components/boot_params_e820.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_boot_params_e820.c -o $@
# proc_kcore ELF program-header scan: the component #included (main renamed) and
# driven over a staged KASLD_SYSROOT /proc/kcore; the only coverage of the parse
# (the live component is CAP_SYS_RAWIO-gated, so it is dark in the fixtures).
TEST_KCORE_BIN := $(TEST_OBJ_DIR)/test_kcore
$(TEST_KCORE_BIN): $(TEST_DIR)/test_kcore.c $(SRC_DIR)/components/proc_kcore.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_kcore.c -o $@
# kernfs_ns_hash salt recovery: the component #included (main renamed) and its
# pure recovery functions exercised over synthesised cookies — unique recovery,
# the offset-table base pin, the patched-kernel no-op, and salt discrimination.
# Host-agnostic (no live getdents64; the seek-cookie path cannot be staged).
TEST_KERNFS_BIN := $(TEST_OBJ_DIR)/test_kernfs_ns_hash
$(TEST_KERNFS_BIN): $(TEST_DIR)/test_kernfs_ns_hash.c $(SRC_DIR)/components/kernfs_ns_hash.c $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_kernfs_ns_hash.c -o $@
# sysfs / ACPI / DT leak-parser tests: each component #included (main renamed)
# and driven over a staged KASLD_SYSROOT fixture tree reproducing the kernel ABI.
TEST_PARSERS_SRCS := $(SRC_DIR)/components/sysfs_efi_runtime_map.c \
$(SRC_DIR)/components/acpi_mrrm.c \
$(SRC_DIR)/components/sysfs_cbmem_address.c \
$(SRC_DIR)/components/sysfs_cxl_region.c \
$(SRC_DIR)/components/sysfs_qcom_rmtfs_mem.c \
$(SRC_DIR)/components/sysfs_iommu_reserved_regions.c \
$(SRC_DIR)/components/sysfs_devicetree_elfcorehdr.c \
$(SRC_DIR)/components/sysfs_nd_region.c \
$(SRC_DIR)/components/sysfs_uio_map.c \
$(SRC_DIR)/components/sysfs_iscsi_transport_handle.c \
$(SRC_DIR)/components/sysfs_devicetree_mmio.c \
$(SRC_DIR)/components/sysfs_pci_resource.c \
$(SRC_DIR)/components/tracefs_printk_formats.c \
$(SRC_DIR)/components/sysfs_devicetree_reserved_memory.c
TEST_PARSERS_BIN := $(TEST_OBJ_DIR)/test_sysfs_parsers
$(TEST_PARSERS_BIN): $(TEST_DIR)/test_sysfs_parsers.c $(TEST_PARSERS_SRCS) $(HDRS) | $(TEST_OBJ_DIR)
$(call ccv,CCLD,$@)
$(Q)$(CC) $(TEST_ALL_CFLAGS) $(ALL_LDFLAGS) -I$(SRC_DIR) $(TEST_DIR)/test_sysfs_parsers.c -o $@
.PHONY: test
# Test headers carry behaviour, not just declarations: test_harness.h runs the
# suite, and test_sysroot.h makes the staged tree and registers its removal.
# They are not in $(HDRS), which is the product's headers, so a test binary had
# no dependency on them at all -- editing one left every binary stale while the
# build reported success, and a measurement of the edit measured the build
# before it.
TEST_HDRS := $(wildcard $(TEST_DIR)/*.h)
TEST_ALL_BINS := $(TEST_BIN) \
$(TEST_RENDER_BIN) \
$(TEST_EST_BIN) \
$(TEST_EV_BIN) \
$(TEST_ALIGN_BIN) \
$(TEST_ADDRP_BIN) \
$(TEST_TWIDTH_BIN) \
$(TEST_TS_BIN) \
$(TEST_PREFETCH_SCAN_BIN) \
$(TEST_CPU_BIN) \
$(TEST_OUTCOME_BIN) \
$(TEST_TEXT_ORDER_BIN) \
$(TEST_KIMG_BIN) \
$(TEST_ENG_BIN) \
$(TEST_INT_BIN) \
$(TEST_DMESG_BIN) \
$(TEST_BACKTRACE_BIN) \
$(TEST_BOOTCFG_BIN) \
$(TEST_KASLRDIS_BIN) \
$(TEST_DTMEM_BIN) \
$(TEST_SOCKPTR_BIN) \
$(TEST_TIMERLIST_BIN) \
$(TEST_KALLSYMS_BIN) \
$(TEST_AVAILFILTER_BIN) \
$(TEST_BTF_BIN) \
$(TEST_DMESG_RESV_BIN) \
$(TEST_BPE820_BIN) \
$(TEST_PARSERS_BIN) \
$(TEST_KCORE_BIN) \
$(TEST_PTDUMP_BIN) \
$(TEST_KMEMLEAK_BIN) \
$(TEST_KERNFS_BIN)
$(TEST_ALL_BINS): $(TEST_HDRS)
test : $(KASLD_BIN) $(TEST_ALL_BINS)
@$(TEST_DIR)/run-all
@$(TEST_DIR)/check-render-width
@$(MAKE) --no-print-directory lint
# Static guards ("lint"): source-invariant greps, the 32-bit narrowing check,
# and shellcheck over all shipped shell scripts (extra/ + tests/) — no compiled
# unit-test binaries. Run after the unit tests by `make test`, and standalone by
# `make lint`.
#
# The guards are independent of each other, so run-guards runs them JOBS at a
# time (one per core by default, JOBS=1 for one at a time) and prints each one's
# output in the order listed here rather than the order they finish, so the
# transcript does not depend on the scheduling. Every guard runs even after one
# fails, and lint's exit status is non-zero if any did.
LINT_RUNNER := $(TEST_DIR)/run-guards
.PHONY: lint
lint :
@$(LINT_RUNNER) \
$(TEST_DIR)/check-rule-registry \
$(TEST_DIR)/check-self-edges \
$(TEST_DIR)/check-extent-callers \
$(TEST_DIR)/check-covering-consumers \
$(TEST_DIR)/check-discard-ledger \
$(TEST_DIR)/check-discard-accounting \
$(TEST_DIR)/check-discard-report \
$(TEST_DIR)/check-scalar-seed-order \
$(TEST_DIR)/check-vantage-coverage \
$(TEST_DIR)/check-test-staging \
$(TEST_DIR)/check-truncation \
$(TEST_DIR)/check-addr-parse \
$(TEST_DIR)/check-absence-vs-denial \
$(TEST_DIR)/check-component-output \
$(TEST_DIR)/check-component-meta \
$(TEST_DIR)/check-component-cap \
$(TEST_DIR)/check-components-built \
$(TEST_DIR)/check-log-prefixes \
$(TEST_DIR)/check-live-probes \
$(TEST_DIR)/check-hash-parity \
$(TEST_DIR)/check-text-floor \
$(TEST_DIR)/check-text-region \
$(TEST_DIR)/check-confidence-floor \
$(TEST_DIR)/check-text-provenance \
$(TEST_DIR)/check-arch-macros \
$(TEST_DIR)/check-lattice-seam \
$(TEST_DIR)/check-page-offset-substitution \
$(TEST_DIR)/check-render-default \
$(TEST_DIR)/check-image-size \
$(TEST_DIR)/check-dram-base \
$(TEST_DIR)/check-fdt-unflatten \
$(TEST_DIR)/check-ksymoff \
$(TEST_DIR)/check-manpages \
$(TEST_DIR)/check-readout-docs \
$(TEST_DIR)/check-doc-structure \
$(TEST_DIR)/check-doc-identifiers \
$(TEST_DIR)/check-diagram-data \
$(TEST_DIR)/check-arch-axes \
$(TEST_DIR)/check-guard-docs \
$(TEST_DIR)/check-matrix-summary \
$(TEST_DIR)/check-version \
$(TEST_DIR)/check-posture-diff \
$(TEST_DIR)/check-posture-summary \
$(TEST_DIR)/check-validators \
$(TEST_DIR)/check-env-docs \
$(TEST_DIR)/check-shellcheck \
$(TEST_DIR)/check-fuzz-harnesses \
$(TEST_DIR)/check-property-arches \
$(TEST_DIR)/check-stext-gap \
$(TEST_DIR)/check-baseline \
$(TEST_DIR)/check-render-parity \
$(TEST_DIR)/check-render-color \
$(TEST_DIR)/check-wire-text \
$(TEST_DIR)/check-sysroot-containment \
$(TEST_DIR)/hardening-fixtures \
$(TEST_DIR)/cli-flags
.PHONY: test-integration
test-integration : $(TEST_INT_BIN)
$(TEST_INT_BIN)
# Container / cgroup execution harness (opt-in: snapshots the live host and runs
# live cpuset probes, so it is not part of the hermetic `make test`). The x86_32
# coupled-arch soundness case needs the i686 cross binary (`make cross`); it
# skips cleanly if absent.
.PHONY: test-container
test-container : build $(SECCOMP_EXEC_BIN) $(FORK_FAIL_LIB)
@SECCOMP_EXEC=$(SECCOMP_EXEC_BIN) FORK_FAIL_LIB=$(FORK_FAIL_LIB) $(TEST_DIR)/container/run
.PHONY: test-estimate
test-estimate : $(TEST_EST_BIN)
$(TEST_EST_BIN)
.PHONY: test-evidence
test-evidence : $(TEST_EV_BIN)
$(TEST_EV_BIN)
.PHONY: test-dmesg-layout
test-dmesg-layout : $(TEST_DMESG_BIN)
$(TEST_DMESG_BIN)
.PHONY: test-btf
test-btf : $(TEST_BTF_BIN)
$(TEST_BTF_BIN)
.PHONY: test-dmesg-reservations
test-dmesg-reservations : $(TEST_DMESG_RESV_BIN)
$(TEST_DMESG_RESV_BIN)
.PHONY: test-boot-params-e820
test-boot-params-e820 : $(TEST_BPE820_BIN)
$(TEST_BPE820_BIN)
.PHONY: test-sysfs-parsers
test-sysfs-parsers : $(TEST_PARSERS_BIN)
$(TEST_PARSERS_BIN)
# Cross-architecture engine test: runs the integration test under qemu-user for
# each 64-bit target (exercises arch-gated rules on their arch). Needs the
# musl-cross toolchains on PATH + qemu-user in QEMU_DIR;
# silently skips any target whose toolchain/qemu is absent. Not part of `make
# test` (host-only, no qemu dependency).
.PHONY: test-cross
test-cross :
$(TEST_DIR)/test-cross
# Offline soundness gate: run extra/validate-bundle over the truth-bearing
# fixtures (meta anonymized: 0) and assert the resolved window contains the real
# base. Standalone (needs jq, the per-arch binaries from `make cross`, and
# qemu-user in QEMU_DIR for foreign arches) — the reproducible, boot-free
# complement to tests/vm/run.
.PHONY: test-fixtures
test-fixtures :
$(TEST_DIR)/validate-fixtures
# Truth-free complement: assert the GUARANTEED window does not move when a
# fakeable input (MemTotal/LowTotal) is shrunk, across the WHOLE fixture corpus
# (incl. anonymized) — catches the "container-faked value reaches the guaranteed
# window" class on every coupled arch, not just the truth-bearing captures.
.PHONY: test-fixtures-perturb
test-fixtures-perturb :
$(TEST_DIR)/validate-fixtures --perturb
# Optional line-coverage report for the engine + rules (build/coverage/). Uses
# --coverage (gcc and clang) + the compiler's own gcov — no extra package for
# the text summary; HTML appears only if lcov+genhtml are installed. The normal
# build/test never use --coverage, so this adds no dependency to them. For a
# clang toolchain: make coverage GCOV="llvm-cov gcov".
.PHONY: coverage
coverage :
CC="$(CC)" $(TEST_DIR)/coverage
# End-to-end coverage of the real kasld binary (orchestrator engine-bridge +
# main + render — the parts -DKASLD_TESTING hides from `make coverage`). Runs
# the instrumented binary live + over the x86_64 fixtures, natively; x86_64 host
# only. Same optional/no-extra-dep story as `coverage`.
.PHONY: coverage-e2e
coverage-e2e :
CC="$(CC)" $(TEST_DIR)/coverage-e2e
# CI entrypoint: the full host test suite. Deterministic, no qemu/cross needed;
# `make` halts on the first failing test binary (each returns non-zero on
# failure). For cross-arch coverage run `make test-cross` and `tests/replay`.
.PHONY: check
check : test
@echo "OK: host test suite passed."
.PHONY: test-engine
test-engine : $(TEST_ENG_BIN)
$(TEST_ENG_BIN)
# Parser fuzz harnesses (tests/fuzz/, opt-in). Each builds against libFuzzer
# with ASan + UBSan. Requires clang or another compiler shipping
# -fsanitize=fuzzer; the host build never touches these. The default build
# graph does NOT depend on fuzz, so the absence of clang/libFuzzer is invisible
# unless the operator asks for it. See tests/fuzz/README.md for run options.
FUZZ_CC ?= clang
FUZZ_CFLAGS ?= -O1 -g -fsanitize=fuzzer,address,undefined -DKASLD_TESTING -I src
FUZZ_OUT := $(BUILD_DIR)/fuzz
# Derived from the tree, not listed: a hand-maintained list is a second
# inventory, and a harness missing from it is never built — which reads as
# "nothing to report" rather than as a harness nobody compiles.
FUZZ_TARGETS := $(patsubst tests/fuzz/%.c,%,$(wildcard tests/fuzz/fuzz_*.c))
FUZZ_BINS := $(addprefix $(FUZZ_OUT)/,$(FUZZ_TARGETS))
# A harness names the parser it drives by #including the source file holding
# it, so most of the program arrives through that one translation unit. What
# does not is the estimate lattice and the quantity table: the orchestrator
# reads both, they live in their own objects, and a harness that includes
# orchestrator.c will not link without them.
FUZZ_SRCS := src/estimate.c src/quantities.c
$(FUZZ_OUT)/% : tests/fuzz/%.c $(FUZZ_SRCS)
@mkdir -p "$(FUZZ_OUT)"
$(call ccv,CCLD,$@)
$(Q)$(FUZZ_CC) $(FUZZ_CFLAGS) "$<" $(FUZZ_SRCS) -o "$@"
.PHONY: fuzz
fuzz : $(FUZZ_BINS)
@echo "Fuzz harnesses built in $(FUZZ_OUT)."
@echo "Run e.g.: $(FUZZ_OUT)/fuzz_capture_result tests/fuzz/corpus/capture_result/"
.PHONY: clean
clean :
@echo "Cleaning $(call disp,$(BUILD_DIR)) ..."
@rm -rf "$(BUILD_DIR)"
# Install the orchestrator binary and the component executables.
#
# Test binaries live in $(BUILD_DIR)/tests/ and fuzz harnesses in
# $(BUILD_DIR)/fuzz/ — both are siblings of $(OBJ_DIR), so the install
# globs below ($(KASLD_BIN) names exactly one path; $(COMP_DIR)/* matches
# only the components/ subdir) cannot reach them. The install target also
# depends on `build`, not on `test` or `fuzz`, so neither is even built
# by an install-only invocation.
.PHONY: install
install : build
install -d "$(DESTDIR)$(PREFIX)/bin"
install -m 755 $(KASLD_BIN) "$(DESTDIR)$(PREFIX)/bin/kasld"
install -m 755 extra/ksymoff "$(DESTDIR)$(PREFIX)/bin/ksymoff"
install -d "$(DESTDIR)$(PREFIX)/libexec/kasld"
@# Install only real component binaries. Arch-gated components leave a
@# non-executable stamp at their target path (so make treats them as
@# up-to-date and does not re-run the failing compile); the -x test keeps
@# those stamps out of the install tree.
for f in $(COMP_DIR)/*; do \
[ -x "$$f" ] || continue; \
install -m 755 "$$f" "$(DESTDIR)$(PREFIX)/libexec/kasld/"; \
done
install -d "$(DESTDIR)$(PREFIX)/share/doc/kasld"
cp -R docs README.md LICENSE THIRD-PARTY-NOTICES.md "$(DESTDIR)$(PREFIX)/share/doc/kasld/"
install -d "$(DESTDIR)$(PREFIX)/share/man/man1"
install -m 644 man/kasld.1 man/ksymoff.1 "$(DESTDIR)$(PREFIX)/share/man/man1/"
.PHONY: uninstall