From 2a45fd10397434e8eb54a430a56558902b75f512 Mon Sep 17 00:00:00 2001 From: Robert Scott Date: Wed, 21 May 2025 21:46:52 +0100 Subject: [PATCH 1/2] cc-wrapper: add support for libcxxhardeningfast/extensive hardening flags as with glibcxxassertions, we don't yet have a nice mechanism for deferring support decisions to the c++ library in use, so for now at least enabling this hardening flag will cause _LIBCPP_HARDENING_MODE to be defined on all compilers --- doc/redirects.json | 6 + doc/release-notes/rl-2511.section.md | 4 +- doc/stdenv/stdenv.chapter.md | 12 ++ .../bintools-wrapper/default.nix | 2 + .../build-support/cc-wrapper/add-hardening.sh | 26 ++- pkgs/stdenv/generic/make-derivation.nix | 4 + pkgs/test/cc-wrapper/hardening.nix | 171 ++++++++++++++++-- pkgs/top-level/variants.nix | 1 + 8 files changed, 211 insertions(+), 15 deletions(-) diff --git a/doc/redirects.json b/doc/redirects.json index d1e9e1724ce0..879eb37bd369 100644 --- a/doc/redirects.json +++ b/doc/redirects.json @@ -124,6 +124,12 @@ "inkscape-plugins": [ "index.html#inkscape-plugins" ], + "libcxxhardeningextensive": [ + "index.html#libcxxhardeningextensive" + ], + "libcxxhardeningfast": [ + "index.html#libcxxhardeningfast" + ], "major-ghc-deprecation": [ "index.html#major-ghc-deprecation" ], diff --git a/doc/release-notes/rl-2511.section.md b/doc/release-notes/rl-2511.section.md index 674198d1dc82..b552bc487a92 100644 --- a/doc/release-notes/rl-2511.section.md +++ b/doc/release-notes/rl-2511.section.md @@ -245,7 +245,7 @@ - `installShellCompletion`: now supports Nushell completion files -- New hardening flags, `strictflexarrays1` and `strictflexarrays3` were made available, corresponding to the gcc/clang options `-fstrict-flex-arrays=1` and `-fstrict-flex-arrays=3` respectively. +- New hardening flags `strictflexarrays1`, `strictflexarrays3`, `glibcxxassertions`, `libcxxhardeningfast` and `libcxxhardeningextensive` were made available. - `gramps` has been updated to 6.0.0 Upstream recommends [backing up your Family Trees](https://gramps-project.org/wiki/index.php/Gramps_6.0_Wiki_Manual_-_Manage_Family_Trees#Backing_up_a_Family_Tree) before upgrading. @@ -261,8 +261,6 @@ - `searx` was updated to use `envsubst` instead of `sed` for parsing secrets from environment variables. If your previous configuration included a secret reference like `server.secret_key = "@SEARX_SECRET_KEY@"`, you must migrate to the new envsubst syntax: `server.secret_key = "$SEARX_SECRET_KEY"`. -- A new hardening flag, `glibcxxassertions` was made available, corresponding to the glibc `_GLIBCXX_ASSERTIONS` option. - - `versionCheckHook`: Packages that previously relied solely on `pname` to locate the program used to version check, but have a differing `meta.mainProgram` entry, might now fail. diff --git a/doc/stdenv/stdenv.chapter.md b/doc/stdenv/stdenv.chapter.md index dc49e205a68f..2a0b239740c0 100644 --- a/doc/stdenv/stdenv.chapter.md +++ b/doc/stdenv/stdenv.chapter.md @@ -1675,6 +1675,18 @@ Adds the `-D_GLIBCXX_ASSERTIONS` compiler flag. This flag only has an effect on These checks may have an impact on performance in some cases. +#### `libcxxhardeningfast` {#libcxxhardeningfast} + +Adds the `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST` compiler flag. This flag only has an effect on libc++ targets, and when defined, enables a set of assertions that prevent undefined behavior caused by violating preconditions of the standard library. libc++ provides several hardening modes, and this "fast" mode contains a set of security-critical checks that can be done with relatively little overhead in constant time. + +Disabling `libcxxhardeningfast` implies disablement of checks from `libcxxhardeningextensive`. + +#### `libcxxhardeningextensive` {#libcxxhardeningextensive} + +Adds the `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE` compiler flag. This flag only has an effect on libc++ targets, and when defined, enables a set of assertions that prevent undefined behavior caused by violating preconditions of the standard library. libc++ provides several hardening modes, and this "extensive" mode adds checks for undefined behavior that incur relatively little overhead but aren’t security-critical. The additional rigour impacts performance more than fast mode: benchmarking is recommended to determine if it is acceptable for a particular application. + +Enabling this flag implies enablement of checks from `libcxxhardeningfast`. Disabling this flag does not imply disablement of checks from `libcxxhardeningfast`. + #### `pacret` {#pacret} This flag adds the `-mbranch-protection=pac-ret` compiler option on aarch64-linux targets. This uses ARM v8.3's Pointer Authentication feature to sign function return pointers before adding them to the stack. The pointer's authenticity is then validated before returning to its destination. This dramatically increases the difficulty of ROP exploitation techniques. diff --git a/pkgs/build-support/bintools-wrapper/default.nix b/pkgs/build-support/bintools-wrapper/default.nix index a05ecc0cdca1..8f5613e9e7bb 100644 --- a/pkgs/build-support/bintools-wrapper/default.nix +++ b/pkgs/build-support/bintools-wrapper/default.nix @@ -49,6 +49,8 @@ "format" "fortify" "fortify3" + "libcxxhardeningextensive" + "libcxxhardeningfast" "pic" "relro" "stackclashprotection" diff --git a/pkgs/build-support/cc-wrapper/add-hardening.sh b/pkgs/build-support/cc-wrapper/add-hardening.sh index d254b139c996..aafde3874dad 100644 --- a/pkgs/build-support/cc-wrapper/add-hardening.sh +++ b/pkgs/build-support/cc-wrapper/add-hardening.sh @@ -25,6 +25,13 @@ if [[ -n "${hardeningEnableMap[strictflexarrays3]-}" ]]; then hardeningEnableMap["strictflexarrays1"]=1 fi +# libcxxhardeningextensive implies libcxxhardeningfast enablement - make explicit before +# we filter unsupported flags because unsupporting libcxxhardeningextensive +# doesn't mean we should unsupport libcxxhardeningfast too +if [[ -n "${hardeningEnableMap[libcxxhardeningextensive]-}" ]]; then + hardeningEnableMap["libcxxhardeningfast"]=1 +fi + # Remove unsupported flags. for flag in @hardening_unsupported_flags@; do @@ -37,6 +44,10 @@ for flag in @hardening_unsupported_flags@; do if [[ "$flag" = 'strictflexarrays1' ]] ; then unset -v "hardeningEnableMap['strictflexarrays3']" fi + # libcxxhardeningfast being unsupported implies libcxxhardeningextensive is unsupported + if [[ "$flag" = 'libcxxhardeningfast' ]] ; then + unset -v "hardeningEnableMap['libcxxhardeningextensive']" + fi done @@ -50,9 +61,14 @@ if [[ -n "${hardeningEnableMap[strictflexarrays3]-}" ]]; then unset -v "hardeningEnableMap['strictflexarrays1']" fi +# now make libcxxhardeningfast and libcxxhardeningextensive mutually exclusive +if [[ -n "${hardeningEnableMap[libcxxhardeningextensive]-}" ]]; then + unset -v "hardeningEnableMap['libcxxhardeningfast']" +fi + if (( "${NIX_DEBUG:-0}" >= 1 )); then - declare -a allHardeningFlags=(fortify fortify3 shadowstack stackprotector stackclashprotection nostrictaliasing pacret strictflexarrays1 strictflexarrays3 pic strictoverflow glibcxxassertions format trivialautovarinit zerocallusedregs) + declare -a allHardeningFlags=(fortify fortify3 shadowstack stackprotector stackclashprotection nostrictaliasing pacret strictflexarrays1 strictflexarrays3 pic strictoverflow libcxxhardeningfast libcxxhardeningextensive glibcxxassertions format trivialautovarinit zerocallusedregs) declare -A hardeningDisableMap=() # Determine which flags were effectively disabled so we can report below. @@ -115,6 +131,14 @@ for flag in "${!hardeningEnableMap[@]}"; do if (( "${NIX_DEBUG:-0}" >= 1 )); then echo HARDENING: enabling glibcxxassertions >&2; fi hardeningCFlagsBefore+=('-D_GLIBCXX_ASSERTIONS') ;; + libcxxhardeningfast) + if (( "${NIX_DEBUG:-0}" >= 1 )); then echo HARDENING: enabling libcxxhardeningfast >&2; fi + hardeningCFlagsBefore+=('-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST') + ;; + libcxxhardeningextensive) + if (( "${NIX_DEBUG:-0}" >= 1 )); then echo HARDENING: enabling libcxxhardeningextensive >&2; fi + hardeningCFlagsBefore+=('-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE') + ;; stackprotector) if (( "${NIX_DEBUG:-0}" >= 1 )); then echo HARDENING: enabling stackprotector >&2; fi hardeningCFlagsBefore+=('-fstack-protector-strong' '--param' 'ssp-buffer-size=4') diff --git a/pkgs/stdenv/generic/make-derivation.nix b/pkgs/stdenv/generic/make-derivation.nix index cae787432714..0af13170c261 100644 --- a/pkgs/stdenv/generic/make-derivation.nix +++ b/pkgs/stdenv/generic/make-derivation.nix @@ -155,6 +155,8 @@ let "relro" "stackprotector" "glibcxxassertions" + "libcxxhardeningfast" + "libcxxhardeningextensive" "stackclashprotection" "strictoverflow" "trivialautovarinit" @@ -427,6 +429,8 @@ let (concretizeFlagImplications "fortify" [ "fortify3" ]) # disabling strictflexarrays1 implies strictflexarrays3 should also be disabled (concretizeFlagImplications "strictflexarrays1" [ "strictflexarrays3" ]) + # disabling libcxxhardeningfast implies libcxxhardeningextensive should also be disabled + (concretizeFlagImplications "libcxxhardeningfast" [ "libcxxhardeningextensive" ]) ] ); enabledHardeningOptions = diff --git a/pkgs/test/cc-wrapper/hardening.nix b/pkgs/test/cc-wrapper/hardening.nix index d224c1fd460c..c5ce7f3836ae 100644 --- a/pkgs/test/cc-wrapper/hardening.nix +++ b/pkgs/test/cc-wrapper/hardening.nix @@ -31,8 +31,8 @@ let [ -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" + cp "$codePath" . + NIX_DEBUG=1 $CC -x ''${TEST_SOURCE_LANG:-c} "$(basename $codePath)" -O1 $TEST_EXTRA_FLAGS -o "$n" ''; f1exampleWithStdEnv = writeCBinWithStdenv ./fortify1-example.c; @@ -41,15 +41,60 @@ let flexArrF2ExampleWithStdEnv = writeCBinWithStdenv ./flex-arrays-fortify-example.c; + # we don't really have a reliable property for testing for + # libstdc++ we'll just have to check for the absence of libcxx checkGlibcxxassertionsWithStdEnv = - expectDefined: - writeCBinWithStdenv ( - writeText "main.cpp" '' - #if${if expectDefined then "n" else ""}def _GLIBCXX_ASSERTIONS - #error "Expected _GLIBCXX_ASSERTIONS to be ${if expectDefined then "" else "un"}defined" - #endif - int main() {} - '' + expectDefined: stdenv': derivationArgs: + brokenIf (stdenv.cc.libcxx != null) ( + writeCBinWithStdenv + (writeText "main.cpp" '' + #if${if expectDefined then "n" else ""}def _GLIBCXX_ASSERTIONS + #error "Expected _GLIBCXX_ASSERTIONS to be ${if expectDefined then "" else "un"}defined" + #endif + int main() {} + '') + stdenv' + ( + derivationArgs + // { + env = (derivationArgs.env or { }) // { + TEST_SOURCE_LANG = derivationArgs.env.TEST_SOURCE_LANG or "c++"; + }; + } + ) + ); + + checkLibcxxHardeningWithStdEnv = + expectValue: stdenv': env: + brokenIf (stdenv.cc.libcxx == null) ( + writeCBinWithStdenv + (writeText "main.cpp" ( + '' + #include + #ifndef _LIBCPP_HARDENING_MODE + #error "Expected _LIBCPP_HARDENING_MODE to be defined" + #endif + #ifndef ${expectValue} + #error "Expected ${expectValue} to be defined" + #endif + + #if _LIBCPP_HARDENING_MODE != ${expectValue} + #error "Expected _LIBCPP_HARDENING_MODE to equal ${expectValue}" + #endif + '' + + '' + int main() {} + '' + )) + stdenv' + ( + env + // { + env = (env.env or { }) // { + TEST_SOURCE_LANG = env.env.TEST_SOURCE_LANG or "c++"; + }; + } + ) ); # for when we need a slightly more complicated program @@ -691,6 +736,48 @@ nameDrvAfterAttrName ( hardeningDisable = [ "glibcxxassertions" ]; }; + lchFastExplicitDisabled = checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_NONE" stdenv { + hardeningDisable = [ "libcxxhardeningfast" ]; + }; + + lchExtensiveExplicitEnabled = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_EXTENSIVE" stdenv + { + hardeningEnable = [ "libcxxhardeningextensive" ]; + }; + + lchExtensiveExplicitDisabledDoesntDisableLchFast = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_FAST" stdenv + { + hardeningEnable = [ "libcxxhardeningfast" ]; + hardeningDisable = [ "libcxxhardeningextensive" ]; + }; + + lchFastExplicitDisabledDisablesLchExtensive = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_NONE" stdenv + { + hardeningEnable = [ "libcxxhardeningextensive" ]; + hardeningDisable = [ "libcxxhardeningfast" ]; + }; + + lchFastExtensiveExplicitEnabledResultsInLchExtensive = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_EXTENSIVE" stdenv + { + hardeningEnable = [ + "libcxxhardeningfast" + "libcxxhardeningextensive" + ]; + }; + + lchFastExtensiveExplicitDisabledDisablesBoth = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_NONE" stdenv + { + hardeningDisable = [ + "libcxxhardeningfast" + "libcxxhardeningextensive" + ]; + }; + # 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 @@ -897,6 +984,30 @@ nameDrvAfterAttrName ( hardeningEnable = [ "glibcxxassertions" ]; }; + lchFastStdenvUnsupp = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_NONE" + (stdenvUnsupport [ "libcxxhardeningfast" ]) + { + hardeningEnable = [ "libcxxhardeningfast" ]; + }; + + lchFastStdenvUnsuppUnsupportsLchExtensive = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_NONE" + (stdenvUnsupport [ "libcxxhardeningfast" ]) + { + hardeningEnable = [ "libcxxhardeningextensive" ]; + }; + + lchExtensiveStdenvUnsuppDoesntUnsupportLchFast = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_FAST" + (stdenvUnsupport [ "libcxxhardeningextensive" ]) + { + hardeningEnable = [ + "libcxxhardeningfast" + "libcxxhardeningextensive" + ]; + }; + fortify3EnabledEnvEnablesFortify1 = checkTestBin (f1exampleWithStdEnv stdenv { @@ -992,6 +1103,38 @@ nameDrvAfterAttrName ( expectFailure = true; }; + lchFastEnabledEnv = checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_FAST" stdenv { + hardeningDisable = [ + "libcxxhardeningfast" + "libcxxhardeningextensive" + ]; + postConfigure = '' + export NIX_HARDENING_ENABLE="libcxxhardeningfast" + ''; + }; + + lchExtensiveEnabledEnv = checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_EXTENSIVE" stdenv { + hardeningDisable = [ + "libcxxhardeningfast" + "libcxxhardeningextensive" + ]; + postConfigure = '' + export NIX_HARDENING_ENABLE="libcxxhardeningextensive" + ''; + }; + + lchFastExtensiveEnabledEnvResultsInLchExtensive = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_EXTENSIVE" stdenv + { + hardeningDisable = [ + "libcxxhardeningfast" + "libcxxhardeningextensive" + ]; + postConfigure = '' + export NIX_HARDENING_ENABLE="libcxxhardeningextensive libcxxhardeningfast" + ''; + }; + # NIX_HARDENING_ENABLE can't enable an unsupported feature stackProtectorUnsupportedEnabledEnv = checkTestBin @@ -1101,9 +1244,15 @@ nameDrvAfterAttrName ( hardeningDisable = [ "all" ]; }) true; - glibcxxassertionsExplicitDisabled = checkGlibcxxassertionsWithStdEnv false stdenv { + allExplicitDisabledGlibcxxAssertions = checkGlibcxxassertionsWithStdEnv false stdenv { hardeningDisable = [ "all" ]; }; + + allExplicitDisabledLibcxxHardening = + checkLibcxxHardeningWithStdEnv "_LIBCPP_HARDENING_MODE_NONE" stdenv + { + hardeningDisable = [ "all" ]; + }; } ) ) diff --git a/pkgs/top-level/variants.nix b/pkgs/top-level/variants.nix index ea288206dfbc..e3a12fcadcf5 100644 --- a/pkgs/top-level/variants.nix +++ b/pkgs/top-level/variants.nix @@ -165,6 +165,7 @@ self: super: { "nostrictaliasing" "pacret" "glibcxxassertions" + "libcxxhardeningfast" "trivialautovarinit" ] ) super'.stdenv; From 422942c3a6a0b526be6afd0b478ea9418c66b6e6 Mon Sep 17 00:00:00 2001 From: Robert Scott Date: Sun, 25 May 2025 19:26:35 +0100 Subject: [PATCH 2/2] ld64: disable libcxxhardeningfast hardening flag ld built with this fails to link glib's gio on x86_64 darwin --- pkgs/by-name/ld/ld64/package.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/by-name/ld/ld64/package.nix b/pkgs/by-name/ld/ld64/package.nix index c6e5c7cff969..f6a1dfc2cdf8 100644 --- a/pkgs/by-name/ld/ld64/package.nix +++ b/pkgs/by-name/ld/ld64/package.nix @@ -119,6 +119,9 @@ stdenv.mkDerivation (finalAttrs: { xar ]; + # ld built with this fails to link glib's gio on x86_64 darwin + hardeningDisable = [ "libcxxhardeningfast" ]; + dontUseCmakeConfigure = true; # CMake is only needed because it’s used by Meson to find LLVM. # Note for overrides: ld64 cannot be built as a debug build because of UB in its iteration implementations,