C: Difference between revisions

From NixOS Wiki
imported>Mic92
(fix markup)
imported>Mic92
(NIX_DEBUG=1 and hardening flags)
Line 1: Line 1:
This article gives practical advices when working C/C++ projects with Nix.
This article gives practical advices when working C/C++ projects with Nix.
It is written to be read from top to bottom.


== Differences between nixpkgs and the rest ==
== Differences between nixpkgs and the rest ==
Line 11: Line 10:
In nixpkgs in contrast this information is provided by environment variables that will
In nixpkgs in contrast this information is provided by environment variables that will
be set based on the build inputs that are given when building a package or
be set based on the build inputs that are given when building a package or
when loading a nix expression into a `<code>nix-shell</code>`.
when loading a nix expression into a <code>nix-shell</code>.
Therefore it is not sufficient to just install libraries with <code>nix-env</code> into the profile
Therefore it is not sufficient to just install libraries with <code>nix-env</code> into the profile
since the compiler will not look in those paths when compiling.
since the compiler will not look in those paths when compiling.
Line 39: Line 38:
and <code>NIX_LDFLAGS</code>
and <code>NIX_LDFLAGS</code>


<syntaxHighlight>
<syntaxHighlight lang=console>
$ cat > shell.nix <<EOF
$ cat > shell.nix <<EOF
with import <nixpkgs> {};
with import <nixpkgs> {};
Line 63: Line 62:
We can print the <code>RPATH</code> of executable using the <code>patchelf</code> command.
We can print the <code>RPATH</code> of executable using the <code>patchelf</code> command.


<syntaxHighlight>
<syntaxHighlight lang=console>
nix-shell -p hello --command 'patchelf --print-rpath $(which hello)'
$ nix-shell -p hello --command 'patchelf --print-rpath $(which hello)'
/nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib
/nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib
</syntaxHighlight>
</syntaxHighlight>
== Debugging the compiler wrapper ==
To inspect what how the shell wrapper processes the variables one can set the <code>NIX_DEBUG</code>
<syntaxHighlight  lang=console>
$ nix-shell -p hello --command 'NIX_DEBUG=1 $CC -v'
HARDENING: disabled flags: pie
HARDENING: Is active (not completely disabled with "all" flag)
HARDENING: enabling fortify
HARDENING: enabling stackprotector
HARDENING: enabling strictoverflow
HARDENING: enabling format
HARDENING: enabling pic
extra flags before to /nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc:
  ''
original flags to /nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc:
  -v
extra flags after to /nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc:
  ''
