Skip to content

gcc: build with --enable-default-pie configure option#439314

Merged
emilazy merged 1 commit intoNixOS:stagingfrom
LunNova:lunnova/gcc-pie-flag
Sep 13, 2025
Merged

gcc: build with --enable-default-pie configure option#439314
emilazy merged 1 commit intoNixOS:stagingfrom
LunNova:lunnova/gcc-pie-flag

Conversation

@LunNova
Copy link
Member

@LunNova LunNova commented Sep 1, 2025

Let's configure GCC with --enable-default-pie.
We already build clang with the equivalent CLANG_DEFAULT_PIE_ON_LINUX set to ON (upstream default).

Distribution gcc --enable-default-pie enabled Reference
Alpine Linux 2016 Commit 5b7befa1b
Debian 2016 Bug #835148
Ubuntu 2016 SecurityTeam/PIE
Arch Linux 2017 FS#49791
Void Linux 2017 Commit 1c2290eec1
Gentoo 2017 News - 17.0 Profiles

Configuring GCC directly with --enable-default-pie is simple and matches what most mainstream distributions have been doing for years. It results in a compiler that opportunistically enables pie whenever it does not conflict with other flags.

Currently our clang stdenvs (pkgsLLVM, darwin default stdenvs) opportunistically apply pie, and our gcc stdenvs (linux default stdenv) do not. This is a bit weird since in some stdenvs adding the "pie" hardening flag is required to get ASLR supporting executables, and in others it's a mix of a no-op and a footgun that can cause builds to fail.

It's easier for gcc and clang to do the right thing by default based on whatever combination of flags have been passed than for our wrapper, so this PR should avoid issues with differing static/shared flags. gcc is extremely sensitive to the ordering of pie flags and static build flags. Prior attempts like #252310 got stuck, mostly due to this complexity.

The enable-default-pie approach doesn't break some unusual stdenvs that we had trouble with in previous attempts. pkgsExtraHardening.pkgsStatic.hello can be built on both aarch64-linux and x86_64-linux.

Comparison of checksec PIE Disabled processes running on my desktop before and after rebuilding on this PR branch:

$ sudo nix run nixpkgs#checksec -- procAll | grep "PIE Disabled" | wc -l
103
$ sudo nix run nixpkgs#checksec -- procAll | grep "PIE Disabled" | wc -l
4

This impacts the behavior of the existing "pie" hardening flag, so we need to decide what to do about that. The current state on this branch enables pie by default at the compiler level while leaving the hardening flag as a no-op, which is fine for some testing but not ready to merge.

If workable it might be nice to drop the hardening flag entirely, and handle treewide removal in a followup.
Packages that cannot build with pie can explicitly pass -no-pie when needed. Most packages already do due to the prevalence of compilers built with --enable-default-pie across other distros, however packages that have not been maintained in the past decade may need it passed via NIX_CFLAGS_COMPILE.
I have ran into zero packages that needed this treatment in a full rebuild of my desktop on this PR, but I'd be surprised if there are none.

An alternative to dropping the "pie" flag is to teach the wrappers to pass -no-pie when the flag is disabled for stdenvs using compilers that have pie by default, along with adding "pie" to the default hardening flag list for those stdenvs.

Things done

  • Built on platform:
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • Tested, as applicable:
  • Ran nixpkgs-review on this PR. See nixpkgs-review usage.
  • Tested basic functionality of all binary files, usually in ./result/bin/.
  • Nixpkgs Release Notes
    • Package update: when the change is major or breaking.
  • NixOS Release Notes
    • Module addition: when adding a new NixOS module.
    • Module update: when the change is significant.
  • Fits CONTRIBUTING.md, pkgs/README.md, maintainers/README.md and other READMEs.

Annoyances encountered: tailscale build failing due to kernel version #438765, dbus-broker failure due to kernel version #439331


Add a 👍 reaction to pull requests you find important.

