Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cc-wrapper hardeningFlags tests: fix expected behaviour in corner cases, add tests for stackclashprotection #253186

Merged
merged 3 commits into from
Jul 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 104 additions & 32 deletions pkgs/test/cc-wrapper/hardening.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
, runCommand
, runCommandWith
, runCommandCC
, hello
, debian-devscripts
}:

Expand All @@ -18,6 +19,7 @@ let
allowSubstitutes = false;
} // env;
} ''
[ -n "$postConfigure" ] && eval "$postConfigure"
[ -n "$preBuild" ] && eval "$preBuild"
n=$out/bin/test-bin
mkdir -p "$(dirname "$n")"
Expand All @@ -29,10 +31,32 @@ let
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 {
hardeningUnsupportedFlags = (stdenv.cc.cc.hardeningUnsupportedFlags or []) ++ additionalUnsupported;
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;
Expand All @@ -45,24 +69,39 @@ let
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 && exit 1";
" && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && false";
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
'';
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}"; })
Expand Down Expand Up @@ -151,6 +190,13 @@ in nameDrvAfterAttrName ({
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" ];
}) {
Expand Down Expand Up @@ -211,12 +257,19 @@ in nameDrvAfterAttrName ({
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"]) {
fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify" "fortify3"]) {
hardeningEnable = [ "fortify" ];
}) {
ignoreFortify = false;
Expand All @@ -237,13 +290,14 @@ in nameDrvAfterAttrName ({
expectFailure = true;
};

fortify3StdenvUnsuppDoesntUnsuppFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
# musl implementation undetectable by this means even if present
fortify3StdenvUnsuppDoesntUnsuppFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
hardeningEnable = [ "fortify" ];
}) {
ignoreFortify = false;
});

fortify3StdenvUnsuppDoesntUnsuppFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
fortify3StdenvUnsuppDoesntUnsuppFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
hardeningEnable = [ "fortify" ];
});

Expand All @@ -254,12 +308,19 @@ in nameDrvAfterAttrName ({
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" ];
preBuild = ''
postConfigure = ''
export NIX_HARDENING_ENABLE="stackprotector"
'';
}) {
Expand All @@ -268,7 +329,7 @@ in nameDrvAfterAttrName ({

stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv {
hardeningDisable = [ "all" ];
preBuild = ''
postConfigure = ''
export NIX_HARDENING_ENABLE="stackprotector"
'';
}) {
Expand All @@ -277,33 +338,34 @@ in nameDrvAfterAttrName ({

stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
hardeningEnable = [ "stackprotector" ];
preBuild = ''
postConfigure = ''
export NIX_HARDENING_ENABLE=""
'';
}) {
ignoreStackProtector = false;
expectFailure = true;
};

fortify3EnabledEnvEnablesFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv {
# musl implementation undetectable by this means even if present
fortify3EnabledEnvEnablesFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv stdenv {
hardeningDisable = [ "fortify" "fortify3" ];
preBuild = ''
postConfigure = ''
export NIX_HARDENING_ENABLE="fortify3"
'';
}) {
ignoreFortify = false;
});

fortify3EnabledEnvEnablesFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv stdenv {
fortify3EnabledEnvEnablesFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
hardeningDisable = [ "fortify" "fortify3" ];
preBuild = ''
postConfigure = ''
export NIX_HARDENING_ENABLE="fortify3"
'';
});

fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
hardeningDisable = [ "fortify" "fortify3" ];
preBuild = ''
postConfigure = ''
export NIX_HARDENING_ENABLE="fortify"
'';
}) {
Expand All @@ -312,33 +374,38 @@ in nameDrvAfterAttrName ({
};

# NIX_HARDENING_ENABLE can't enable an unsupported feature

stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
preBuild = ''
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" ];
preBuild = ''
postConfigure = ''
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0'
'';
}) {
ignoreFortify = false;
expectFailure = true;
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" ];
preBuild = ''
postConfigure = ''
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
'';
}) {
Expand All @@ -347,14 +414,14 @@ in nameDrvAfterAttrName ({

fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
hardeningDisable = [ "fortify" ];
preBuild = ''
postConfigure = ''
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
'';
});

fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv {
hardeningEnable = [ "fortify" ];
preBuild = ''
postConfigure = ''
export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror'
'';
};
Expand Down Expand Up @@ -393,4 +460,9 @@ in {
ignoreStackProtector = false;
expectFailure = true;
};

allExplicitDisabledStackClashProtection = checkTestBin tb {
ignoreStackClashProtection = false;
expectFailure = true;
};
}))