How do I run a static pie executable?

VirtualHere is closed-source software. It it distributed as two components: a server (which exposes USB devices) and a client (which connects to a server and gains the ability to use USB devices).

The server is a static executable and thus executes just fine on NixOS:

$ file vhusbdx86_64
vhusbdx86_64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header
$ sudo ./vhusbdx86_64
VirtualHere USB Server is running...press CTRL-C to stop

The client is also statically linked, but it is a “pie executable” and does not work:

$ file ./vhuit64
./vhuit64: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked, no section header
$ ./vhuit64
Could not start dynamically linked executable: ./vhuit64
NixOS cannot run dynamically linked executables intended for generic
linux environments out of the box. For more information, see:
https://nix.dev/permalink/stub-ld

How do I fix the client to work on NixOS?

In general, I understand why pre-compiled dynamic executables do not run on NixOS, and I am familiar with usage of patchelf. However, this is the first time that I encounter a strange static-yet-not-static executable and I can’t find anything regarding “PIE”, “PIC”, “position” or “independent” in patchelf’s manpage.

1 Like

Well this is certainly strange. The only helpful information I could find on static PIE (ELF) executables was this: Why are relocations present in the ELF of a statically linked executable? | elf-by-example

What is the output of ldd vhuit64?

$ ldd vhuit64
        not a dynamic executable

Well that was disappointing. Just to be sure, you are running this on an x86_64 machine, running x86_64 NixOS correct?

What do you get if you run patchelf --print-interpreter and patchelf --print-needed on the executable?

Yes, the architecture matches.

$ uname -a
Linux kuuro 6.6.33 #1-NixOS SMP PREEMPT_DYNAMIC Wed Jun 12 09:13:03 UTC 2024 x86_64 GNU/Linux

Sadly, no luck with patchelf.

$ patchelf --print-interpreter vhuit64
patchelf: no section headers. The input file is probably a statically linked, self-decompressing binary
$ patchelf --print-needed vhuit64
patchelf: no section headers. The input file is probably a statically linked, self-decompressing binary

OK, let’s try readelf. --all will output tens of kilobytes worth of information, so instead run

$ readelf --file-header --program-headers --sections --dynamic --notes --version-info --arch-specific --unwind --section-groups --histogram vhuit64

And for comparison, also run it on the other statically-linked executable that does run.

I put the full outputs for both in the gist, vhuit64_info is for the problematic one. Analysis of VirtualHere executables · GitHub

Diff (-working +failing):

diff --git a/vhusbdx86_64_info b/vhuit64_info
index 4253bd6..b3bc201 100644
--- a/vhusbdx86_64_info
+++ b/vhuit64_info
@@ -5,10 +5,10 @@ ELF Header:
   Version:                           1 (current)
   OS/ABI:                            UNIX - System V
   ABI Version:                       0
-  Type:                              EXEC (Executable file)
+  Type:                              DYN (Shared object file)
   Machine:                           Advanced Micro Devices X86-64
   Version:                           0x1
-  Entry point address:               0x7c8e20
+  Entry point address:               0x23050f8
   Start of program headers:          64 (bytes into file)
   Start of section headers:          0 (bytes into file)
   Flags:                             0x0
@@ -26,10 +26,10 @@ There are no section groups in this file.
 Program Headers:
   Type           Offset             VirtAddr           PhysAddr
                  FileSiz            MemSiz              Flags  Align
-  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
-                 0x0000000000001000 0x0000000000359e78  RW     0x8000
-  LOAD           0x0000000000000000 0x0000000000760000 0x0000000000760000
-                 0x000000000006a21a 0x000000000006a21a  R E    0x8000
+  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
+                 0x0000000000001000 0x0000000001df6490  RW     0x1000
+  LOAD           0x0000000000000000 0x0000000001df7000 0x0000000001df7000
+                 0x000000000050f506 0x000000000050f506  R E    0x1000
   GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                  0x0000000000000000 0x0000000000000000  RW     0x10

Evidently, it is not merely a static executable but attempts to invoke the dynamic loader.

If this is a GUI application, then it is clear why because rendering a GUI in a modern desktop cannot be done in a static binary to my knowledge. It’s likely dlopening at least a graphics driver such as mesa.

You could strace it to figure out which shared library it’s trying to load but you’ll need to patch it or run it inside of an FHS environment either way.


PIE is a red herring; it just means that the code is independent from its location in memory. It has nothing to do with whether the binary makes use of dynamic loading or not. (It does facilitate dynamic loading of a binary however.)

2 Likes

Thanks, you put me on the right track. Turns out, both executables come packed with UPX:

$ strings vhusbdx86_64 | grep UPX
UPX!
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 4.23 Copyright (C) 1996-2024 the UPX Team. All Rights Reserved. $
UPX!
UPX!

After unpacking them with upx -d, the difference is obvious: the underlying executable is static in one case, but dynamic in the other:

$ file vh*
vhuit64:      ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8821360dbdbe75548f2deebd130ff6936323b968, for GNU/Linux 3.2.0, stripped
vhusbdx86_64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header
$ ldd vhuit64
        linux-vdso.so.1 (0x00007ffe749f0000)
        libGL.so.1 => not found
        libEGL.so.1 => not found
        libwayland-egl.so.1 => not found
        libwayland-client.so.0 => not found
        libX11.so.6 => not found
        libSM.so.6 => not found
        libxkbcommon.so.0 => not found
        libgtk-3.so.0 => not found
        libgdk-3.so.0 => not found
        libpangocairo-1.0.so.0 => not found
        libcairo.so.2 => not found
        libgdk_pixbuf-2.0.so.0 => not found
        libgio-2.0.so.0 => not found
        libpangoft2-1.0.so.0 => not found
        libpango-1.0.so.0 => not found
        libgobject-2.0.so.0 => not found
        libglib-2.0.so.0 => not found
        libfontconfig.so.1 => not found
        libdl.so.2 => /nix/store/dbcw19dshdwnxdv5q2g6wldj6syyvq7l-glibc-2.39-52/lib/libdl.so.2 (0x00007fa781edb000)
        libz.so.1 => not found
        libstdc++.so.6 => not found
        libm.so.6 => /nix/store/dbcw19dshdwnxdv5q2g6wldj6syyvq7l-glibc-2.39-52/lib/libm.so.6 (0x00007fa77ff1d000)
        libgcc_s.so.1 => /nix/store/nda0h04bakn2damsd06vkscwi5ds4qjd-xgcc-13.2.0-libgcc/lib/libgcc_s.so.1 (0x00007fa781eb4000)
        libpthread.so.0 => /nix/store/dbcw19dshdwnxdv5q2g6wldj6syyvq7l-glibc-2.39-52/lib/libpthread.so.0 (0x00007fa781eaf000)
        libc.so.6 => /nix/store/dbcw19dshdwnxdv5q2g6wldj6syyvq7l-glibc-2.39-52/lib/libc.so.6 (0x00007fa77fd30000)
        /lib64/ld-linux-x86-64.so.2 => /nix/store/dbcw19dshdwnxdv5q2g6wldj6syyvq7l-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007fa781ee8000)

So at least now it’s obvious what’s happening, and I can proceed with patchelf.

Thanks for the pointers, folks! :raised_hands:

2 Likes