linuxdynamic-linkingchroot

chroot "no such file or directory" prints wrong missing file


I know similar questions were asked here a billion times, but different to those I got my system working. I just get the wrong error-message when I break it (and I want to debug another issue using these, so it's critical they work).

Starting with the working system:

$ tree
.
├── bin
│   └── bash
├── lib
│   ├── libc.so.6
│   ├── libdl.so.2
│   └── libtinfo.so.6
└── lib64
    └── ld-linux-x86-64.so.2
$ sudo chroot . /bin/bash
bash-5.0#

As we'd expect everything to run the bash is there and the bash runs.

Now when I remove anything inside the lib folder, I get an error telling me the library is missing:

$ rm -f lib/libdl.so.2
$ sudo chroot . /bin/bash
/bin/bash: error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory

Also as expected. However, when I remove ld-linux-x86-64.so.2 inside the lib64 folder:

$ rm -f lib64/ld-linux-x86-64.so.2
$ sudo chroot . /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory

It's telling me /bin/bash is missing. The message is identical to when I actually delete it.

$ rm -f bin/bash
$ sudo chroot . /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory

So for some reason it thinks the bash is missing when actually, what I assume is the dynamic linker, is missing. I assume this is because it's using this linker to load the elf in the first place, but that doesn't make the message more correct.

I even checked when I actually prevent bash it from finding ld-linux-x86-64.so.2 by running it in qemu on a different system I get the correct error message:

<some arm system>$ qemu-x86_64 -L /tmp/nowhere bin/bash
/lib64/ld-linux-x86-64.so.2: No such file or directory

Is this a bug? Is there some option to tell chroot to not behave like this and print the actually missing file? Is there some magic in this file? What is going on here?

TLDR: Why does chroot tell me the executable is missing when actually lib64/ld-linux-x86-64.so.2 is?


Solution

  • Why does chroot tell me the executable is missing when actually lib64/ld-linux-x86-64.so.2 is?

    assuming you are using chroot program from GNU coreutils, we can look at the code to understand what is going on (hoping the "magic" will go away). here a github mirror of chroot.c.

    if we search for the string in the error message failed to run command, we immediately find the (only) line of code that prints it:

      /* Execute the given command.  */
      execvp (argv[0], argv);
    
      int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
      error (0, errno, _("failed to run command %s"), quote (argv[0]));
      return exit_status;
    }
    

    as you can see, it's printed after the execvp() system call. execvp() is (one variant of) the system call that allows to execute a program (/bin/bash in your case). execvp() does not return if the execution of the program was successful because:

    The exec() family of functions replaces the current process image with a new process image.

    it returns only in case of error and sets errno appropriately.

    the code then inspects errno to decide the exit status:

    int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
    

    and finally prints the error:

    error (0, errno, _("failed to run command %s"), quote (argv[0]));
    

    as you can see, argv[0] is always printed after failed to run command (/bin/bash in your case i.e. the program it tried to execute in a chroot environment).

    errno is the error number "returned" by execvp() and determines what gets printed after (error() is defined as follows on my system):

    /* Print a message with `fprintf (stderr, FORMAT, ...)';
       if ERRNUM is nonzero, follow it with ": " and strerror (ERRNUM).
       If STATUS is nonzero, terminate the program with `exit (STATUS)'.  */
    
    extern void error (int __status, int __errnum, const char *__format, ...)
    

    No such file or directory is error number ENOENT and is "returned" by execvp() and friends when:

    ENOENT The file pathname or a script or ELF interpreter does not exist.

    ("ELF interpreter" is a synonymous for dynamic linker I guess)

    chroot can't actually know what really went wrong, it can merely report what execvp() put in errno and I think this is the reason why the error is vague and a bit misleading.