mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-26 15:44:20 +00:00
aaeeef5b6c
Replaces / Closes #353131 A while ago `postgresql` switched to using structured attrs[1]. In the PR it was reported that this made postgresql notably slower when importing SQL dumps[2]. After a bit of debugging it turned out that the hardening was entirely missing and the following combination of settings was the culprit: hardeningEnable = [ "pie" ]; __structuredAttrs = true; I.e. the combination of custom hardening settings and structured attrs. What happened here is that internally the default and enabled hardening flags get written into `NIX_HARDENING_ENABLE`. However, the value is a list and the setting is not in the `env` section. This means that in the structured-attrs case we get something like declare -ax NIX_HARDENING_ENABLE=([0]="bindnow" [1]="format" [2]="fortify" [3]="fortify3" [4]="pic" [5]="relro" [6]="stackprotector" [7]="strictoverflow" [8]="zerocallusedregs" [9]="pie") i.e. an actual array rather than a string with all hardening flags being space-separated which is what the hardening code of the cc-wrapper expects[3]. This only happens if `hardeningEnable` or `hardeningDisable` are explicitly set by a derivation: if none of those are set, `NIX_HARDENING_ENABLE` won't be set by `stdenv.mkDerivation` and the default hardening flags are configured by the setup hook of the cc-wrapper[4]. In other words, this _only_ applies to derivations that have both custom hardening settings _and_ `__structuredAttrs = true;`. All values of `NIX_HARDENING_ENABLE` are well-known, so we don't have to worry about escaping issues. Just forcing it to a string by concatenating the list everytime solves the issue without additional issues like eval errors when inheriting `env` from a structuredAttrs derivation[5]. The price we're paying is a full rebuild. [1] https://github.com/NixOS/nixpkgs/pull/294504 [2] https://github.com/NixOS/nixpkgs/pull/294504#issuecomment-2451482522 [3]cf3e5d3744/pkgs/build-support/cc-wrapper/add-hardening.sh (L9)
[4]cf3e5d3744/pkgs/build-support/cc-wrapper/setup-hook.sh (L114)
[5]1e84a7fb95
476 lines
15 KiB
Nix
476 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;
|
|
});
|
|
|
|
pieExplicitEnabledStructuredAttrs = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
|
|
hardeningEnable = [ "pie" ];
|
|
__structuredAttrs = true;
|
|
}) {
|
|
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;
|
|
};
|
|
}))
|