[Toybox] chroot pondering

scsijon scsijon at lamiaworks.com
Thu Feb 10 13:35:38 PST 2022


On 11/2/22 08:08, toybox-request at lists.landley.net wrote:
> Message: 1
> Date: Thu, 10 Feb 2022 02:03:33 -0600
> From: Rob Landley <rob at landley.net>
> To: toybox <toybox at lists.landley.net>
> Subject: [Toybox] chroot pondering.
> Message-ID: <e4af7e03-98c0-b2e4-8b70-b4438ffb0789 at landley.net>
> Content-Type: text/plain; charset=utf-8
>
> One type of change that tends to accumulate in my tree are things I implement
> enough of to know how to finish it, but am not sure I SHOULD do?
>
> Here's a change I had lying around in my tree that I just cleaned up so it
> works, adding a new "-f" option to chroot that uses execveat() to run a binary
> that lives outside the chroot in the chroot.
>
> This is a useful option for _me_ (something I've wanted chroot to be able to do
> for years), but I don't know how representative I am in this?
>
> The problem is, of course, that it has to be a _static_ binary, because although
> the executable is available via the fd, the shared libraries aren't. Debian's
> qemu-user-static package for the binfmt_misc stuff is static for the same reason:
>
> $ cat /proc/sys/fs/binfmt_misc/qemu-sh4
> enabled
> interpreter /usr/bin/qemu-sh4-static
> flags: OCF
> offset 0
> magic 7f454c4601010100000000000000000002002a00
> mask fffffffffffffffcfffffffffffffffffeffffff
>
> Which is why I can "chroot root/sh4/fs" after "scripts/mkroot.sh CROSS=sh4" and
> it works. The interpreter is static because launching a binary filched out of an
> adjacent namespace is NOT the same as the dynamic linker being able to straddle
> namespaces at runtime: exec(fd) after the chroot works fine, even when the fd is
> CLOEXEC, the libraries and dynamic linker itself would need to be in the chroot.
>
> Making glibc's dynamic linker work with this would involve a child process doing
> LD_BIND_NOW and using ptrace() to set a breakpoint and inject the chroot right
> before the call to main, but of course bionic ignores the environment variable
> and instead uses an ELF flag to indicate this for some reason? (Why? If you're
> making this decision at compile time you could just link it statically instead?
> Bionic still listens to LD_PRELOAD, so it's not conceptually objecting to
> dynamic linker control variables. Meanwhile, musl never does lazy binding so
> setting the variable for it is a NOP and it just needs the chroot before main()...)
>
> Anyway, anything like THAT would belongs in the "contain" command I've got in
> the post-1.0 todo heap instead, and possibly this does too, but for the record
> here's the patch...
>
> Rob
>
> diff --git a/toys/other/chroot.c b/toys/other/chroot.c
> index d791f34a..210afc09 100644
> --- a/toys/other/chroot.c
> +++ b/toys/other/chroot.c
> @@ -7,27 +7,46 @@
>    * The container guys use pivot_root() to deal with this, which does actually
>    * edit mount tree. (New option? Kernel patch?)
>
> -USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
> +USE_CHROOT(NEWTOY(chroot, "^<1f", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
>
>   config CHROOT
>     bool "chroot"
>     default y
>     help
> -    usage: chroot NEWROOT [COMMAND [ARG...]]
> +    usage: chroot [-f] NEWROOT [COMMAND [ARG...]]
>
>       Run command within a new root directory. If no command, run /bin/sh.
> +
> +    -f	Run (static) COMMAND from outside of chroot.
>   */
>
> +#define FOR_chroot
>   #include "toys.h"
>
> +// Try to be less impolite to BSD than a straight inline syscall()
> +#ifdef __NR_execveat
> +#define execveat(...) syscall(__NR_execveat, __VA_ARGS__)
> +#else
> +#define execveat(...)
> +#undef FLAG_f
> +#define FLAG_f 0
> +#endif
> +
>   void chroot_main(void)
>   {
> -  char *binsh[] = {"/bin/sh", "-i", 0};
> +  char *binsh[] = {"/bin/sh", "-i", 0},
> +        **ss = toys.optargs[1] ? toys.optargs+1 : binsh;
> +  int fd = FLAG(f) ? xopen(*ss, O_RDONLY|O_CLOEXEC) : -1;
>
>     if (chdir(*toys.optargs) || chroot(".")) {
>       toys.exitval = 125;
>       perror_exit_raw(*toys.optargs);
>     }
> -  if (toys.optargs[1]) xexec(toys.optargs+1);
> -  else xexec(binsh);
> +
> +  if (fd!=-1) {
> +    execveat(fd, "", ss, environ, 0x1000);
> +    error_exit("could not run '%s' (is it statically linked?)", *ss);
> +  }
> +
> +  xexec(ss);
>   }
>
> ------------------------------

I wonder if in this age, that rather than just a simple chroot you could 
/ should be looking at implementing something like pflask 
(https://github.com/ghedo/pflask). It does a lot more, is easy to use / 
implement, and hasn't needed updating since 2018 as it does all it is 
suppose to do.

regards





More information about the Toybox mailing list