buildGoModule produces dynamically linked binaries without RUNPATH?

I’ve noticed that all go binaries in nixpkgs built with CGO_ENABLED=1 are not “pure”, meaning that the resulting binaries don’t set RUNPATH at all. Is this expected or am I missing something?

For example building any C++ binary produces the correct RUNPATH as expected:

$ readelf -d "$(nix build github:NixOS/nixpkgs/nixos-24.05#envoy --print-out-paths)/bin/envoy"
Dynamic section at offset 0x376d5c0 contains 33 entries:
  Tag        Type                         Name/Value
 ...
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [librt.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [ld-linux-x86-64.so.2]
 0x000000000000000c (INIT)               0x4bc768
 0x000000000000000d (FINI)               0x2a00324
 0x000000000000001a (FINI_ARRAY)         0x3607950
 0x000000000000001c (FINI_ARRAYSZ)       16 (bytes)
 0x0000000000000019 (INIT_ARRAY)         0x3607960
 0x000000000000001b (INIT_ARRAYSZ)       20456 (bytes)
 0x000000000000001d (RUNPATH)            Library runpath: [/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib]
...

However if I do the same for most packages built with buildGoModule with CGO_ENABLED=1 (which is the default behavior), I get something like the following:

$ readelf -d "$(nix build github:NixOS/nixpkgs/nixos-24.05#opentofu --print-out-paths)/bin/tofu"

Dynamic section at offset 0x4efca00 contains 20 entries:
  Tag        Type                         Name/Value
 0x0000000000000004 (HASH)               0x3c96cc0
 0x0000000000000006 (SYMTAB)             0x3c970a0
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000005 (STRTAB)             0x3c96dc0
 0x000000000000000a (STRSZ)              732 (bytes)
 0x0000000000000007 (RELA)               0x3c967a8
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x0000000000000003 (PLTGOT)             0x52fcb40
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000001 (NEEDED)             Shared library: [libresolv.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000006ffffffe (VERNEED)            0x3c96be0
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x3c96c40
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000002 (PLTRELSZ)           1056 (bytes)
 0x0000000000000017 (JMPREL)             0x3c967c0
 0x0000000000000000 (NULL)               0x0

As you can see RUNPATH is nowhere to be found and often causes when the binary is executed inside an FHS environment.

While adding these options to individual buildGoModules works as expected

  nativeBuildInputs = [ autoPatchelfHook ];
  buildInputs = [ glibc ];
  autoPatchelfFlags = ["--keep-libc"];

I wonder why this is not the default?

2 Likes

I think I spoke too soon when I said that all go binaries are missing RUNPATH, it appears this is only the case with binaries that don’t really care about cgo and only have pure golang dependencies.

However if the package or any of its dependencies has meaningful use of cgo, suddenly the RUNPATH gets populated as expected.

I am trying to figure out what controls that and whether there is a meaningful fix for pure go packages, any suggestions?