Using built-in specs.
COLLECT_GCC=/nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc
COLLECT_LTO_WRAPPER=/nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/libexec/gcc/x86_64-unknown-linux-gnu/7.4.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with:
Thread model: posix
gcc version 7.4.0 (GCC)
</syntaxHighlight>
== Hardening flags ==
To improve the security of application the wrapper also inject additional hardening compile flags into the application.
Under some circumstances this can make programs fails to build or function.
To disable all hardening options one can export the environment variable <code>hardeningDisable="all"</code>.
This also works for derivations like that:
<syntaxHighlight lang=nix>
with import <nixpkgs> {};
stdenv.mkDerivation {
  hardeningDisable = [ "all" ];
};
</syntaxHighlight>
It is also possible to only enable certain parts:
<syntaxHighlight lang=nix>
with import <nixpkgs> {};
stdenv.mkDerivation {
  hardeningDisable = [ "format" ];
};
</syntaxHighlight>
Further options are described in the [https://nixos.org/nixpkgs/manual/#sec-hardening-in-nixpkgs manual]

Revision as of 16:47, 6 February 2019

This article gives practical advices when working C/C++ projects with Nix.

Differences between nixpkgs and the rest

The way nixpkgs and its stdenv handles compiling and linking is very different from other linux distributions. Usually header files are put into well known paths i.e. /usr/include, where the compiler will look for them. Same is true when linking against libraries, which are put in a few places, where the build-time linker. Dynamically linked libraries will have a run-time linker (also known as ld.so) set as an interpreter. This linker reads /etc/ld.so.conf to figure out where to find libraries. In nixpkgs in contrast this information is provided by environment variables that will be set based on the build inputs that are given when building a package or when loading a nix expression into a nix-shell. Therefore it is not sufficient to just install libraries with nix-env into the profile since the compiler will not look in those paths when compiling.

The compiler wrapper

When inspecting the compiler or linker executable one will notice that those are not binaries but shell scripts:

$ nix-shell -p hello --command 'which $CC'
/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0/bin/gcc
$ nix-shell -p hello --command 'which $LD'
/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0/bin/ld
$ file /nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0/bin/gcc /nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0/bin/ld
/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0/bin/gcc: a /nix/store/vs6d2fjkl4kb3jb7rwibsd76k9v2n4xy-bash-4.4-p23/bin/bash script, ASCII text executable
/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0/bin/ld:  symbolic link to /nix/store/lwdkm354f3zzsvkf7pqmnc8w6r164b42-binutils-wrapper-2.30/bin/ld

These shell-scripts wrap around the actual compiler and add additional compiler flags depending on environment variables. In particular the wrapper around the C compiler, will also look for a NIX_CFLAGS_COMPILE variable and prepend the content to command line arguments passed to the underlying compiler. A different variable is called NIX_LDFLAGS, which will be provided as input to the build time linker. Nixpkgs use these variables to influence what kind of header files and libraries are visible to the build tools when running. For example when we add zlib to buildInputs of a stdenv.mkDerivation call and load the resulting file in a nix-shell, we can see the effect on both NIX_CFLAGS_COMPILE and NIX_LDFLAGS

$ cat > shell.nix <<EOF
with import <nixpkgs> {};
stdenv.mkDerivation {
  name = "myenv";
  buildInputs = [ zlib ];
};
EOF
[nix-shell:~] echo $NIX_CFLAGS_COMPILE
-isystem /nix/store/bjl5kk674rmdzzpmcsvmw73hvf35jwh8-zlib-1.2.11-dev/include -isystem /nix/store/bjl5kk674rmdzzpmcsvmw73hvf35jwh8-zlib-1.2.11-dev/include
[nix-shell:~] echo $NIX_LDFLAGS
-rpath /nix/store/d5dzr90q2wy2nlw0z7s0pgxkjfjv1jrj-myenv/lib64 -rpath /nix/store/d5dzr90q2wy2nlw0z7s0pgxkjfjv1jrj-myenv/lib -L/nix/store/5dphwv1xs46n0qbhynny2lbhmx4xh1fc-zlib-1.2.11/lib -L/nix/store/5dphwv1xs46n0qbhynny2lbhmx4xh1fc-zlib-1.2.11/lib

In $NIX_CFLAGS_COMPILE we see that the include search path is extended by appending new directories using the -isystem flag.

For $NIX_LDFLAGS see that the library link path is extended using the -L flag. We also notice that in addition to library paths the linker gets instructed to extend the RPATH of the program using the -rpath flag. This is needed when the executable is executed since the runtime linker will read the RPATH from the elf header to figure out where to find shared libraries. We can print the RPATH of executable using the patchelf command.

$ nix-shell -p hello --command 'patchelf --print-rpath $(which hello)'
/nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib

Debugging the compiler wrapper

To inspect what how the shell wrapper processes the variables one can set the NIX_DEBUG

$ nix-shell -p hello --command 'NIX_DEBUG=1 $CC -v'
HARDENING: disabled flags: pie
HARDENING: Is active (not completely disabled with "all" flag)
HARDENING: enabling fortify
HARDENING: enabling stackprotector
HARDENING: enabling strictoverflow
HARDENING: enabling format
HARDENING: enabling pic
extra flags before to /nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc:
  ''
original flags to /nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc:
  -v
extra flags after to /nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc:
  ''
Using built-in specs.
COLLECT_GCC=/nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/bin/gcc
COLLECT_LTO_WRAPPER=/nix/store/4ga86h16l157r7bas9hcwxgl9d3r32s6-gcc-7.4.0/libexec/gcc/x86_64-unknown-linux-gnu/7.4.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with:
Thread model: posix
gcc version 7.4.0 (GCC)

Hardening flags

To improve the security of application the wrapper also inject additional hardening compile flags into the application. Under some circumstances this can make programs fails to build or function. To disable all hardening options one can export the environment variable hardeningDisable="all". This also works for derivations like that:

with import <nixpkgs> {};
stdenv.mkDerivation {
  hardeningDisable = [ "all" ];
};

It is also possible to only enable certain parts:


with import <nixpkgs> {};
stdenv.mkDerivation {
  hardeningDisable = [ "format" ];
};

Further options are described in the manual