diff --git a/pkgs/test/cc-wrapper/fortify1-example.c b/pkgs/test/cc-wrapper/fortify1-example.c new file mode 100644 index 00000000000..48b9c268e72 --- /dev/null +++ b/pkgs/test/cc-wrapper/fortify1-example.c @@ -0,0 +1,16 @@ +/* an example that should be protected by FORTIFY_SOURCE=1 */ +#include +#include +#include + + +int main(int argc, char *argv[]) { + /* allocate on the heap so we're likely to get an + * over-allocation and can be more sure that a + * failure is because of fortify protection rather + * than a genuine segfault */ + char* buffer = malloc(sizeof(char) * 7); + strcpy(buffer, argv[1]); + puts(buffer); + return 0; +} diff --git a/pkgs/test/cc-wrapper/fortify2-example.c b/pkgs/test/cc-wrapper/fortify2-example.c new file mode 100644 index 00000000000..dfb5a8e8729 --- /dev/null +++ b/pkgs/test/cc-wrapper/fortify2-example.c @@ -0,0 +1,16 @@ +/* an example that should be protected by FORTIFY_SOURCE=2 but + * not FORTIFY_SOURCE=1 */ +#include +#include + +struct buffer_with_pad { + char buffer[7]; + char pad[25]; +}; + +int main(int argc, char *argv[]) { + struct buffer_with_pad b; + strcpy(b.buffer, argv[1]); + puts(b.buffer); + return 0; +} diff --git a/pkgs/test/cc-wrapper/fortify3-example.c b/pkgs/test/cc-wrapper/fortify3-example.c new file mode 100644 index 00000000000..9a0a5f4792c --- /dev/null +++ b/pkgs/test/cc-wrapper/fortify3-example.c @@ -0,0 +1,13 @@ +/* an example that should be protected by FORTIFY_SOURCE=3 but + * not FORTIFY_SOURCE=2 */ +#include +#include +#include + + +int main(int argc, char *argv[]) { + char* buffer = malloc(atoi(argv[2])); + strcpy(buffer, argv[1]); + puts(buffer); + return 0; +} diff --git a/pkgs/test/cc-wrapper/hardening.nix b/pkgs/test/cc-wrapper/hardening.nix new file mode 100644 index 00000000000..41ddaefdfea --- /dev/null +++ b/pkgs/test/cc-wrapper/hardening.nix @@ -0,0 +1,396 @@ +{ lib +, stdenv +, runCommand +, runCommandWith +, runCommandCC +, debian-devscripts +}: + +let + # writeCBin from trivial-builders won't let us choose + # our own stdenv + writeCBinWithStdenv = codePath: stdenv': env: runCommandWith { + name = "test-bin"; + stdenv = stdenv'; + derivationArgs = { + inherit codePath; + preferLocalBuild = true; + allowSubstitutes = false; + } // env; + } '' + [ -n "$preBuild" ] && eval "$preBuild" + n=$out/bin/test-bin + mkdir -p "$(dirname "$n")" + cp "$codePath" code.c + NIX_DEBUG=1 $CC -x c code.c -O1 $TEST_EXTRA_FLAGS -o "$n" + ''; + + f1exampleWithStdEnv = writeCBinWithStdenv ./fortify1-example.c; + f2exampleWithStdEnv = writeCBinWithStdenv ./fortify2-example.c; + f3exampleWithStdEnv = writeCBinWithStdenv ./fortify3-example.c; + + stdenvUnsupport = additionalUnsupported: stdenv.override { + cc = stdenv.cc.override { + cc = (lib.extendDerivation true { + hardeningUnsupportedFlags = (stdenv.cc.cc.hardeningUnsupportedFlags or []) ++ additionalUnsupported; + } stdenv.cc.cc); + }; + allowedRequisites = null; + }; + + checkTestBin = testBin: { + # can only test flags that are detectable by hardening-check + ignoreBindNow ? true, + ignoreFortify ? true, + ignorePie ? true, + ignoreRelRO ? true, + ignoreStackProtector ? true, + expectFailure ? false, + }: let + expectFailureClause = lib.optionalString expectFailure + " && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && exit 1"; + in runCommandCC "check-test-bin" { + nativeBuildInputs = [ debian-devscripts ]; + buildInputs = [ testBin ]; + meta.platforms = lib.platforms.linux; # ELF-reliant + } '' + hardening-check --nocfprotection \ + ${lib.optionalString ignoreBindNow "--nobindnow"} \ + ${lib.optionalString ignoreFortify "--nofortify"} \ + ${lib.optionalString ignorePie "--nopie"} \ + ${lib.optionalString ignoreRelRO "--norelro"} \ + ${lib.optionalString ignoreStackProtector "--nostackprotector"} \ + $(PATH=$HOST_PATH type -P test-bin) ${expectFailureClause} + touch $out + ''; + + nameDrvAfterAttrName = builtins.mapAttrs (name: drv: + drv.overrideAttrs (_: { name = "test-${name}"; }) + ); + + # returning a specific exit code when aborting due to a fortify + # check isn't mandated. so it's better to just ensure that a + # nonzero exit code is returned when we go a single byte beyond + # the buffer, with the example programs being designed to be + # unlikely to genuinely segfault for such a small overflow. + fortifyExecTest = testBin: runCommand "exec-test" { + buildInputs = [ + testBin + ]; + meta.broken = !(stdenv.buildPlatform.canExecute stdenv.hostPlatform); + } '' + ( + export PATH=$HOST_PATH + echo "Saturated buffer:" # check program isn't completly broken + test-bin 012345 7 + echo "One byte too far:" # eighth byte being the null terminator + (! test-bin 0123456 7) || (echo 'Expected failure, but succeeded!' && exit 1) + ) + echo "Expected behaviour observed" + touch $out + ''; + + brokenIf = cond: drv: if cond then drv.overrideAttrs (old: { meta = old.meta or {} // { broken = true; }; }) else drv; + +in nameDrvAfterAttrName ({ + bindNowExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "bindnow" ]; + }) { + ignoreBindNow = false; + }); + + # musl implementation undetectable by this means even if present + fortifyExplicitEnabled = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + }) { + ignoreFortify = false; + }); + + fortify1ExplicitEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + }); + + # musl implementation is effectively FORTIFY_SOURCE=1-only, + # clang-on-glibc also only appears to support FORTIFY_SOURCE=1 (!) + fortifyExplicitEnabledExecTest = brokenIf ( + stdenv.hostPlatform.isMusl || (stdenv.cc.isClang && stdenv.hostPlatform.libc == "glibc") + ) (fortifyExecTest (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + })); + + fortify3ExplicitEnabled = brokenIf ( + stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12" + ) (checkTestBin (f3exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify3" ]; + }) { + ignoreFortify = false; + }); + + # musl implementation is effectively FORTIFY_SOURCE=1-only + fortify3ExplicitEnabledExecTest = brokenIf ( + stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12" + ) (fortifyExecTest (f3exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify3" ]; + })); + + pieExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "pie" ]; + }) { + ignorePie = false; + }); + + relROExplicitEnabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "relro" ]; + }) { + ignoreRelRO = false; + }; + + stackProtectorExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "stackprotector" ]; + }) { + ignoreStackProtector = false; + }); + + bindNowExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "bindnow" ]; + }) { + ignoreBindNow = false; + expectFailure = true; + }; + + fortifyExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3ExplicitDisabled = checkTestBin (f3exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify3" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortifyExplicitDisabledDisablesFortify3 = checkTestBin (f3exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify3" ]; + hardeningDisable = [ "fortify" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3ExplicitDisabledDoesntDisableFortify = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + hardeningDisable = [ "fortify3" ]; + }) { + ignoreFortify = false; + }; + + pieExplicitDisabled = brokenIf ( + stdenv.hostPlatform.isMusl && stdenv.cc.isClang + ) (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "pie" ]; + }) { + ignorePie = false; + expectFailure = true; + }); + + # can't force-disable ("partial"?) relro + relROExplicitDisabled = brokenIf true (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "pie" ]; + }) { + ignoreRelRO = false; + expectFailure = true; + }); + + stackProtectorExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "stackprotector" ]; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + # most flags can't be "unsupported" by compiler alone and + # binutils doesn't have an accessible hardeningUnsupportedFlags + # mechanism, so can only test a couple of flags through altered + # stdenv trickery + + fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify"]) { + hardeningEnable = [ "fortify" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3StdenvUnsupp = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify3"]) { + hardeningEnable = [ "fortify3" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortifyStdenvUnsuppUnsupportsFortify3 = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify"]) { + hardeningEnable = [ "fortify3" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3StdenvUnsuppDoesntUnsuppFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) { + hardeningEnable = [ "fortify" ]; + }) { + ignoreFortify = false; + }); + + fortify3StdenvUnsuppDoesntUnsuppFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) { + hardeningEnable = [ "fortify" ]; + }); + + stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) { + hardeningEnable = [ "stackprotector" ]; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + # NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable + # and hardeningEnable + + stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "stackprotector" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="stackprotector" + ''; + }) { + ignoreStackProtector = false; + }; + + stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "all" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="stackprotector" + ''; + }) { + ignoreStackProtector = false; + }; + + stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "stackprotector" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="" + ''; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + fortify3EnabledEnvEnablesFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" "fortify3" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="fortify3" + ''; + }) { + ignoreFortify = false; + }); + + fortify3EnabledEnvEnablesFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" "fortify3" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="fortify3" + ''; + }); + + fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" "fortify3" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="fortify" + ''; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + # NIX_HARDENING_ENABLE can't enable an unsupported feature + + stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) { + preBuild = '' + export NIX_HARDENING_ENABLE="stackprotector" + ''; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + # undetectable by this means on static even if present + fortify1ExplicitEnabledCmdlineDisabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f1exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0' + ''; + }) { + ignoreFortify = false; + expectFailure = true; + }); + + # musl implementation undetectable by this means even if present + fortify1ExplicitDisabledCmdlineEnabled = brokenIf ( + stdenv.hostPlatform.isMusl || stdenv.hostPlatform.isStatic + ) (checkTestBin (f1exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1' + ''; + }) { + ignoreFortify = false; + }); + + fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1' + ''; + }); + + fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror' + ''; + }; + +} // (let + tb = f2exampleWithStdEnv stdenv { + hardeningDisable = [ "all" ]; + hardeningEnable = [ "fortify" "pie" ]; + }; +in { + + allExplicitDisabledBindNow = checkTestBin tb { + ignoreBindNow = false; + expectFailure = true; + }; + + allExplicitDisabledFortify = checkTestBin tb { + ignoreFortify = false; + expectFailure = true; + }; + + allExplicitDisabledPie = brokenIf ( + stdenv.hostPlatform.isMusl && stdenv.cc.isClang + ) (checkTestBin tb { + ignorePie = false; + expectFailure = true; + }); + + # can't force-disable ("partial"?) relro + allExplicitDisabledRelRO = brokenIf true (checkTestBin tb { + ignoreRelRO = false; + expectFailure = true; + }); + + allExplicitDisabledStackProtector = checkTestBin tb { + ignoreStackProtector = false; + expectFailure = true; + }; +})) diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index d6fd75359fc..c479070c607 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -23,6 +23,14 @@ with pkgs; stdenv-inputs = callPackage ./stdenv-inputs { }; stdenv = callPackage ./stdenv { }; + hardeningFlags = recurseIntoAttrs (callPackage ./cc-wrapper/hardening.nix {}); + hardeningFlags-gcc = recurseIntoAttrs (callPackage ./cc-wrapper/hardening.nix { + stdenv = gccStdenv; + }); + hardeningFlags-clang = recurseIntoAttrs (callPackage ./cc-wrapper/hardening.nix { + stdenv = llvmPackages.stdenv; + }); + config = callPackage ./config.nix { }; haskell = callPackage ./haskell { }; diff --git a/pkgs/tools/misc/debian-devscripts/default.nix b/pkgs/tools/misc/debian-devscripts/default.nix index e1ee9757fe8..b8a58ff400e 100644 --- a/pkgs/tools/misc/debian-devscripts/default.nix +++ b/pkgs/tools/misc/debian-devscripts/default.nix @@ -1,4 +1,4 @@ -{lib, stdenv, fetchurl, xz, dpkg +{lib, stdenv, fetchurl, fetchpatch, xz, dpkg , libxslt, docbook_xsl, makeWrapper, writeShellScript , python3Packages , perlPackages, curl, gnupg, diffutils, nano, pkg-config, bash-completion, help2man @@ -19,6 +19,14 @@ in stdenv.mkDerivation rec { hash = "sha256-j0fUVTS/lPKFdgeMhksiJz2+E5koB07IK2uEj55EWG0="; }; + patches = [ + (fetchpatch { + name = "hardening-check-obey-binutils-env-vars.patch"; + url = "https://github.com/Debian/devscripts/pull/2/commits/c6a018e0ef50a1b0cb4962a2f96dae7c6f21f1d4.patch"; + hash = "sha256-UpS239JiAM1IYxNuJLdILq2h0xlR5t0Tzhj47xiMHww="; + }) + ]; + postPatch = '' substituteInPlace scripts/Makefile --replace /usr/share/dpkg ${dpkg}/share/dpkg substituteInPlace scripts/debrebuild.pl --replace /usr/bin/perl ${perlPackages.perl}/bin/perl