Writing derivation for binary driver that accesses hard coded paths in the FHS

I’m trying to package dell-command-configure.

Dell provides a debian package which we can extract a binary and shared object files to create a derivation.

The problem: the runtime accesses a file at /usr/lib/ext/dell/omreg.cfg.

I could create a NixOS module with a one-shot systemd service to create this file, but as omreg.cfg is provided in the debian package this doesn’t seem like the ideal solution.

The absolute path to this file is contained in some of the shared object files:

grep -r '/usr/lib/ext/dell/omreg.cfg' .
grep: ./srvadmin-hapi/opt/dell/srvadmin/lib64/libdchbas.so.9.5.0: binary file matches
grep: ./srvadmin-hapi/opt/dell/srvadmin/lib64/libdchcfl.so.9.5.0: binary file matches
grep: ./srvadmin-hapi/opt/dell/srvadmin/lib64/libdchipm.so.9.5.0: binary file matches

But patching them results in a segfault unless the replacement path is exactly the same length:

installPhase = '''
   ...
  sed -i 's/\/usr\/lib\/ext\/dell\/omreg.cfg/hello-world/g' $out/lib/*
'';
$ sudo cctk
zsh: segmentation fault  sudo cctk

I assume because some addresses are accessed via a relative pointer.

I’ve also played with using libredirect to intercept the FS call, to no success

wrapProgram $out/bin/cctk \
  --set LD_PRELOAD "${libredirect}/lib/libredirect.so"\
  --set NIX_REDIRECTS "/opt/dell/srvadmin/etc/omreg.cfg=$out/omreg-hapi.cfg:/usr/lib/ext/dell/omreg.cfg=$out/omreg-hapi.cfg"

I’m not really sure how libredirect works thought.

Does anyone have any ideas?

Looks like Replacing hard coded paths in ELF binaries - tape software addresses this exact problem

1 Like

Using buildFHSUserEnv doesn’t seem to work for these shared libraries:

let dell-command-configure = stdenv.mkDerivation { ... }; in
buildFHSUserEnv {
  name = "cctk";
  inherit (dell-command-configure) meta;

  runScript = "${dell-command-configure}/bin/cctk";

  extraInstallCommands = ''
    mkdir -p $out/usr/lib/ext/dell
    cat ${dell-command-configure}/omreg-hapi.cfg > $out/usr/lib/ext/dell/omreg.cfg
  '';
}
$ tree $(dirname $(readlink `whereis cctk`))/../
/nix/store/kxpxpgrdxbi6wyd53n1397cnldzij916-cctk/bin/../
├── bin
│   └── cctk
└── usr
    └── lib
        └── ext
            └── dell
                └── omreg.cfg

5 directories, 2 files
$ sudo cctk
[sudo] password for ryan:
Error communicating with BIOS - Unable to Get DMI Information!
Status = 0
bool SMBIOSPresent = 0
TableLength = 0

Unable to get BIOS tables or Unknown type encountered!!.

(this is the error when cctk can’t find /usr/lib/ext/dell/omreg.cfg)

Well, it works! some absolute wizardry · RyanGibb/nixos@ccbf20f · GitHub

Thanks @raphi.

Unfortunately I can’t upstream this to nixpkgs because it uses rahpi’s fork of patchelf, but you can use it from my flake nix shell github:RyanGibb/nixos/ccbf20f\#cctk.

Do you think you would consider upstreaming your fork of patchelf @raphi?

1 Like

Yeah it’s already on my agenda (no ETA)

2 Likes

(For others) the issue is being tracked here: Add option to replace an imported symbol · Issue #416 · NixOS/patchelf · GitHub

Reading about LD_PRELOAD, the Nixpkgs libredirect should work with:

  preFixup = ''
    wrapProgram $out/bin/cctk \
      --set LD_PRELOAD "${libredirect}/lib/libredirect.so" \
      --set NIX_REDIRECTS /usr/lib/ext/dell/omreg.cfg=$out/omreg-hapi.cfg.cfg
  '';

Which creates:

cat /nix/store/2nrambmc6h3n1yfxaywk67yrhxpvzk8w-dell-command-configure-4.6.0-277/bin/cctk
#! /nix/store/dd3713mm8wql4r2d5jxx0f58g16nfm4h-bash-5.1-p16/bin/bash -e
export LD_PRELOAD='/nix/store/b8djymhdrwvyi4r7g86gpy7lva2zy48p-libredirect-0/lib/libredirect.so'
export NIX_REDIRECTS='/usr/lib/ext/dell/omreg.cfg=/nix/store/3qj5rmvl2ix0dibgj5g427hvb66i8c2n-dell-command-configure-unpacked-4.6.0-277/srvadmin-hapi/opt/dell/srvadmin/etc/omreg.d/omreg-hapi.cfg'
exec -a "$0" "/nix/store/2nrambmc6h3n1yfxaywk67yrhxpvzk8w-dell-command-configure-4.6.0-277/bin/.cctk-wrapped"  "$@"

But the process still seems to access the file at the absolute path:

# strace cctk
...
access("/usr/lib/ext/dell/omreg.cfg", F_OK) = -1 ENOENT (No such file or directory)
...

It could be an issue with how I’m using libredirect, or how the binary is using the shared libraries.

It seems like this the issue is while LD_PRELOAD can replace shared libraries, it doesn’t propitiate to replacing shared libraries (our wrapper/libredirect) of shared libraries (cctk’s dependencies)…

That’s super cool! I can already see how it could prove useful to patch programs that expect configuration to be written in the same folder as the executable (read-only in nix). This way we can even patch the function the check some XDG environment variable to let each user have their own configuration.

1 Like

@tobiasBora True! Although I should say it’s definitely preferable to patch the source if it’s available.

Yes sure!

Actually, I was thinking that when patching the library is not possible (for instance because there is not even a library as the file is static), it may be possible to modify the syscall using ptrace… even if I’m not an expert and I don’t know if it’s possible to easily “override” a single library this way. Hopefully the number of times where such hack is needed is quite small.

I think using ptrace would be another option, although not one I’ve explored!

I’ve added a PR to upstream this dell-command-configure: init at 4.8.0-494 by RyanGibb · Pull Request #260715 · NixOS/nixpkgs · GitHub