mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-25 16:33:15 +00:00
2e0d7e230a
these were not updated to understand hardeningUnsupportedFlagsByTargetPlatform when it was added causing more tests to fail for clang than otherwise would
469 lines
15 KiB
Nix
469 lines
15 KiB
Nix
{ lib
|
|
, stdenv
|
|
, runCommand
|
|
, runCommandWith
|
|
, runCommandCC
|
|
, hello
|
|
, 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 "$postConfigure" ] && eval "$postConfigure"
|
|
[ -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;
|
|
|
|
# for when we need a slightly more complicated program
|
|
helloWithStdEnv = stdenv': env: (hello.override { stdenv = stdenv'; }).overrideAttrs ({
|
|
preBuild = ''
|
|
export CFLAGS="$TEST_EXTRA_FLAGS"
|
|
'';
|
|
NIX_DEBUG = "1";
|
|
postFixup = ''
|
|
cp $out/bin/hello $out/bin/test-bin
|
|
'';
|
|
} // env);
|
|
|
|
stdenvUnsupport = additionalUnsupported: stdenv.override {
|
|
cc = stdenv.cc.override {
|
|
cc = (lib.extendDerivation true rec {
|
|
# this is ugly - have to cross-reference from
|
|
# hardeningUnsupportedFlagsByTargetPlatform to hardeningUnsupportedFlags
|
|
# because the finalAttrs mechanism that hardeningUnsupportedFlagsByTargetPlatform
|
|
# implementations use to do this won't work with lib.extendDerivation.
|
|
# but it's simplified by the fact that targetPlatform is already fixed
|
|
# at this point.
|
|
hardeningUnsupportedFlagsByTargetPlatform = _: hardeningUnsupportedFlags;
|
|
hardeningUnsupportedFlags = (
|
|
if stdenv.cc.cc ? hardeningUnsupportedFlagsByTargetPlatform
|
|
then stdenv.cc.cc.hardeningUnsupportedFlagsByTargetPlatform stdenv.targetPlatform
|
|
else (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,
|
|
ignoreStackClashProtection ? true,
|
|
expectFailure ? false,
|
|
}: let
|
|
stackClashStr = "Stack clash protection: yes";
|
|
expectFailureClause = lib.optionalString expectFailure
|
|
" && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && false";
|
|
in runCommandCC "check-test-bin" {
|
|
nativeBuildInputs = [ debian-devscripts ];
|
|
buildInputs = [ testBin ];
|
|
meta.platforms = if ignoreStackClashProtection
|
|
then lib.platforms.linux # ELF-reliant
|
|
else [ "x86_64-linux" ]; # stackclashprotection test looks for x86-specific instructions
|
|
} (''
|
|
if ${lib.optionalString (!expectFailure) "!"} {
|
|
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) | tee $out
|
|
'' + lib.optionalString (!ignoreStackClashProtection) ''
|
|
# stack clash protection doesn't actually affect the exit code of
|
|
# hardening-check (likely authors think false negatives too common)
|
|
{ grep -F '${stackClashStr}' $out || { echo "Didn't find '${stackClashStr}' in output" && false ;} ;}
|
|
'' + ''
|
|
} ; then
|
|
'' + lib.optionalString expectFailure ''
|
|
echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2
|
|
'' + ''
|
|
exit 2
|
|
fi
|
|
'');
|
|
|
|
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;
|
|
});
|
|
|
|
# protection patterns generated by clang not detectable?
|
|
stackClashProtectionExplicitEnabled = brokenIf stdenv.cc.isClang (checkTestBin (helloWithStdEnv stdenv {
|
|
hardeningEnable = [ "stackclashprotection" ];
|
|
}) {
|
|
ignoreStackClashProtection = 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;
|
|
};
|
|
|
|
stackClashProtectionExplicitDisabled = checkTestBin (helloWithStdEnv stdenv {
|
|
hardeningDisable = [ "stackclashprotection" ];
|
|
}) {
|
|
ignoreStackClashProtection = 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" "fortify3"]) {
|
|
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;
|
|
};
|
|
|
|
# musl implementation undetectable by this means even if present
|
|
fortify3StdenvUnsuppDoesntUnsuppFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
|
|
hardeningEnable = [ "fortify" ];
|
|
}) {
|
|
ignoreFortify = false;
|
|
});
|
|
|
|
fortify3StdenvUnsuppDoesntUnsuppFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
|
|
hardeningEnable = [ "fortify" ];
|
|
});
|
|
|
|
stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
|
|
hardeningEnable = [ "stackprotector" ];
|
|
}) {
|
|
ignoreStackProtector = false;
|
|
expectFailure = true;
|
|
};
|
|
|
|
stackClashProtectionStdenvUnsupp = checkTestBin (helloWithStdEnv (stdenvUnsupport ["stackclashprotection"]) {
|
|
hardeningEnable = [ "stackclashprotection" ];
|
|
}) {
|
|
ignoreStackClashProtection = false;
|
|
expectFailure = true;
|
|
};
|
|
|
|
# NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable
|
|
# and hardeningEnable
|
|
|
|
stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "stackprotector" ];
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE="stackprotector"
|
|
'';
|
|
}) {
|
|
ignoreStackProtector = false;
|
|
};
|
|
|
|
stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "all" ];
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE="stackprotector"
|
|
'';
|
|
}) {
|
|
ignoreStackProtector = false;
|
|
};
|
|
|
|
stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
|
|
hardeningEnable = [ "stackprotector" ];
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE=""
|
|
'';
|
|
}) {
|
|
ignoreStackProtector = false;
|
|
expectFailure = true;
|
|
};
|
|
|
|
# musl implementation undetectable by this means even if present
|
|
fortify3EnabledEnvEnablesFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "fortify" "fortify3" ];
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE="fortify3"
|
|
'';
|
|
}) {
|
|
ignoreFortify = false;
|
|
});
|
|
|
|
fortify3EnabledEnvEnablesFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "fortify" "fortify3" ];
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE="fortify3"
|
|
'';
|
|
});
|
|
|
|
fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "fortify" "fortify3" ];
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE="fortify"
|
|
'';
|
|
}) {
|
|
ignoreFortify = false;
|
|
expectFailure = true;
|
|
};
|
|
|
|
# NIX_HARDENING_ENABLE can't enable an unsupported feature
|
|
stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
|
|
postConfigure = ''
|
|
export NIX_HARDENING_ENABLE="stackprotector"
|
|
'';
|
|
}) {
|
|
ignoreStackProtector = false;
|
|
expectFailure = true;
|
|
};
|
|
|
|
# current implementation prevents the command-line from disabling
|
|
# fortify if cc-wrapper is enabling it.
|
|
|
|
# undetectable by this means on static even if present
|
|
fortify1ExplicitEnabledCmdlineDisabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f1exampleWithStdEnv stdenv {
|
|
hardeningEnable = [ "fortify" ];
|
|
postConfigure = ''
|
|
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0'
|
|
'';
|
|
}) {
|
|
ignoreFortify = false;
|
|
expectFailure = false;
|
|
});
|
|
|
|
# current implementation doesn't force-disable fortify if
|
|
# command-line enables it even if we use hardeningDisable.
|
|
|
|
# musl implementation undetectable by this means even if present
|
|
fortify1ExplicitDisabledCmdlineEnabled = brokenIf (
|
|
stdenv.hostPlatform.isMusl || stdenv.hostPlatform.isStatic
|
|
) (checkTestBin (f1exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "fortify" ];
|
|
postConfigure = ''
|
|
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
|
|
'';
|
|
}) {
|
|
ignoreFortify = false;
|
|
});
|
|
|
|
fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
|
|
hardeningDisable = [ "fortify" ];
|
|
postConfigure = ''
|
|
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
|
|
'';
|
|
});
|
|
|
|
fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv {
|
|
hardeningEnable = [ "fortify" ];
|
|
postConfigure = ''
|
|
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;
|
|
};
|
|
|
|
allExplicitDisabledStackClashProtection = checkTestBin tb {
|
|
ignoreStackClashProtection = false;
|
|
expectFailure = true;
|
|
};
|
|
}))
|