Commit 70cb669a authored by K900's avatar K900
Browse files

buildFHSEnv: fix nested fhsenvs with LD_PRELOAD



I hate this, but I also kinda love this. It's very cursed. Please help.

Co-authored-by: default avatarAlyssa Ross <hi@alyssa.is>
parent 7014f869
Loading
Loading
Loading
Loading
+63 −0
Original line number Diff line number Diff line
#include <fstream>

#include <spawn.h>
#include <string.h>
#include <unistd.h>

#include <sys/wait.h>

const char LD_SO_CONF[] = R"(/lib
/lib/x86_64-linux-gnu
/lib64
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib64
/lib/i386-linux-gnu
/lib32
/usr/lib/i386-linux-gnu
/usr/lib32
/run/opengl-driver/lib
/run/opengl-driver-32/lib
)";

int main(int, const char *argv[]) {
  std::ofstream ld_so_conf;
  ld_so_conf.open("/etc/ld.so.conf");
  ld_so_conf << LD_SO_CONF;
  ld_so_conf.close();
  if (!ld_so_conf) {
    perror("Failed to generate ld.so.conf");
    return 1;
  }

  int e;
  pid_t pid;
  const char *ldconfig_argv[] = {"/bin/ldconfig", NULL};
  char *ldconfig_envp[] = {NULL};
  if ((e = posix_spawn(&pid, ldconfig_argv[0], NULL, NULL,
                       (char *const *)ldconfig_argv, ldconfig_envp))) {
    fprintf(stderr, "Failed to run ldconfig: %s\n", strerror(e));
    return 1;
  }

  int status;
  if (waitpid(pid, &status, 0) == -1) {
    perror("Failed to wait for ldconfig");
    return 1;
  }
  if (WIFEXITED(status)) {
    if (WEXITSTATUS(status)) {
      fprintf(stderr, "ldconfig exited %d\n", WEXITSTATUS(status));
      return 1;
    }
  } else {
    fprintf(stderr, "ldconfig killed by signal %d\n", WTERMSIG(status));
    return 1;
  }

  argv[0] = "/init";
  execv(argv[0], (char *const *)argv);

  perror("Failed to exec stage 2 init");
  return 1;
}
+25 −22
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
, writeShellScript
, glibc
, pkgsi686Linux
, runCommandCC
, coreutils
, bubblewrap
}:
@@ -98,29 +99,30 @@ let
    ];
  in map (path: "/etc/${path}") files;

  # Create this on the fly instead of linking from /nix
  # The container might have to modify it and re-run ldconfig if there are
  # issues running some binary with LD_LIBRARY_PATH
  createLdConfCache = ''
    cat > /etc/ld.so.conf <<EOF
    /lib
    /lib/x86_64-linux-gnu
    /lib64
    /usr/lib
    /usr/lib/x86_64-linux-gnu
    /usr/lib64
    /lib/i386-linux-gnu
    /lib32
    /usr/lib/i386-linux-gnu
    /usr/lib32
    /run/opengl-driver/lib
    /run/opengl-driver-32/lib
    EOF
    ldconfig &> /dev/null
  # Here's the problem case:
  # - we need to run bash to run the init script
  # - LD_PRELOAD may be set to another dynamic library, requiring us to discover its dependencies
  # - oops! ldconfig is part of the init script, and it hasn't run yet
  # - everything explodes
  #
  # In particular, this happens with fhsenvs in fhsenvs, e.g. when running
  # a wrapped game from Steam.
  #
  # So, instead of doing that, we build a tiny static (important!) shim
  # that executes ldconfig in a completely clean environment to generate
  # the initial cache, and then execs into the "real" init, which is the
  # first time we see anything dynamically linked at all.
  #
  # Also, the real init is placed strategically at /init, so we don't
  # have to recompile this every time.
  containerInit = runCommandCC "container-init" {
    buildInputs = [ stdenv.cc.libc.static or null ];
  } ''
    $CXX -static -s -o $out ${./container-init.cc}
  '';
  init = run: writeShellScript "${name}-init" ''

  realInit = run: writeShellScript "${name}-init" ''
    source /etc/profile
    ${createLdConfCache}
    exec ${run} "$@"
  '';

@@ -253,6 +255,7 @@ let
      --symlink /etc/ld.so.cache ${glibc}/etc/ld.so.cache \
      --ro-bind ${glibc}/etc/rpc ${glibc}/etc/rpc \
      --remount-ro ${glibc}/etc \
      --symlink ${realInit runScript} /init \
  '' + optionalString fhsenv.isMultiBuild (indentLines ''
      --tmpfs ${pkgsi686Linux.glibc}/etc \
      --symlink /etc/ld.so.conf ${pkgsi686Linux.glibc}/etc/ld.so.conf \
@@ -265,7 +268,7 @@ let
      "''${auto_mounts[@]}"
      "''${x11_args[@]}"
      ${concatStringsSep "\n  " extraBwrapArgs}
      ${init runScript} ${initArgs}
      ${containerInit} ${initArgs}
    )
    exec "''${cmd[@]}"
  '';