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

stdenv: Enable PIE by default #252310

Open
wants to merge 1 commit into
base: staging
Choose a base branch
from
Open

Conversation

chivay
Copy link
Member

@chivay chivay commented Aug 30, 2023

Description of changes

Almost exactly 7 years ago, a large security-related PR that introducted hardening was merged - #12895.

In 2016, enabling PIE was a bit controversial as it could break some packages and introduce overhead on 32 bit architectures.
Unfortunately, this decision lead us to the current state of nixpkgs where only a handful of packages (~20) enables PIE by default.

I think that we should revisit this discussion and enable PIE by default for all packages, leaving the option to opt-out of PIE if it causes crashes or broken packages. Other large distros have already done this, see:

https://wiki.ubuntu.com/Security/Features#pie
https://wiki.gentoo.org/wiki/Hardened/FAQ
https://wiki.archlinux.org/title/Arch_package_guidelines/Security#PIE

I'm aware that this PR won't be merged as-is since I don't have enough experience with stdenv, but it should be at least a humble beginning to a larger fix.

Kudos to @BonusPlay for pointing out this issue.

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 23.11 Release Notes (or backporting 23.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

@chivay
Copy link
Member Author

chivay commented Sep 4, 2023

After some digging, I've discovered the issue behind failed ofborg builds:

checking for gcc... gcc
checking whether the C compiler works... no
checking whether the C compiler works... no
configure: error: in `/build/bash-5.2':
configure: error: C compiler cannot create executables
See `config.log' for more details
configure: error: in `/build/bash-5.2':
configure: error: C compiler cannot create executables

It turns out that the bootstrap tools are missing a Scrt1.o file from glibc. After applying following patch and swapping the bootstrap files bash was built correctly.

$ nix build .#bash && checksec --file result/bin/bash
warning: Git tree '/home/chivay/repos/nixpkgs' is dirty
[*] '/home/chivay/repos/nixpkgs/result/bin/bash'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b'/nix/store/r01cc12xxvq8cn75ih6pq4spk6a1dyai-glibc-2.37-8/lib'
    FORTIFY:  Enabled

diff --git a/pkgs/stdenv/linux/make-bootstrap-tools.nix b/pkgs/stdenv/linux/make-bootstrap-tools.nix
index d6c4da0ab2be..03f218c5d143 100644
--- a/pkgs/stdenv/linux/make-bootstrap-tools.nix
+++ b/pkgs/stdenv/linux/make-bootstrap-tools.nix
@@ -79,6 +79,7 @@ in with pkgs; rec {
         cp -d ${libc.out}/lib/libnss*.so* $out/lib
         cp -d ${libc.out}/lib/libresolv*.so* $out/lib
         cp -d ${libc.out}/lib/crt?.o $out/lib
+        cp -d ${libc.out}/lib/Scrt*.o $out/lib

         # Hacky compat with our current unpack-bootstrap-tools.sh
         ln -s librt.so "$out"/lib/librt-dummy.so

@msm-code
Copy link
Contributor

msm-code commented Sep 7, 2023

Looking forward to this!

As a security engineer it's a bit weird to use NixOS as my main system, since it's clear that security is not the priority. Which is OK - plenty of security focused distros already, and we reproducibility fams may get Spectrum one day. But there are things one expects from "security focused" distros (like QubesOS), and there are things that constitute the status quo, the ground level. PIE is one of those things - it's a simple enhancement, supported by every major distribution already, and it's a major security improvement (TLDR: arbitrary write primitive is not enough for exploitation anymore, one needs to leak ASLR first so a separate vulnerability is usually needed).

I'm happy to help testing it if necessary. But can one of the maintainers weigh in on this enhancement first?

@chivay
Copy link
Member Author

chivay commented Sep 7, 2023

After some further experimentation I've discovered additional quirks - although bash is built correctly, some other packages like python3 end up without PIE (and for some unrelated reason also no stack canaries?)

$ checksec --file result/bin/python3
[*] '/home/chivay/repos/nixpkgs/result/bin/python3'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'/nix/store/cn63ysfl4jk78hgkbz50qg626m0qnpid-python3-3.10.12/lib:/nix/store/y6139wxl897ghqwy002i54jqpzci2ygy-libxcrypt-4.4.36/lib:/nix/store/r01cc12xxvq8cn75ih6pq4spk6a1dyai-glibc-2.37-8/lib:/nix/store/4784z5x4jqqr24i0shizglvzvslkq1f5-gcc-12.3.0-lib/lib'

Lack of PIE seems to be caused by inconsistent NIX_HARDENING_ENABLE defaults used in shell scripts.

grepping over this variable reveals:

$ rg NIX_HARDENING_ENABLE= pkgs/build-support/
pkgs/build-support/cc-wrapper/fortran-hook.sh
8:: ${NIX_HARDENING_ENABLE="stackprotector pic strictoverflow relro bindnow"}

pkgs/build-support/bintools-wrapper/setup-hook.sh
68:: ${NIX_HARDENING_ENABLE="fortify stackprotector pic strictoverflow format relro bindnow"}

pkgs/build-support/cc-wrapper/setup-hook.sh
114:: ${NIX_HARDENING_ENABLE="fortify fortify3 stackprotector pic strictoverflow format relro bindnow"}

Adding pie to cc-wrapper/setup-hook.sh fixes the issue. Although IMO this should be defined in a nix file as a single source of truth.

@RaitoBezarius
Copy link
Member

In those situations, I advise adding at least the two following group of reviewers:

(a) low-level stdenv contributors/developers, e.g. @trofi @Artturin (@amjoseph-nixpkgs does a lot of work in this area those days too and is already included, @Ericson2314 is an historical contributor and is still doing work in those areas AFAIK)

(b) security team, e.g. @risicle who has been driving a lot of hardening enablement (fortify source 3 comes to mind)

(c) bonus, staging people because this one will probably a massive breakage incoming, cc @vcunat @mweinelt @K900 who were recently involved in staging rounds.

In either case, this should definitely get its own Hydra jobset for pre-evaluation of the breakage, and then the classical discussion ensues on how to sort out this and what to do.

For record, I am in favor of this, I just think we need the human resources to opt-out/fix the hardening for broken packages.

@trofi
Copy link
Contributor

trofi commented Sep 7, 2023

You might want to include all runtime *.o files from glibc (that's what we do for musl bootstrap):

$ ls /nix/store/905gkx2q1pswixwmi1qfhfl6mik3f22l-glibc-2.37-8/lib/*.o |& unnix
/<<NIX>>/glibc-2.37-8/lib/crt1.o
/<<NIX>>/glibc-2.37-8/lib/crti.o
/<<NIX>>/glibc-2.37-8/lib/crtn.o
/<<NIX>>/glibc-2.37-8/lib/gcrt1.o
/<<NIX>>/glibc-2.37-8/lib/grcrt1.o
/<<NIX>>/glibc-2.37-8/lib/Mcrt1.o
/<<NIX>>/glibc-2.37-8/lib/rcrt1.o
/<<NIX>>/glibc-2.37-8/lib/Scrt1.o
  • Scrt - PIE
  • rcrt - static-PIE
  • gcrt - profile-genrated
  • Mcrt - unused nowadays.

Otherweise we would need to re-cut bootstrapFiles again when someone finds that we could use static-pie or PGO builds in more contexts.

@risicle
Copy link
Contributor

risicle commented Sep 7, 2023

I echo the general approval of this idea.

Firstly we will probably find it helpful if more/most packages have tests or we find a way of automatically testing binaries for basic-execution-without-crashing.

Secondly as you find problems it may be good to try narrowing them down into a test which you can add to tests.hardeningFlags (recently merged, obligatory plug for own PR fixing them up a bit #253186)

chivay added a commit to chivay/nixpkgs that referenced this pull request Sep 7, 2023
Include all runtime object files in output package, enabling different
kinds of build modes - non-PIE, PIE, static PIE and profile-generated.

Suggested by @trofi:
NixOS#252310 (comment)
happysalada pushed a commit to happysalada/nixpkgs that referenced this pull request Sep 9, 2023
Include all runtime object files in output package, enabling different
kinds of build modes - non-PIE, PIE, static PIE and profile-generated.

Suggested by @trofi:
NixOS#252310 (comment)
ivandimitrov8080 pushed a commit to ivandimitrov8080/nixpkgs that referenced this pull request Sep 10, 2023
Include all runtime object files in output package, enabling different
kinds of build modes - non-PIE, PIE, static PIE and profile-generated.

Suggested by @trofi:
NixOS#252310 (comment)
@risicle
Copy link
Contributor

risicle commented Oct 5, 2023

Also please see #259070 where I've refactored how defaultHardeningFlags is determined and would change the place you have to make this alteration to pkgs/build-support/bintools-wrapper/default.nix.

@LunNova
Copy link
Member

LunNova commented Oct 10, 2023

Lack of PIE seems to be caused by inconsistent NIX_HARDENING_ENABLE defaults used in shell scripts.

#206490 or a similar change should help with it. I never had the time to push that PR forward.

Copy link
Member

@LunNova LunNova left a comment

Choose a reason for hiding this comment

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

Without #206490 (or #259070) this PR will do nothing to many derivations, as enabledHardeningOptions is not used unless hardeningDisable != [] || hardeningEnable != [] || stdenv.hostPlatform.isMusl, and it falls back to defaults in various build-support scripts.

@risicle
Copy link
Contributor

risicle commented Oct 12, 2023

and/or #259070 which unifies the set of defaults with those used by the build-support scripts ;)

@LunNova
Copy link
Member

LunNova commented Apr 9, 2024

Result of nixpkgs-review pr 252310 run on x86_64-linux 1

67 packages built:
  • cosmic-applets
  • cosmic-applibrary
  • cosmic-bg
  • cosmic-comp
  • cosmic-comp.debug
  • cosmic-design-demo
  • cosmic-edit
  • cosmic-files
  • cosmic-greeter
  • cosmic-icons
  • cosmic-launcher
  • cosmic-notifications
  • cosmic-osd
  • cosmic-panel
  • cosmic-protocols
  • cosmic-randr
  • cosmic-screenshot
  • cosmic-session
  • cosmic-settings
  • cosmic-settings-daemon
  • cosmic-store
  • cosmic-term
  • cosmic-workspaces-epoch
  • cosmic-workspaces-epoch.debug
  • mesa
  • mesa-demos
  • mesa.debug
  • mesa.dev
  • mesa.drivers
  • mesa.driversdev
  • mesa.opencl
  • mesa.osmesa
  • mesa.spirv2dxil
  • mesa_glu
  • mesa_glu.dev
  • mesa_i686
  • mesa_i686.debug
  • mesa_i686.dev
  • mesa_i686.drivers
  • mesa_i686.driversdev
  • mesa_i686.osmesa
  • mesa_i686.spirv2dxil
  • wayland
  • wayland-logout
  • wayland-pipewire-idle-inhibit
  • wayland-protocols
  • wayland-proxy-virtwl
  • wayland.bin (wayland-scanner)
  • wayland-utils
  • wayland.debug
  • wayland.dev
  • wayland.doc
  • wayland.man
  • waylandpp
  • waylandpp.bin
  • waylandpp.dev
  • waylandpp.devman
  • waylandpp.doc
  • waylandpp.lib
  • xcb-imdkit
  • xcb-util-cursor
  • xcb-util-cursor-HEAD
  • xcb-util-cursor-HEAD.dev
  • xcb-util-cursor.dev
  • xcbuild
  • xcbuildHook
  • xcbutilxrm

@LeSuisse
Copy link
Contributor

LeSuisse commented Jun 3, 2024

Out of curiosity, I made a quick patch to add pie to pkgsExtraHardening and built up to some Go binaries with it. It looks like they are now picking the flags correctly and I did not encountered any issue.

Should we start by doing that to make it a bit easier to start playing with pie with limited risks?

diff --git a/pkgs/top-level/stage.nix b/pkgs/top-level/stage.nix
index 390aa36db03b..3f01c235ce9c 100644
--- a/pkgs/top-level/stage.nix
+++ b/pkgs/top-level/stage.nix
@@ -293,7 +293,12 @@ let
           stdenv = super'.withDefaultHardeningFlags (
             super'.stdenv.cc.defaultHardeningFlags ++ [
               "trivialautovarinit"
-            ]
+            ] ++ lib.optionals (with super'.stdenv;
+              # Always enable PIE except when using musl for:
+              #    - static aarch64, where compilation works, but produces segfaulting dynamically linked binaries.
+              #    - static armv7l, where compilation fails.
+              !(targetPlatform.libc == "musl" && targetPlatform.isAarch && targetPlatform.isStatic)
+            ) [ "pie" ]
           ) super'.stdenv;
         })
       ] ++ overlays;

@risicle
Copy link
Contributor

risicle commented Jun 4, 2024

I'd be generally in favour, but I think we should decide where we're making platform-specific decisions. If we disable the musl/static/aarch combination because it just plain doesn't work, we should probably knock it out using the hardeningUnsupportedFlags mechanism(s).

@LeSuisse
Copy link
Contributor

LeSuisse commented Jun 5, 2024

To check if the exclusion was still needed I added pie to pkgsExtraHardening. From what I can see the necessity of the exclusion was never revisited since its introduction a few years ago. I tried to build pkgsExtraHardening.pkgsStatic.hello on my aarch64 machine but I get the following error when building expand-response-params:

┃        > Running phase: unpackPhase
┃        > Running phase: patchPhase
┃        > Running phase: updateAutotoolsGnuConfigScriptsPhase
┃        > Running phase: configurePhase
┃        > no configure script, doing nothing
┃        > Running phase: buildPhase
┃        > /nix/store/a2n6w5g0cs0lcppxlnm49bmb6v1hsfhf-binutils-2.41/bin/ld: cannot find Scrt1.o: No such file or directory
┃        > collect2: error: ld returned 1 exit status

I would have expected this to have been solved by #253760 (and the follow up updates of the bootstrap files).

At least the comment does not seem to be completely accurate anymore 😅

@chivay
Copy link
Member Author

chivay commented Jun 10, 2024

I would have expected this to have been solved by #253760 (and the follow up updates of the bootstrap files).

Looks like latest aarch64 bootstrap tools update was in f05b5d4 (February 2023) whereas #253760 was merged in September 2023.

Bumping them should fix this (at least I hope so).

LeSuisse added a commit to LeSuisse/nixpkgs that referenced this pull request Jun 19, 2024
I needed to update the aarch64 gnu bootstrap in order to check if the
combinaison of musl / static / aarch64 was still broken when enabling
PIE (NixOS#252310).

In order to that I followed the same approach than in
ea67e45.

Files came from this Hydra build:

  https://hydra.nixos.org/build/262861772

…which used nixpkgs revision a985888
to instantiate:

  /nix/store/rqhq21qxwyhd633wgc53269jzp19y75m-stdenv-bootstrap-tools.drv

…and then built:

  /nix/store/z3sgi7wh8c5pnm3xm2s3kcbwqbw0d5dp-stdenv-bootstrap-tools

I downloaded these files from Hydra and prefetched them into the Nix
store with the following commands:

  STOREPATH=dk28gq49ckmgwpnx36709ff0hxnkmqpk-stdenv-bootstrap-tools
  OPTIONS="--option binary-caches https://cache.nixos.org --option trusted-public-keys cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
  nix --extra-experimental-features nix-command store prefetch-file \
    file://$(nix --extra-experimental-features nix-command store add-file --name bootstrap-tools.tar.xz  $(nix-store ${OPTIONS} -r /nix/store/${STOREPATH})/on-server/bootstrap-tools.tar.xz)
  nix --extra-experimental-features nix-command store prefetch-file --executable \
    file://$(nix --extra-experimental-features nix-command store add-path --name busybox $(nix-store ${OPTIONS} -r /nix/store/${STOREPATH})/on-server/busybox)

These commands produced the following output:

  copying path '/nix/store/dk28gq49ckmgwpnx36709ff0hxnkmqpk-stdenv-bootstrap-tools' from 'https://cache.nixos.org'...
  warning: you did not specify '--add-root'; the result might be removed by the garbage collector
  Downloaded 'file:///nix/store/rpsfzw2ixbdrd5nm4l00y9sjq8h9lg5k-bootstrap-tools.tar.xz' to '/nix/store/ax259maqg6nfdrxznv49as902j1fzhhr-rpsfzw2ixbdrd5nm4l00y9sjq8h9lg5k-bootstrap-tools.tar.xz' (hash 'sha256-Ag5/vwGqv8q9SwdJYmmcvtqcLJjVYNwhgcqQ0BHTTdg=').
  warning: you did not specify '--add-root'; the result might be removed by the garbage collector
  Downloaded 'file:///nix/store/7p38lg5jh4rvgq6aaxqs5sw0gy357z6s-busybox' to '/nix/store/y9bhy7662zdsacn6hr6zp81mzdyb7vzf-7p38lg5jh4rvgq6aaxqs5sw0gy357z6s-busybox' (hash 'sha256-8areJJa2A0xauz5XqwZTgkHSb3qSdi7rTiCI05SaS0Y=').

The sha256sums of all the on-server components:

  $ sha256sum /nix/store/${STOREPATH}/on-server/*
  020e7fbf01aabfcabd4b074962699cbeda9c2c98d560dc2181ca90d011d34dd8  /nix/store/dk28gq49ckmgwpnx36709ff0hxnkmqpk-stdenv-bootstrap-tools/on-server/bootstrap-tools.tar.xz
  8f4e1b1cafbfd3cc1517f477870f3b54170934f09b4c19ae2257acea557fbbca  /nix/store/dk28gq49ckmgwpnx36709ff0hxnkmqpk-stdenv-bootstrap-tools/on-server/busybox
@risicle
Copy link
Contributor

risicle commented Jun 20, 2024

My normal approach when adding new hardening flags is to add them to hardeningUnsupportedFlags in pkgs/stdenv/linux/bootstrap-tools/default.nix & pkgs/stdenv/linux/bootstrap-tools-musl/default.nix, then it doesn't matter what the bootstrap compiler supports.

@nyabinary
Copy link
Contributor

This seems like a pretty simple PR that got hung up on bikeshedding which is a shame. Any way to revive this?

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/where-did-you-get-stuck-in-the-nix-ecosystem-tell-me-your-story/31415/72

@chivay
Copy link
Member Author

chivay commented Sep 19, 2024

Out of curiosity, I made a quick patch to add pie to pkgsExtraHardening and built up to some Go binaries with it. It looks like they are now picking the flags correctly and I did not encountered any issue.

Should we start by doing that to make it a bit easier to start playing with pie with limited risks?

I'm attempting something similar, but on a NixOS configuration level (overriding nixpgks with pkgsExtraHardening)

diff --git a/pkgs/top-level/stage.nix b/pkgs/top-level/stage.nix
index 6ad193c8926d..320ec1f82abe 100644
--- a/pkgs/top-level/stage.nix
+++ b/pkgs/top-level/stage.nix
@@ -330,6 +330,7 @@ let
               "pacret"
               "stackclashprotection"
               "trivialautovarinit"
+              "pie"
             ]
           ) super'.stdenv;
           glibc = super'.glibc.override rec {

I'm already seeing some failures from Docker dependencies:

nix build .#pkgsExtraHardening.go-md2man
nix build .#pkgsExtraHardening.rootlesskit

fork/exec /build/go-build3987390772/b001/lowlevelmsgutil.test: no such file or directory
fork/exec /build/go-build1993151711/b001/lowlevelmsgutil.test: no such file or directory

They both fail during check phase so I'd presume that the binaries produced are non-static and trying to find the ld.so

EDIT: Well it took a while for the build to stop completely. The big ones failing at this stage of build are ghc and systemd.

systemd log:

/nix/store/h5rm9c560w30yimclf663fhini2qdy7p-clang-wrapper-18.1.8/bin/clang -std=gnu11 -Wno-compare-distinct-pointer-types -fno-stack-protector -O2 -target bpf -g -c -D__x86_64__ -I. -isystem /usr/include/x86_64-unknown-linux-gnu -idirafter /nix/store/y7vswvh4rfqca70gdihlca6d3f81fjs1-libbpf-1.4.5/include ../src/core/bpf/socket_bind/socket-bind.bpf.c -o src/core/bpf/socket_bind/socket-bind.bpf.unstripped.o
clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
clang: warning: argument unused during compilation: '-fstack-clash-protection' [-Wunused-command-line-argument]
clang: warning: argument unused during compilation: '--gcc-toolchain=/nix/store/j0n4f6qr4wjl5kb8rdsrksnh37i9pb1b-gcc-13.3.0' [-Wunused-command-line-argument]
error: option 'cf-protection=return' cannot be specified on this target

The error seems not related to PIE itself (although it causes some additional noise). Maybe systemd should be using unwrapped clang for the BPF programs?

ghc log:

�[31m===> Command failed with error code: 1�[0m
/nix/store/z7rwqsmgk6p2fshh07907il892ixv79r-binutils-2.42/bin/ld: /nix/store/j0n4f6qr4wjl5kb8rdsrksnh37i9pb1b-gcc-13.3.0/lib/gcc/x86_64-unknown-linux-gnu/13.3.0/crtbegin.o: relocation R_X86_64_32 against hidden symbol `__TMC_END__' can not be used when making a PIE object
/nix/store/z7rwqsmgk6p2fshh07907il892ixv79r-binutils-2.42/bin/ld: failed to set dynamic section sizes: bad value
�[31m===> Command failed with error code: 1�[0m
collect2: error: ld returned 1 exit status
/nix/store/z7rwqsmgk6p2fshh07907il892ixv79r-binutils-2.42/bin/ld: /nix/store/j0n4f6qr4wjl5kb8rdsrksnh37i9pb1b-gcc-13.3.0/lib/gcc/x86_64-unknown-linux-gnu/13.3.0/crtbegin.o: relocation R_X86_64_32 against hidden symbol `__TMC_END__' can not be used when making a PIE object
/nix/store/z7rwqsmgk6p2fshh07907il892ixv79r-binutils-2.42/bin/ld: failed to set dynamic section sizes: bad value
`gcc' failed in phase `Linker'. (Exit code: 1)
collect2: error: ld returned 1 exit status
`gcc' failed in phase `Linker'. (Exit code: 1)

This one seems much more cursed. No idea what's going on there.

EDIT: Ok, seems more obvious now. Ghc is trying to link against crtbegin.o instead of crtbeginS.o
#129247 - seems like GHC doesn't support PIE at all

@LunNova LunNova self-requested a review September 19, 2024 11:33
@chivay
Copy link
Member Author

chivay commented Sep 20, 2024

GHC seems to work after applying a5e76fb
However the correct fix would be to somehow make it build with PIE 🤔

@LunNova
Copy link
Member

LunNova commented Sep 21, 2024

Turning it off for GHC specifically with intent to fix it later so it can be turned on more widely sooner seems pretty reasonable.

@chivay
Copy link
Member Author

chivay commented Sep 24, 2024

systemd build failure fix attempt: #344292

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

Successfully merging this pull request may close these issues.