@nixpkgs-ci nixpkgs-ci bot added 10.rebuild-linux: 501+ This PR causes many rebuilds on Linux and should normally target the staging branches. 10.rebuild-darwin: 501+ This PR causes many rebuilds on Darwin and should normally target the staging branches. 10.rebuild-linux-stdenv This PR causes stdenv to rebuild on Linux and must target a staging branch. 10.rebuild-darwin: 5001+ This PR causes many rebuilds on Darwin and must target the staging branches. 10.rebuild-linux: 5001+ This PR causes many rebuilds on Linux and must target the staging branches. labels Sep 1, 2025
@LunNova LunNova force-pushed the lunnova/gcc-pie-flag branch from 2c0cbaa to 7640607 Compare September 2, 2025 02:42
@LunNova LunNova requested review from emilazy and risicle September 2, 2025 02:44
@nixpkgs-ci nixpkgs-ci bot added the 6.topic: stdenv Standard environment label Sep 2, 2025
Copy link
Member

@emilazy emilazy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is the correct path forward for handling this migration, and am looking forward to Nixpkgs not being embarrassingly behind on this default.

If workable it might be nice to drop the hardening flag entirely, and handle treewide removal in a followup. Packages that cannot build with pie can explicitly pass -no-pie when needed. Most packages already do due to the prevalence of compilers built with --enable-default-pie across other distros, however packages that have not been maintained in the past decade may need it passed via NIX_CFLAGS_COMPILE. I have ran into zero packages that needed this treatment in a full rebuild of my desktop on this PR, but I'd be surprised if there are none.

We have a few instances of hardeningDisable = [ "pie" ]; in the tree. Have you tested those? If they’re no longer necessary, it would be good to remove them beforehand. If they are necessary, then we either need to migrate them to another scheme or make the hardeningDisable set the appropriate flag to disable it.

I personally support doing things outside of wrappers wherever possible, with a view towards reducing or eliminating their role entirely over time, so I would not mind the hardeningDisable flag going away. However, I’ll defer to @risicle on this; there is a reasonable argument for keeping the existing, consistent interface, I think, if it’s not too troublesome to adapt it to pass an explicit flag to disable PIE.

@risicle
Copy link
Contributor

risicle commented Sep 2, 2025

It's an interesting question we've not really encountered before. I guess the whole "hardening flags" mechanism was built around just that - hardening "flags" that had to be added to the invocation. It's not really a "flag" anymore if it's built-in. It would actually take a bit of refactoring of add-hardening.sh to be able to handle an option that adds flags when it's disabled.

I don't think I have a strong opinion, my only concern being how this might affect more obscure architectures/platforms/stdenvs.

@LunNova
Copy link
Member Author

LunNova commented Sep 2, 2025

We have a few instances of hardeningDisable = [ "pie" ]; in the tree. Have you tested those? If they’re no longer necessary, it would be good to remove them beforehand. If they are necessary, then we either need to migrate them to another scheme or make the hardeningDisable set the appropriate flag to disable it.

The packages I could identify that set hardeningDisable = [ "pie" ] build on this branch, despite that being a no-op. End result bin comparison:

https://gist.github.com/LunNova/00645ba8a7890a5b59b7519611847adc

Skipped codd because it doesn't build in master, didn't build all the different kernel packages to save time.

@LunNova
Copy link
Member Author

LunNova commented Sep 3, 2025

It might be easiest to land this change without changing anything about the existing "pie" flag, since our darwin and pkgsLLVM stdenvs already opportunistically turn on pie and this PR merely aligns GCC toolchains.

Does that make sense? It'd leave followup work of deprecating the flag (probably accepting it and warning that it's a no-op for 1 release cycle) and treewide removal of its usage in hardeningDisable calls.

@LunNova
Copy link
Member Author

LunNova commented Sep 3, 2025

my only concern being how this might affect more obscure architectures/platforms/stdenvs.

@risicle do you have an idea of a good set of weird stdenvs to test?

@emilazy
Copy link
Member

emilazy commented Sep 8, 2025

