Merge pull request #268619 from tweag/lib-descriptions
[NixPkgs.git] / pkgs / test / cc-wrapper / hardening.nix
blob41ddaefdfea8dec9f49db4e674f58d5d282ea7ec
1 { lib
2 , stdenv
3 , runCommand
4 , runCommandWith
5 , runCommandCC
6 , debian-devscripts
7 }:
9 let
10   # writeCBin from trivial-builders won't let us choose
11   # our own stdenv
12   writeCBinWithStdenv = codePath: stdenv': env: runCommandWith {
13     name = "test-bin";
14     stdenv = stdenv';
15     derivationArgs = {
16       inherit codePath;
17       preferLocalBuild = true;
18       allowSubstitutes = false;
19     } // env;
20   } ''
21     [ -n "$preBuild" ] && eval "$preBuild"
22     n=$out/bin/test-bin
23     mkdir -p "$(dirname "$n")"
24     cp "$codePath" code.c
25     NIX_DEBUG=1 $CC -x c code.c -O1 $TEST_EXTRA_FLAGS -o "$n"
26   '';
28   f1exampleWithStdEnv = writeCBinWithStdenv ./fortify1-example.c;
29   f2exampleWithStdEnv = writeCBinWithStdenv ./fortify2-example.c;
30   f3exampleWithStdEnv = writeCBinWithStdenv ./fortify3-example.c;
32   stdenvUnsupport = additionalUnsupported: stdenv.override {
33     cc = stdenv.cc.override {
34       cc = (lib.extendDerivation true {
35         hardeningUnsupportedFlags = (stdenv.cc.cc.hardeningUnsupportedFlags or []) ++ additionalUnsupported;
36       } stdenv.cc.cc);
37     };
38     allowedRequisites = null;
39   };
41   checkTestBin = testBin: {
42     # can only test flags that are detectable by hardening-check
43     ignoreBindNow ? true,
44     ignoreFortify ? true,
45     ignorePie ? true,
46     ignoreRelRO ? true,
47     ignoreStackProtector ? true,
48     expectFailure ? false,
49   }: let
50     expectFailureClause = lib.optionalString expectFailure
51       " && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && exit 1";
52   in runCommandCC "check-test-bin" {
53     nativeBuildInputs = [ debian-devscripts ];
54     buildInputs = [ testBin ];
55     meta.platforms = lib.platforms.linux;  # ELF-reliant
56   } ''
57     hardening-check --nocfprotection \
58       ${lib.optionalString ignoreBindNow "--nobindnow"} \
59       ${lib.optionalString ignoreFortify "--nofortify"} \
60       ${lib.optionalString ignorePie "--nopie"} \
61       ${lib.optionalString ignoreRelRO "--norelro"} \
62       ${lib.optionalString ignoreStackProtector "--nostackprotector"} \
63       $(PATH=$HOST_PATH type -P test-bin) ${expectFailureClause}
64     touch $out
65   '';
67   nameDrvAfterAttrName = builtins.mapAttrs (name: drv:
68     drv.overrideAttrs (_: { name = "test-${name}"; })
69   );
71   # returning a specific exit code when aborting due to a fortify
72   # check isn't mandated. so it's better to just ensure that a
73   # nonzero exit code is returned when we go a single byte beyond
74   # the buffer, with the example programs being designed to be
75   # unlikely to genuinely segfault for such a small overflow.
76   fortifyExecTest = testBin: runCommand "exec-test" {
77     buildInputs = [
78       testBin
79     ];
80     meta.broken = !(stdenv.buildPlatform.canExecute stdenv.hostPlatform);
81   } ''
82     (
83       export PATH=$HOST_PATH
84       echo "Saturated buffer:" # check program isn't completly broken
85       test-bin 012345 7
86       echo "One byte too far:" # eighth byte being the null terminator
87       (! test-bin 0123456 7) || (echo 'Expected failure, but succeeded!' && exit 1)
88     )
89     echo "Expected behaviour observed"
90     touch $out
91   '';
93   brokenIf = cond: drv: if cond then drv.overrideAttrs (old: { meta = old.meta or {} // { broken = true; }; }) else drv;
95 in nameDrvAfterAttrName ({
96   bindNowExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
97     hardeningEnable = [ "bindnow" ];
98   }) {
99     ignoreBindNow = false;
100   });
102   # musl implementation undetectable by this means even if present
103   fortifyExplicitEnabled = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv {
104     hardeningEnable = [ "fortify" ];
105   }) {
106     ignoreFortify = false;
107   });
109   fortify1ExplicitEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
110     hardeningEnable = [ "fortify" ];
111   });
113   # musl implementation is effectively FORTIFY_SOURCE=1-only,
114   # clang-on-glibc also only appears to support FORTIFY_SOURCE=1 (!)
115   fortifyExplicitEnabledExecTest = brokenIf (
116     stdenv.hostPlatform.isMusl || (stdenv.cc.isClang && stdenv.hostPlatform.libc == "glibc")
117   ) (fortifyExecTest (f2exampleWithStdEnv stdenv {
118     hardeningEnable = [ "fortify" ];
119   }));
121   fortify3ExplicitEnabled = brokenIf (
122     stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
123   ) (checkTestBin (f3exampleWithStdEnv stdenv {
124     hardeningEnable = [ "fortify3" ];
125   }) {
126     ignoreFortify = false;
127   });
129   # musl implementation is effectively FORTIFY_SOURCE=1-only
130   fortify3ExplicitEnabledExecTest = brokenIf (
131     stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
132   ) (fortifyExecTest (f3exampleWithStdEnv stdenv {
133     hardeningEnable = [ "fortify3" ];
134   }));
136   pieExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
137     hardeningEnable = [ "pie" ];
138   }) {
139     ignorePie = false;
140   });
142   relROExplicitEnabled = checkTestBin (f2exampleWithStdEnv stdenv {
143     hardeningEnable = [ "relro" ];
144   }) {
145     ignoreRelRO = false;
146   };
148   stackProtectorExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
149     hardeningEnable = [ "stackprotector" ];
150   }) {
151     ignoreStackProtector = false;
152   });
154   bindNowExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
155     hardeningDisable = [ "bindnow" ];
156   }) {
157     ignoreBindNow = false;
158     expectFailure = true;
159   };
161   fortifyExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
162     hardeningDisable = [ "fortify" ];
163   }) {
164     ignoreFortify = false;
165     expectFailure = true;
166   };
168   fortify3ExplicitDisabled = checkTestBin (f3exampleWithStdEnv stdenv {
169     hardeningDisable = [ "fortify3" ];
170   }) {
171     ignoreFortify = false;
172     expectFailure = true;
173   };
175   fortifyExplicitDisabledDisablesFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
176     hardeningEnable = [ "fortify3" ];
177     hardeningDisable = [ "fortify" ];
178   }) {
179     ignoreFortify = false;
180     expectFailure = true;
181   };
183   fortify3ExplicitDisabledDoesntDisableFortify = checkTestBin (f2exampleWithStdEnv stdenv {
184     hardeningEnable = [ "fortify" ];
185     hardeningDisable = [ "fortify3" ];
186   }) {
187     ignoreFortify = false;
188   };
190   pieExplicitDisabled = brokenIf (
191     stdenv.hostPlatform.isMusl && stdenv.cc.isClang
192   ) (checkTestBin (f2exampleWithStdEnv stdenv {
193     hardeningDisable = [ "pie" ];
194   }) {
195     ignorePie = false;
196     expectFailure = true;
197   });
199   # can't force-disable ("partial"?) relro
200   relROExplicitDisabled = brokenIf true (checkTestBin (f2exampleWithStdEnv stdenv {
201     hardeningDisable = [ "pie" ];
202   }) {
203     ignoreRelRO = false;
204     expectFailure = true;
205   });
207   stackProtectorExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
208     hardeningDisable = [ "stackprotector" ];
209   }) {
210     ignoreStackProtector = false;
211     expectFailure = true;
212   };
214   # most flags can't be "unsupported" by compiler alone and
215   # binutils doesn't have an accessible hardeningUnsupportedFlags
216   # mechanism, so can only test a couple of flags through altered
217   # stdenv trickery
219   fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify"]) {
220     hardeningEnable = [ "fortify" ];
221   }) {
222     ignoreFortify = false;
223     expectFailure = true;
224   };
226   fortify3StdenvUnsupp = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
227     hardeningEnable = [ "fortify3" ];
228   }) {
229     ignoreFortify = false;
230     expectFailure = true;
231   };
233   fortifyStdenvUnsuppUnsupportsFortify3 = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify"]) {
234     hardeningEnable = [ "fortify3" ];
235   }) {
236     ignoreFortify = false;
237     expectFailure = true;
238   };
240   fortify3StdenvUnsuppDoesntUnsuppFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
241     hardeningEnable = [ "fortify" ];
242   }) {
243     ignoreFortify = false;
244   });
246   fortify3StdenvUnsuppDoesntUnsuppFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
247     hardeningEnable = [ "fortify" ];
248   });
250   stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
251     hardeningEnable = [ "stackprotector" ];
252   }) {
253     ignoreStackProtector = false;
254     expectFailure = true;
255   };
257   # NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable
258   # and hardeningEnable
260   stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
261     hardeningDisable = [ "stackprotector" ];
262     preBuild = ''
263       export NIX_HARDENING_ENABLE="stackprotector"
264     '';
265   }) {
266     ignoreStackProtector = false;
267   };
269   stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv {
270     hardeningDisable = [ "all" ];
271     preBuild = ''
272       export NIX_HARDENING_ENABLE="stackprotector"
273     '';
274   }) {
275     ignoreStackProtector = false;
276   };
278   stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
279     hardeningEnable = [ "stackprotector" ];
280     preBuild = ''
281       export NIX_HARDENING_ENABLE=""
282     '';
283   }) {
284     ignoreStackProtector = false;
285     expectFailure = true;
286   };
288   fortify3EnabledEnvEnablesFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv {
289     hardeningDisable = [ "fortify" "fortify3" ];
290     preBuild = ''
291       export NIX_HARDENING_ENABLE="fortify3"
292     '';
293   }) {
294     ignoreFortify = false;
295   });
297   fortify3EnabledEnvEnablesFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv stdenv {
298     hardeningDisable = [ "fortify" "fortify3" ];
299     preBuild = ''
300       export NIX_HARDENING_ENABLE="fortify3"
301     '';
302   });
304   fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
305     hardeningDisable = [ "fortify" "fortify3" ];
306     preBuild = ''
307       export NIX_HARDENING_ENABLE="fortify"
308     '';
309   }) {
310     ignoreFortify = false;
311     expectFailure = true;
312   };
314   # NIX_HARDENING_ENABLE can't enable an unsupported feature
316   stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
317     preBuild = ''
318       export NIX_HARDENING_ENABLE="stackprotector"
319     '';
320   }) {
321     ignoreStackProtector = false;
322     expectFailure = true;
323   };
325   # undetectable by this means on static even if present
326   fortify1ExplicitEnabledCmdlineDisabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f1exampleWithStdEnv stdenv {
327     hardeningEnable = [ "fortify" ];
328     preBuild = ''
329       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0'
330     '';
331   }) {
332     ignoreFortify = false;
333     expectFailure = true;
334   });
336   # musl implementation undetectable by this means even if present
337   fortify1ExplicitDisabledCmdlineEnabled = brokenIf (
338     stdenv.hostPlatform.isMusl || stdenv.hostPlatform.isStatic
339   ) (checkTestBin (f1exampleWithStdEnv stdenv {
340     hardeningDisable = [ "fortify" ];
341     preBuild = ''
342       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
343     '';
344   }) {
345     ignoreFortify = false;
346   });
348   fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
349     hardeningDisable = [ "fortify" ];
350     preBuild = ''
351       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
352     '';
353   });
355   fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv {
356     hardeningEnable = [ "fortify" ];
357     preBuild = ''
358       export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror'
359     '';
360   };
362 } // (let
363   tb = f2exampleWithStdEnv stdenv {
364     hardeningDisable = [ "all" ];
365     hardeningEnable = [ "fortify" "pie" ];
366   };
367 in {
369   allExplicitDisabledBindNow = checkTestBin tb {
370     ignoreBindNow = false;
371     expectFailure = true;
372   };
374   allExplicitDisabledFortify = checkTestBin tb {
375     ignoreFortify = false;
376     expectFailure = true;
377   };
379   allExplicitDisabledPie = brokenIf (
380     stdenv.hostPlatform.isMusl && stdenv.cc.isClang
381   ) (checkTestBin tb {
382     ignorePie = false;
383     expectFailure = true;
384   });
386   # can't force-disable ("partial"?) relro
387   allExplicitDisabledRelRO = brokenIf true (checkTestBin tb {
388     ignoreRelRO = false;
389     expectFailure = true;
390   });
392   allExplicitDisabledStackProtector = checkTestBin tb {
393     ignoreStackProtector = false;
394     expectFailure = true;
395   };