11 # writeCBin from trivial-builders won't let us choose
13 writeCBinWithStdenv = codePath: stdenv': env: runCommandWith {
18 preferLocalBuild = true;
19 allowSubstitutes = false;
22 [ -n "$postConfigure" ] && eval "$postConfigure"
23 [ -n "$preBuild" ] && eval "$preBuild"
25 mkdir -p "$(dirname "$n")"
27 NIX_DEBUG=1 $CC -x c code.c -O1 $TEST_EXTRA_FLAGS -o "$n"
30 f1exampleWithStdEnv = writeCBinWithStdenv ./fortify1-example.c;
31 f2exampleWithStdEnv = writeCBinWithStdenv ./fortify2-example.c;
32 f3exampleWithStdEnv = writeCBinWithStdenv ./fortify3-example.c;
34 # for when we need a slightly more complicated program
35 helloWithStdEnv = stdenv': env: (hello.override { stdenv = stdenv'; }).overrideAttrs ({
37 export CFLAGS="$TEST_EXTRA_FLAGS"
41 cp $out/bin/hello $out/bin/test-bin
45 stdenvUnsupport = additionalUnsupported: stdenv.override {
46 cc = stdenv.cc.override {
47 cc = (lib.extendDerivation true rec {
48 # this is ugly - have to cross-reference from
49 # hardeningUnsupportedFlagsByTargetPlatform to hardeningUnsupportedFlags
50 # because the finalAttrs mechanism that hardeningUnsupportedFlagsByTargetPlatform
51 # implementations use to do this won't work with lib.extendDerivation.
52 # but it's simplified by the fact that targetPlatform is already fixed
54 hardeningUnsupportedFlagsByTargetPlatform = _: hardeningUnsupportedFlags;
55 hardeningUnsupportedFlags = (
56 if stdenv.cc.cc ? hardeningUnsupportedFlagsByTargetPlatform
57 then stdenv.cc.cc.hardeningUnsupportedFlagsByTargetPlatform stdenv.targetPlatform
58 else (stdenv.cc.cc.hardeningUnsupportedFlags or [])
59 ) ++ additionalUnsupported;
62 allowedRequisites = null;
65 checkTestBin = testBin: {
66 # can only test flags that are detectable by hardening-check
71 ignoreStackProtector ? true,
72 ignoreStackClashProtection ? true,
73 expectFailure ? false,
75 stackClashStr = "Stack clash protection: yes";
76 expectFailureClause = lib.optionalString expectFailure
77 " && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && false";
78 in runCommandCC "check-test-bin" {
79 nativeBuildInputs = [ debian-devscripts ];
80 buildInputs = [ testBin ];
81 meta.platforms = if ignoreStackClashProtection
82 then lib.platforms.linux # ELF-reliant
83 else [ "x86_64-linux" ]; # stackclashprotection test looks for x86-specific instructions
85 if ${lib.optionalString (!expectFailure) "!"} {
86 hardening-check --nocfprotection \
87 ${lib.optionalString ignoreBindNow "--nobindnow"} \
88 ${lib.optionalString ignoreFortify "--nofortify"} \
89 ${lib.optionalString ignorePie "--nopie"} \
90 ${lib.optionalString ignoreRelRO "--norelro"} \
91 ${lib.optionalString ignoreStackProtector "--nostackprotector"} \
92 $(PATH=$HOST_PATH type -P test-bin) | tee $out
93 '' + lib.optionalString (!ignoreStackClashProtection) ''
94 # stack clash protection doesn't actually affect the exit code of
95 # hardening-check (likely authors think false negatives too common)
96 { grep -F '${stackClashStr}' $out || { echo "Didn't find '${stackClashStr}' in output" && false ;} ;}
99 '' + lib.optionalString expectFailure ''
100 echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2
106 nameDrvAfterAttrName = builtins.mapAttrs (name: drv:
107 drv.overrideAttrs (_: { name = "test-${name}"; })
110 # returning a specific exit code when aborting due to a fortify
111 # check isn't mandated. so it's better to just ensure that a
112 # nonzero exit code is returned when we go a single byte beyond
113 # the buffer, with the example programs being designed to be
114 # unlikely to genuinely segfault for such a small overflow.
115 fortifyExecTest = testBin: runCommand "exec-test" {
119 meta.broken = !(stdenv.buildPlatform.canExecute stdenv.hostPlatform);
122 export PATH=$HOST_PATH
123 echo "Saturated buffer:" # check program isn't completly broken
125 echo "One byte too far:" # eighth byte being the null terminator
126 (! test-bin 0123456 7) || (echo 'Expected failure, but succeeded!' && exit 1)
128 echo "Expected behaviour observed"
132 brokenIf = cond: drv: if cond then drv.overrideAttrs (old: { meta = old.meta or {} // { broken = true; }; }) else drv;
134 in nameDrvAfterAttrName ({
135 bindNowExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
136 hardeningEnable = [ "bindnow" ];
138 ignoreBindNow = false;
141 # musl implementation undetectable by this means even if present
142 fortifyExplicitEnabled = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv {
143 hardeningEnable = [ "fortify" ];
145 ignoreFortify = false;
148 fortify1ExplicitEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
149 hardeningEnable = [ "fortify" ];
152 # musl implementation is effectively FORTIFY_SOURCE=1-only,
153 # clang-on-glibc also only appears to support FORTIFY_SOURCE=1 (!)
154 fortifyExplicitEnabledExecTest = brokenIf (
155 stdenv.hostPlatform.isMusl || (stdenv.cc.isClang && stdenv.hostPlatform.libc == "glibc")
156 ) (fortifyExecTest (f2exampleWithStdEnv stdenv {
157 hardeningEnable = [ "fortify" ];
160 fortify3ExplicitEnabled = brokenIf (
161 stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
162 ) (checkTestBin (f3exampleWithStdEnv stdenv {
163 hardeningEnable = [ "fortify3" ];
165 ignoreFortify = false;
168 # musl implementation is effectively FORTIFY_SOURCE=1-only
169 fortify3ExplicitEnabledExecTest = brokenIf (
170 stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
171 ) (fortifyExecTest (f3exampleWithStdEnv stdenv {
172 hardeningEnable = [ "fortify3" ];
175 pieExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
176 hardeningEnable = [ "pie" ];
181 pieExplicitEnabledStructuredAttrs = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
182 hardeningEnable = [ "pie" ];
183 __structuredAttrs = true;
188 relROExplicitEnabled = checkTestBin (f2exampleWithStdEnv stdenv {
189 hardeningEnable = [ "relro" ];
194 stackProtectorExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
195 hardeningEnable = [ "stackprotector" ];
197 ignoreStackProtector = false;
200 # protection patterns generated by clang not detectable?
201 stackClashProtectionExplicitEnabled = brokenIf stdenv.cc.isClang (checkTestBin (helloWithStdEnv stdenv {
202 hardeningEnable = [ "stackclashprotection" ];
204 ignoreStackClashProtection = false;
207 bindNowExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
208 hardeningDisable = [ "bindnow" ];
210 ignoreBindNow = false;
211 expectFailure = true;
214 fortifyExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
215 hardeningDisable = [ "fortify" ];
217 ignoreFortify = false;
218 expectFailure = true;
221 fortify3ExplicitDisabled = checkTestBin (f3exampleWithStdEnv stdenv {
222 hardeningDisable = [ "fortify3" ];
224 ignoreFortify = false;
225 expectFailure = true;
228 fortifyExplicitDisabledDisablesFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
229 hardeningEnable = [ "fortify3" ];
230 hardeningDisable = [ "fortify" ];
232 ignoreFortify = false;
233 expectFailure = true;
236 fortify3ExplicitDisabledDoesntDisableFortify = checkTestBin (f2exampleWithStdEnv stdenv {
237 hardeningEnable = [ "fortify" ];
238 hardeningDisable = [ "fortify3" ];
240 ignoreFortify = false;
243 pieExplicitDisabled = brokenIf (
244 stdenv.hostPlatform.isMusl && stdenv.cc.isClang
245 ) (checkTestBin (f2exampleWithStdEnv stdenv {
246 hardeningDisable = [ "pie" ];
249 expectFailure = true;
252 # can't force-disable ("partial"?) relro
253 relROExplicitDisabled = brokenIf true (checkTestBin (f2exampleWithStdEnv stdenv {
254 hardeningDisable = [ "pie" ];
257 expectFailure = true;
260 stackProtectorExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
261 hardeningDisable = [ "stackprotector" ];
263 ignoreStackProtector = false;
264 expectFailure = true;
267 stackClashProtectionExplicitDisabled = checkTestBin (helloWithStdEnv stdenv {
268 hardeningDisable = [ "stackclashprotection" ];
270 ignoreStackClashProtection = false;
271 expectFailure = true;
274 # most flags can't be "unsupported" by compiler alone and
275 # binutils doesn't have an accessible hardeningUnsupportedFlags
276 # mechanism, so can only test a couple of flags through altered
279 fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify" "fortify3"]) {
280 hardeningEnable = [ "fortify" ];
282 ignoreFortify = false;
283 expectFailure = true;
286 fortify3StdenvUnsupp = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
287 hardeningEnable = [ "fortify3" ];
289 ignoreFortify = false;
290 expectFailure = true;
293 fortifyStdenvUnsuppUnsupportsFortify3 = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify"]) {
294 hardeningEnable = [ "fortify3" ];
296 ignoreFortify = false;
297 expectFailure = true;
300 # musl implementation undetectable by this means even if present
301 fortify3StdenvUnsuppDoesntUnsuppFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
302 hardeningEnable = [ "fortify" ];
304 ignoreFortify = false;
307 fortify3StdenvUnsuppDoesntUnsuppFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
308 hardeningEnable = [ "fortify" ];
311 stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
312 hardeningEnable = [ "stackprotector" ];
314 ignoreStackProtector = false;
315 expectFailure = true;
318 stackClashProtectionStdenvUnsupp = checkTestBin (helloWithStdEnv (stdenvUnsupport ["stackclashprotection"]) {
319 hardeningEnable = [ "stackclashprotection" ];
321 ignoreStackClashProtection = false;
322 expectFailure = true;
325 # NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable
326 # and hardeningEnable
328 stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
329 hardeningDisable = [ "stackprotector" ];
331 export NIX_HARDENING_ENABLE="stackprotector"
334 ignoreStackProtector = false;
337 stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv {
338 hardeningDisable = [ "all" ];
340 export NIX_HARDENING_ENABLE="stackprotector"
343 ignoreStackProtector = false;
346 stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
347 hardeningEnable = [ "stackprotector" ];
349 export NIX_HARDENING_ENABLE=""
352 ignoreStackProtector = false;
353 expectFailure = true;
356 # musl implementation undetectable by this means even if present
357 fortify3EnabledEnvEnablesFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv stdenv {
358 hardeningDisable = [ "fortify" "fortify3" ];
360 export NIX_HARDENING_ENABLE="fortify3"
363 ignoreFortify = false;
366 fortify3EnabledEnvEnablesFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
367 hardeningDisable = [ "fortify" "fortify3" ];
369 export NIX_HARDENING_ENABLE="fortify3"
373 fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
374 hardeningDisable = [ "fortify" "fortify3" ];
376 export NIX_HARDENING_ENABLE="fortify"
379 ignoreFortify = false;
380 expectFailure = true;
383 # NIX_HARDENING_ENABLE can't enable an unsupported feature
384 stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
386 export NIX_HARDENING_ENABLE="stackprotector"
389 ignoreStackProtector = false;
390 expectFailure = true;
393 # current implementation prevents the command-line from disabling
394 # fortify if cc-wrapper is enabling it.
396 # undetectable by this means on static even if present
397 fortify1ExplicitEnabledCmdlineDisabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f1exampleWithStdEnv stdenv {
398 hardeningEnable = [ "fortify" ];
400 export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0'
403 ignoreFortify = false;
404 expectFailure = false;
407 # current implementation doesn't force-disable fortify if
408 # command-line enables it even if we use hardeningDisable.
410 # musl implementation undetectable by this means even if present
411 fortify1ExplicitDisabledCmdlineEnabled = brokenIf (
412 stdenv.hostPlatform.isMusl || stdenv.hostPlatform.isStatic
413 ) (checkTestBin (f1exampleWithStdEnv stdenv {
414 hardeningDisable = [ "fortify" ];
416 export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
419 ignoreFortify = false;
422 fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
423 hardeningDisable = [ "fortify" ];
425 export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
429 fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv {
430 hardeningEnable = [ "fortify" ];
432 export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror'
437 tb = f2exampleWithStdEnv stdenv {
438 hardeningDisable = [ "all" ];
439 hardeningEnable = [ "fortify" "pie" ];
443 allExplicitDisabledBindNow = checkTestBin tb {
444 ignoreBindNow = false;
445 expectFailure = true;
448 allExplicitDisabledFortify = checkTestBin tb {
449 ignoreFortify = false;
450 expectFailure = true;
453 allExplicitDisabledPie = brokenIf (
454 stdenv.hostPlatform.isMusl && stdenv.cc.isClang
457 expectFailure = true;
460 # can't force-disable ("partial"?) relro
461 allExplicitDisabledRelRO = brokenIf true (checkTestBin tb {
463 expectFailure = true;
466 allExplicitDisabledStackProtector = checkTestBin tb {
467 ignoreStackProtector = false;
468 expectFailure = true;
471 allExplicitDisabledStackClashProtection = checkTestBin tb {
472 ignoreStackClashProtection = false;
473 expectFailure = true;