So is the thinking that the existing instances of hardeningDisable = [ "pie" ]; were required when we were injecting a flag, because -no-pie wouldn’t work, but that doing it this way makes them all work because they’re already trying to pass -no-pie?

@emilazy
Copy link
Member

emilazy commented Sep 10, 2025

FWIW, this seems to bootstrap and compile things fine on macOS.

@LunNova
Copy link
Member Author

LunNova commented Sep 10, 2025

So is the thinking that the existing instances of hardeningDisable = [ "pie" ]; were required when we were injecting a flag, because -no-pie wouldn’t work, but that doing it this way makes them all work because they’re already trying to pass -no-pie?

Sometimes the package already passes -no-pie.
Sometimes the package passes flags that are incompatible with pie such as -mcmodel=large. This causes a failure with hardeningEnable = [ "pie" ]; cc1plus: sorry, unimplemented: code model ‘large’ with ‘-fpic’
The --enable-default-pie flag doesn't enable pie if it would immediately cause an error, so does not share this failure mode.

valgrind is an example of a package that will fail at runtime if pie is enabled. It has hardeningDisable = [ "pie" "stackprotector" ];. valgrind's configure script sets -no-pie when needed, so should not break with this PR applied.

The worst case scenario for this approach is a package that passes flags that are compatible with PIE, doesn't pass -no-pie, but produces binaries that will fail at runtime if PIE is enabled.

@LunNova
Copy link
Member Author

LunNova commented Sep 10, 2025

No example of that last type because I can't find one, I've been running my desktop and build boxes on this for a week now and have yet to run into runtime issues.

@emilazy
Copy link
Member

emilazy commented Sep 10, 2025

Seems like the GHC issue is relating to conflicts with it passing -no-pie too. I think you are right that we are sufficiently embarrassingly behind the curve here that everyone has already figured it out, and that if we do need to pass -no-pie it will be easy and probably won’t be for anything important.

Not sure if you want to keep this drafted for now, but I would be comfortable moving forward with this. I’m okay splitting out the deprecation of the hardening flag, but I think we should do it soon after we merge this, since a non‐functioning hardening flag is pretty confusing, even if it’s harmless in this case.

I’ve asked @alyssais to check if there are any Musl‐relevant considerations here, since there are a few packages that set lib.optional stdenv.hostPlatform.isStatic "pie" or lib.optional stdenv.hostPlatform.isMusl "pie". Other than that I think this should be ready to go.

@emilazy emilazy requested a review from alyssais September 10, 2025 16:07
@LunNova
Copy link
Member Author

LunNova commented Sep 10, 2025

a non‐functioning hardening flag is pretty confusing, even if it’s harmless in this case.

Does it make sense to keep the hardening flag as a way to turn a package that would build but silently disable PIE into a build failure? That might be a remaining use for it but I don't know if it's a good use. 😅

@LunNova LunNova added the 1.severity: security Issues which raise a security issue, or PRs that fix one label Sep 10, 2025
@emilazy
Copy link
Member

emilazy commented Sep 10, 2025

Probably not. That seems more like the job of a fixupPhase hook; possibly even one we could enable by default (with a dontCheckExecutablePie opt‐out or such).

Copy link
Member Author

@LunNova LunNova Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging this on for static targets doesn't get automatic static-pie. The stdenv still works. Unsure if that means we should default on without hasSharedLibraries.

static-pie would still need a hardening flag and wrapper support. The "pie" hardening flag doesn't try to support static-pie currently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We apparently already default the hardening flag on for Musl (except on ARM, for some reason…?), so I guess our static builds are already using PIE, and that’s also probably why some things disable it conditional on Musl.

I think Alpine has a patch for default static PIE. It might just be a matter of adjusting spec files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that applies for pkgsMusl but not pkgsStatic. Testing with checksec on nixos-unstable:

