anvil-editor: init at 0.4
[NixPkgs.git] / pkgs / test / cc-wrapper / hardening.nix
blobfb30d17841e3da58b1c98924a192c4c145164296
1 { lib
2 , stdenv
3 , runCommand
4 , runCommandWith
5 , runCommandCC
6 , hello
7 , debian-devscripts
8 }:
10 let
11   # writeCBin from trivial-builders won't let us choose
12   # our own stdenv
13   writeCBinWithStdenv = codePath: stdenv': env: runCommandWith {
14     name = "test-bin";
15     stdenv = stdenv';
16     derivationArgs = {
17       inherit codePath;
18       preferLocalBuild = true;
19       allowSubstitutes = false;
20     } // env;
21   } ''
22     [ -n "$postConfigure" ] && eval "$postConfigure"
23     [ -n "$preBuild" ] && eval "$preBuild"
24     n=$out/bin/test-bin
25     mkdir -p "$(dirname "$n")"
26     cp "$codePath" code.c
27     NIX_DEBUG=1 $CC -x c code.c -O1 $TEST_EXTRA_FLAGS -o "$n"
28   '';
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 ({
36     preBuild = ''
37       export CFLAGS="$TEST_EXTRA_FLAGS"
38     '';
39     NIX_DEBUG = "1";
40     postFixup = ''
41       cp $out/bin/hello $out/bin/test-bin
42     '';
43   } // env);
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
53         # at this point.
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;
60       } stdenv.cc.cc);
61     };
62     allowedRequisites = null;
63   };
65   checkTestBin = testBin: {
66     # can only test flags that are detectable by hardening-check
67     ignoreBindNow ? true,
68     ignoreFortify ? true,
69     ignorePie ? true,
70     ignoreRelRO ? true,
71     ignoreStackProtector ? true,
72     ignoreStackClashProtection ? true,
73     expectFailure ? false,
74   }: let
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
84   } (''
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 ;} ;}
97   '' + ''
98     } ; then
99   '' + lib.optionalString expectFailure ''
100       echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2
101   '' + ''
102       exit 2
103     fi
104   '');
106   nameDrvAfterAttrName = builtins.mapAttrs (name: drv:
107     drv.overrideAttrs (_: { name = "test-${name}"; })
108   );
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" {
116     buildInputs = [
117       testBin
118     ];
119     meta.broken = !(stdenv.buildPlatform.canExecute stdenv.hostPlatform);
120   } ''
121     (
122       export PATH=$HOST_PATH
123       echo "Saturated buffer:" # check program isn't completly broken
124       test-bin 012345 7
125       echo "One byte too far:" # eighth byte being the null terminator
126       (! test-bin 0123456 7) || (echo 'Expected failure, but succeeded!' && exit 1)
127     )
128     echo "Expected behaviour observed"
129     touch $out
130   '';
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" ];
137   }) {
138     ignoreBindNow = false;
139   });
141   # musl implementation undetectable by this means even if present
142   fortifyExplicitEnabled = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv {
143     hardeningEnable = [ "fortify" ];
144   }) {
145     ignoreFortify = false;
146   });
148   fortify1ExplicitEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
149     hardeningEnable = [ "fortify" ];
150   });
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" ];
158   }));
160   fortify3ExplicitEnabled = brokenIf (
161     stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
162   ) (checkTestBin (f3exampleWithStdEnv stdenv {
163     hardeningEnable = [ "fortify3" ];
164   }) {
165     ignoreFortify = false;
166   });
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" ];
173   }));
175   pieExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
176     hardeningEnable = [ "pie" ];
177   }) {
178     ignorePie = false;
179   });
181   pieExplicitEnabledStructuredAttrs = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
182     hardeningEnable = [ "pie" ];
183     __structuredAttrs = true;
184   }) {
185     ignorePie = false;
186   });
188   relROExplicitEnabled = checkTestBin (f2exampleWithStdEnv stdenv {
189     hardeningEnable = [ "relro" ];
190   }) {
191     ignoreRelRO = false;
192   };
194   stackProtectorExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
195     hardeningEnable = [ "stackprotector" ];
196   }) {
197     ignoreStackProtector = false;
198   });
200   # protection patterns generated by clang not detectable?
201   stackClashProtectionExplicitEnabled = brokenIf stdenv.cc.isClang (checkTestBin (helloWithStdEnv stdenv {
202     hardeningEnable = [ "stackclashprotection" ];
203   }) {
204     ignoreStackClashProtection = false;
205   });
207   bindNowExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
208     hardeningDisable = [ "bindnow" ];
209   }) {
210     ignoreBindNow = false;
211     expectFailure = true;
212   };
214   fortifyExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
215     hardeningDisable = [ "fortify" ];
216   }) {
217     ignoreFortify = false;
218     expectFailure = true;
219   };
221   fortify3ExplicitDisabled = checkTestBin (f3exampleWithStdEnv stdenv {
222     hardeningDisable = [ "fortify3" ];
223   }) {
224     ignoreFortify = false;
225     expectFailure = true;
226   };
228   fortifyExplicitDisabledDisablesFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
229     hardeningEnable = [ "fortify3" ];
230     hardeningDisable = [ "fortify" ];
231   }) {
232     ignoreFortify = false;
233     expectFailure = true;
234   };
236   fortify3ExplicitDisabledDoesntDisableFortify = checkTestBin (f2exampleWithStdEnv stdenv {
237     hardeningEnable = [ "fortify" ];
238     hardeningDisable = [ "fortify3" ];
239   }) {
240     ignoreFortify = false;
241   };
243   pieExplicitDisabled = brokenIf (
244     stdenv.hostPlatform.isMusl && stdenv.cc.isClang
245   ) (checkTestBin (f2exampleWithStdEnv stdenv {
246     hardeningDisable = [ "pie" ];
247   }) {
248     ignorePie = false;
249     expectFailure = true;
250   });
252   # can't force-disable ("partial"?) relro
253   relROExplicitDisabled = brokenIf true (checkTestBin (f2exampleWithStdEnv stdenv {
254     hardeningDisable = [ "pie" ];
255   }) {
256     ignoreRelRO = false;
257     expectFailure = true;
258   });
260   stackProtectorExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
261     hardeningDisable = [ "stackprotector" ];
262   }) {
263     ignoreStackProtector = false;
264     expectFailure = true;
265   };
267   stackClashProtectionExplicitDisabled = checkTestBin (helloWithStdEnv stdenv {
268     hardeningDisable = [ "stackclashprotection" ];
269   }) {
270     ignoreStackClashProtection = false;
271     expectFailure = true;
272   };
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
277   # stdenv trickery
279   fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify" "fortify3"]) {
280     hardeningEnable = [ "fortify" ];
281   }) {
282     ignoreFortify = false;
283     expectFailure = true;
284   };
286   fortify3StdenvUnsupp = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
287     hardeningEnable = [ "fortify3" ];
288   }) {
289     ignoreFortify = false;
290     expectFailure = true;
291   };
293   fortifyStdenvUnsuppUnsupportsFortify3 = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify"]) {
294     hardeningEnable = [ "fortify3" ];
295   }) {
296     ignoreFortify = false;
297     expectFailure = true;
298   };
300   # musl implementation undetectable by this means even if present
301   fortify3StdenvUnsuppDoesntUnsuppFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
302     hardeningEnable = [ "fortify" ];
303   }) {
304     ignoreFortify = false;
305   });
307   fortify3StdenvUnsuppDoesntUnsuppFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
308     hardeningEnable = [ "fortify" ];
309   });
311   stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
312     hardeningEnable = [ "stackprotector" ];
313   }) {
314     ignoreStackProtector = false;
315     expectFailure = true;
316   };
318   stackClashProtectionStdenvUnsupp = checkTestBin (helloWithStdEnv (stdenvUnsupport ["stackclashprotection"]) {
319     hardeningEnable = [ "stackclashprotection" ];
320   }) {
321     ignoreStackClashProtection = false;
322     expectFailure = true;
323   };
325   # NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable
326   # and hardeningEnable
328   stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
329     hardeningDisable = [ "stackprotector" ];
330     postConfigure = ''
331       export NIX_HARDENING_ENABLE="stackprotector"
332     '';
333   }) {
334     ignoreStackProtector = false;
335   };
337   stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv {
338     hardeningDisable = [ "all" ];
339     postConfigure = ''
340       export NIX_HARDENING_ENABLE="stackprotector"
341     '';
342   }) {
343     ignoreStackProtector = false;
344   };
346   stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
347     hardeningEnable = [ "stackprotector" ];
348     postConfigure = ''
349       export NIX_HARDENING_ENABLE=""
350     '';
351   }) {
352     ignoreStackProtector = false;
353     expectFailure = true;
354   };
356   # musl implementation undetectable by this means even if present
357   fortify3EnabledEnvEnablesFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv stdenv {
358     hardeningDisable = [ "fortify" "fortify3" ];
359     postConfigure = ''
360       export NIX_HARDENING_ENABLE="fortify3"
361     '';
362   }) {
363     ignoreFortify = false;
364   });
366   fortify3EnabledEnvEnablesFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
367     hardeningDisable = [ "fortify" "fortify3" ];
368     postConfigure = ''
369       export NIX_HARDENING_ENABLE="fortify3"
370     '';
371   });
373   fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
374     hardeningDisable = [ "fortify" "fortify3" ];
375     postConfigure = ''
376       export NIX_HARDENING_ENABLE="fortify"
377     '';
378   }) {
379     ignoreFortify = false;
380     expectFailure = true;
381   };
383   # NIX_HARDENING_ENABLE can't enable an unsupported feature
384   stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
385     postConfigure = ''
386       export NIX_HARDENING_ENABLE="stackprotector"
387     '';
388   }) {
389     ignoreStackProtector = false;
390     expectFailure = true;
391   };
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" ];
399     postConfigure = ''
400       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0'
401     '';
402   }) {
403     ignoreFortify = false;
404     expectFailure = false;
405   });
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" ];
415     postConfigure = ''
416       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
417     '';
418   }) {
419     ignoreFortify = false;
420   });
422   fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
423     hardeningDisable = [ "fortify" ];
424     postConfigure = ''
425       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
426     '';
427   });
429   fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv {
430     hardeningEnable = [ "fortify" ];
431     postConfigure = ''
432       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror'
433     '';
434   };
436 } // (let
437   tb = f2exampleWithStdEnv stdenv {
438     hardeningDisable = [ "all" ];
439     hardeningEnable = [ "fortify" "pie" ];
440   };
441 in {
443   allExplicitDisabledBindNow = checkTestBin tb {
444     ignoreBindNow = false;
445     expectFailure = true;
446   };
448   allExplicitDisabledFortify = checkTestBin tb {
449     ignoreFortify = false;
450     expectFailure = true;
451   };
453   allExplicitDisabledPie = brokenIf (
454     stdenv.hostPlatform.isMusl && stdenv.cc.isClang
455   ) (checkTestBin tb {
456     ignorePie = false;
457     expectFailure = true;
458   });
460   # can't force-disable ("partial"?) relro
461   allExplicitDisabledRelRO = brokenIf true (checkTestBin tb {
462     ignoreRelRO = false;
463     expectFailure = true;
464   });
466   allExplicitDisabledStackProtector = checkTestBin tb {
467     ignoreStackProtector = false;
468     expectFailure = true;
469   };
471   allExplicitDisabledStackClashProtection = checkTestBin tb {
472     ignoreStackClashProtection = false;
473     expectFailure = true;
474   };