$ nix run nixpkgs#checksec file (nix build github:nixos/nixpkgs/nixos-unstable#pkgsMusl.hello --print-out-paths -
-no-link)/bin/hello
RELRO           Stack Canary      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY    Fortified   Fortifiable      Name                            
Full RELRO      Canary Found      NX enabled    PIE Enabled     No RPATH   RUNPATH      213 symbols     No         0           0                /nix/store/zdfx8s4vyv0hghfi5bb3ib2rpa74xpr3-hello-2.12.2/bin/hello
$ nix run nixpkgs#checksec file (nix build github:nixos/nixpkgs/nixos-unstable#pkgsStatic.hello --print-out-paths
 --no-link)/bin/hello
RELRO           Stack Canary      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY    Fortified   Fortifiable      Name                            
Partial RELRO   Canary Found      NX enabled    PIE Disabled    No RPATH   No RUNPATH   462 symbols     N/A         0           0                /nix/store/lwnanc42zfbpi2airnmg8h03a1v18whj-hello-static-x86_64-unknown-linux-musl-2.12.2/bin/hello

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So then should we set this to unconditionally default on?

@LunNova LunNova marked this pull request as ready for review September 10, 2025 17:56
@vcunat
Copy link
Member

vcunat commented Oct 3, 2025

stop-gap suggestion from chat:

--- a/pkgs/stdenv/linux/make-bootstrap-tools.nix
+++ b/pkgs/stdenv/linux/make-bootstrap-tools.nix
@@ -50,6 +50,7 @@
   bootGCC = pkgs.gcc.cc.override {
     enableLTO = false;
     isl = null;
+    enableDefaultPie = false;
   };
 
   bootBinutils = pkgs.binutils.bintools.override {

LordGrimmauld added a commit to LordGrimmauld/nixpkgs that referenced this pull request Oct 3, 2025
@trofi
Copy link
Contributor

trofi commented Oct 6, 2025

Bisect says this change broke ltrace testsuite in master:

$ nix build --no-link -f. -L ltrace
...
ltrace> Running /build/ltrace-0.7.91/testsuite/ltrace.minor/print-instruction-pointer.exp ...
ltrace> FAIL: 1161.*printf in /build/ltrace-0.7.91/testsuite/ltrace.minor/print-instruction-pointer.ltrace for 0 times, should be 1
ltrace> FAIL: 1181.*printf in /build/ltrace-0.7.91/testsuite/ltrace.minor/print-instruction-pointer.ltrace for 0 times, should be 1
ltrace>                 ===  Summary ===
ltrace>
ltrace> # of expected passes            242
ltrace> # of unexpected failures        2
ltrace> # of unsupported tests          1
ltrace> make[4]: *** [Makefile:498: check-DEJAGNU] Error 1

@LunNova
Copy link
Member Author

LunNova commented Oct 6, 2025

@siraben
Copy link
Member

siraben commented Oct 9, 2025

This breaks cross-compilation with some exotic platforms, such as mmixware.

Bisect log
git bisect start
# status: waiting for both good and bad commits
# bad: [ef8cf06ecce404070508c999be8fb9c86fa62b49] libtins: fix cmake 4 compatibility (#449694)
git bisect bad ef8cf06ecce404070508c999be8fb9c86fa62b49
# status: waiting for good commit(s), bad commit known
# good: [8d8db2ff25244cc0a79687561c452ff6be914d2e] Merge master into staging-next
git bisect good 8d8db2ff25244cc0a79687561c452ff6be914d2e
# good: [fc96a54e88c60f095f696a96858fefcdf36fe8ca] Merge master into staging-next
git bisect good fc96a54e88c60f095f696a96858fefcdf36fe8ca
# good: [b4f1f9e3f2d205cc34a2d69dbbb926fd30f52ff4] tfswitch: 1.5.1 -> 1.6.0 (#443600)
git bisect good b4f1f9e3f2d205cc34a2d69dbbb926fd30f52ff4
# good: [2fd51536cc99ade187e605ad79d4fa5d2fd3a0e8] linuxPackages.ecapture: 1.4.1 -> 1.4.2 (#446650)
git bisect good 2fd51536cc99ade187e605ad79d4fa5d2fd3a0e8
# good: [efc39c7711609be26eeea6e1c0bed2b7f3e71cc3] bitcoin-knots: fix bitcoin-qt build (#448831)
git bisect good efc39c7711609be26eeea6e1c0bed2b7f3e71cc3
# bad: [3568cae541843fd38c9c59b5f4f5138cc863e13e] chatterino2: unpin boost (#446410)
git bisect bad 3568cae541843fd38c9c59b5f4f5138cc863e13e
# bad: [3e03b8e865900a97b550bdb55a42a9d4a448c311] libspatialaudio: backport patch for CMake 4
git bisect bad 3e03b8e865900a97b550bdb55a42a9d4a448c311
# bad: [437f622838a43c070b61b18aa695929fee84d2db] tailscale: 1.86.5 -> 1.88.1
git bisect bad 437f622838a43c070b61b18aa695929fee84d2db
# good: [13a2755e051843ec048aa018259ebc6b087d8771] Merge staging-next into staging
git bisect good 13a2755e051843ec048aa018259ebc6b087d8771
# good: [37b26cf940d7d38c1cbc30b71f297edf32c19de8] pcre2: 10.44 -> 10.46 (#438376)
git bisect good 37b26cf940d7d38c1cbc30b71f297edf32c19de8
# good: [d0e71d6bb28f13f18a116b94c96fdb16f3aa6a7e] Merge staging-next into staging
git bisect good d0e71d6bb28f13f18a116b94c96fdb16f3aa6a7e
# bad: [0b9ac6896dc00a5f0e4e8d35ade1fc11032c2ebd] mariadb-connector-c: fix build against gcc15  (#442634)
git bisect bad 0b9ac6896dc00a5f0e4e8d35ade1fc11032c2ebd
# bad: [5e556339838e4221816d734d30c9459e764684c7] Merge remote-tracking branch 'origin/staging-next' into staging
git bisect bad 5e556339838e4221816d734d30c9459e764684c7
# good: [e2c4055c7c1ce3821d7f307b94311b7128d4a7aa] libapparmor: 4.1.1 -> 4.1.2 (#442481)
git bisect good e2c4055c7c1ce3821d7f307b94311b7128d4a7aa
# good: [4a9d5572097e0084061aecd0520c6973081fed34] llvmPackages_{20,21,git}.compiler-rt: no fmv on aarch64 without libc (#409265)
git bisect good 4a9d5572097e0084061aecd0520c6973081fed34
# bad: [411faf46e2c88c284c8ecaee890d61eab4c28f83] gcc: build with --enable-default-pie configure option (#439314)
git bisect bad 411faf46e2c88c284c8ecaee890d61eab4c28f83
# bad: [ccc56d1a79ff2a0f528cecf5e36eb76beaacc8c0] gcc: build with --enable-default-pie configure option
git bisect bad ccc56d1a79ff2a0f528cecf5e36eb76beaacc8c0
# first bad commit: [ccc56d1a79ff2a0f528cecf5e36eb76beaacc8c0] gcc: build with --enable-default-pie configure option
Running phase: unpackPhase
@nix { "action": "setPhase", "phase": "unpackPhase" }
unpacking source archive /nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz
source root is hello-2.10
setting SOURCE_DATE_EPOCH to timestamp 1416139241 of file "hello-2.10/ChangeLog"
Running phase: patchPhase
@nix { "action": "setPhase", "phase": "patchPhase" }
Running phase: updateAutotoolsGnuConfigScriptsPhase
@nix { "action": "setPhase", "phase": "updateAutotoolsGnuConfigScriptsPhase" }
Updating Autotools / GNU config script to a newer upstream version: ./build-aux/config.sub
Updating Autotools / GNU config script to a newer upstream version: ./build-aux/config.guess
Running phase: updateAutotoolsGnuConfigScriptsPhase
@nix { "action": "setPhase", "phase": "updateAutotoolsGnuConfigScriptsPhase" }
Updating Autotools / GNU config script to a newer upstream version: ./build-aux/config.sub
Updating Autotools / GNU config script to a newer upstream version: ./build-aux/config.guess
Running phase: configurePhase
@nix { "action": "setPhase", "phase": "configurePhase" }
patching script interpreter paths in ./configure
./configure: interpreter directive changed from "#! /bin/sh" to "/nix/store/imsax9b5vcfk8a54r2a5bxdzgyaqzarm-bash-5.3p3/bin/sh"
configure flags: --disable-dependency-tracking --prefix=/nix/store/wcli59d38i6vj1hin44xb51rp0q732lp-hello-mmix-unknown-mmixware-2.10 --build=x86_64-unknown-linux-gnu --host=mmix-unknown-mmixware
checking for a BSD-compatible install... /nix/store/k658hb3gj596h63r45jlr7i9spi4pq64-coreutils-9.7/bin/install -c
checking whether build environment is sane... yes
checking for mmix-unknown-mmixware-strip... mmix-unknown-mmixware-strip
checking for a thread-safe mkdir -p... /nix/store/k658hb3gj596h63r45jlr7i9spi4pq64-coreutils-9.7/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for mmix-unknown-mmixware-gcc... mmix-unknown-mmixware-gcc
checking whether the C compiler works... no
configure: error: in `/build/hello-2.10':
configure: error: C compiler cannot create executables
See `config.log' for more details

@LunNova
Copy link
Member Author

LunNova commented Oct 9, 2025

Probably we need to swap enableDefaultPie ? true, to be conditional on some aspect of stdenv.targetPlatform to resolve this.

Do you have any examples of exotic targets that need adjusted other than mmixware?

@siraben
Copy link
Member

siraben commented Oct 9, 2025

Continuing discussion in issue

LunNova added a commit to LunNova/nixpkgs that referenced this pull request Oct 9, 2025
work-jdannenberg pushed a commit to work-jdannenberg/nixpkgs that referenced this pull request Oct 10, 2025
LunNova added a commit to LunNova/nixpkgs that referenced this pull request Oct 17, 2025
In discussion on NixOS#439314 conditionalizing this seemed unnecessary
as pkgsStatic's gcc kept working. However, some less typical
!hasSharedLibraries stdenvs for embedded platforms broke.

It seems simplest to make this conditional on hasSharedLibraries for
now. We may need to revisit this if we want to enable static-pie by
default with gcc spec file changes.
@trofi
Copy link
Contributor

trofi commented Oct 31, 2025

Broke whisper. Proposed a possible workaround:

@corngood
Copy link
Contributor

This broke cross-compiled gcc for x86_64-cygwin, which unfortunately is not reproducible on master.

host=linux/target=cygwin works, but host=cygwin/target=cygwin is broken.

PIC is disabled when building libiberty, but enabled when building c++tools. This makes the latter fail to link:

x86_64-pc-cygwin-g++ -static-libstdc++ -static-libgcc -Wl,--stack,12582912 -fPIE  -o g++-mapper-server.exe server.o resolver.o ../libcody/libcody.a ../libiberty/pic/libiberty.a
/nix/store/88pzg489jr5x8pzzz0y7dp3pljpmvmay-x86_64-pc-cygwin-binutils-2.44/bin/x86_64-pc-cygwin-ld: cannot find ../libiberty/pic/libiberty.a: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:102: g++-mapper-server.exe] Error 1

This may be an upstream bug, but I thought I'd make a note of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.severity: security Issues which raise a security issue, or PRs that fix one 6.topic: stdenv Standard environment 10.rebuild-darwin: 501+ This PR causes many rebuilds on Darwin and should normally target the staging branches. 10.rebuild-darwin: 5001+ This PR causes many rebuilds on Darwin and must target the staging branches. 10.rebuild-linux: 501+ This PR causes many rebuilds on Linux and should normally target the staging branches. 10.rebuild-linux: 5001+ This PR causes many rebuilds on Linux and must target the staging branches. 10.rebuild-linux-stdenv This PR causes stdenv to rebuild on Linux and must target a staging branch. 12.approvals: 1 This PR was reviewed and approved by one person.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

9 participants