From 543f0ba1e12789dad3c74adc9b7cd65be919143c Mon Sep 17 00:00:00 2001 From: fmaury Date: Thu, 8 Sep 2022 10:15:54 +0000 Subject: [PATCH] Add post on unprivileged debootstrap --- posts/debootscrap.md | 121 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 posts/debootscrap.md diff --git a/posts/debootscrap.md b/posts/debootscrap.md new file mode 100644 index 0000000..910f4e1 --- /dev/null +++ b/posts/debootscrap.md @@ -0,0 +1,121 @@ + +--- +title: "Building a Debian rootfs from an unprivileged user with deboostrap" +slug: debootscrap +authors: "Florian Maury" +description: "Faking fakechroot to build a debian rootfs from an unprivileged account, using user namespaces" +date: 2022-09-08T18:00:00+02:00 +type: posts +draft: false +layout: "singletoc" +categories: +- sysadmin +tags: +- debian +- unprivileged +- security +lang: en +--- + +At Gatewatcher[^GW], we put efforts in making our building system reproducible +and working offline, so that we can reduce the risk of supply chain attacks. +Some efforts are also made so that our building system run with as few +privileges as possible. +[^GW]: https://www.gatewatcher.com/ + +One of the few things we were still running as a privileged user recently was +the build of our initial Debian root filesystem, for our base system and for our +containers. + +Indeed, the official Debian Docker container from Docker Hub was not generated +in a way that we can consider secure for our need. It basically downloads a blob +from a web server, does no verification whatsoever of that blob and ships it as +the root filesystem[^debdock]. Even though the root filesystem they are using +can be rebuilt in a reproducible way, downloading the result from Internet +without verifying it against the expected hash is sort of missing the point of +reproducible builds. Also, debuerreotype uses debootstrap, which is problematic +in itself, as explained hereafter. +[^debdock]: https://github.com/debuerreotype/docker-debian-artifacts/blob/master/download.sh#L7 + +To create such root filesystem, multiple tools are provided by the Debian team, +among which `debootstrap`, and `multistrap`. + +Multistrap has not been updated in many years[^multistrap], and suffers from +some limitations that were show-stoppers for us, but it is capable to create +root filesystems from an unprivileged user without hacks. +[^multistrap]: https://browse.dgit.debian.org/multistrap.git/log/ + +On the other hand, deboostrap is not really friendly with the idea of building a +system from an unprivileged user. + +First of, there is a check to ensure we are running it with UID 0[^checkUID]. +This can be bypassed in several documented ways, including using `fakeroot`, +which overloads some libc calls, using `LD_PRELOAD`. An other, less hacky, way +is to run the program in a user namespace. +[^checkUID]: https://salsa.debian.org/installer-team/debootstrap/-/blob/bullseye/debootstrap#L586 + +Unfortunately, this is not sufficient to run `debootstrap`, since it performs +another check consisting of trying to create a "/dev/null" node[^checkNode]. +This is more problematic since nodes cannot be created from a user namespace, as +this would create a easy way of escaping the namespace. +[^checkNode]: https://salsa.debian.org/installer-team/debootstrap/-/blob/bullseye/functions#L1619 + +As it seems, though, there is a way to build an unprivileged Debian root +filesystem that is even built into deboostrap, using the installation variant +"fakechroot". Alternate code pathes exist in deboostrap when this variant is +selecte that side-step some checks, and fakes some calls. This variant also adds +a check to ensure this variant is run only if the `fakechroot` utility is in +use. Therefore, you are expected to run debootstap as followed, as documented in +the `fakechroot` manpage: + +```sh +# apt update && apt install -y debootstrap fakeroot fakechroot +$ fakechroot fakeroot debootstrap --variant=fakechroot bullseye $HOME/rootfs +``` + +`fakechroot` works by overloading some functions with `LD_PRELOAD`, and has some +documented limitations regarding symlinks. As it happens, these limitations +include rewrites of absolute symlinks, by prefixing them with the path of the +faked chroot. As a result, within the chroot, you will find links that are +broken when actually chrooting, such as when you would use that directory +hierarchy as a root filesystem on a container or a virtual machine. + +With `fakechroot`: + +```sh +$ readlink /path/to/my/chroot/usr/sbin/telinit +/path/to/my/chroot/bin/systemctl +``` + +Without `fakechroot` (this is what you want to see, in a normal system): + +```sh +$ readlink /path/to/my/chroot/usr/sbin/telinit +/bin/systemctl +``` + +After some verifications, we decided that it was safe to fake the use of +`fakechroot`, while using the "fakechroot" installation variant. For this, we +set the environment variable `FAKECHROOT` to `true`, which fakechroot is +supposed to set and which is controlled by debootstrap to authorize the use of +the "fakechroot" variant. And it worked. + +So to build a working root filesystem from an unprivileged user, we are now +doing the following: + +```sh +$ podman unshare +$ FAKECHROOT=true debootstrap --variant=fakechroot bullseye chroot/ +$ tar -C chroot/ --exclude=dev/* -czf ./chroot.tgz . +$ exit +$ cat < Containerfile +FROM scratch +ADD chroot.tgz . +CMD ["/bin/bash"] +EOF +$ podman build -f Containerfile +``` + +This series of commands builds a Debian container from an unprivileged user. +More work needs to be done to achieve offline reproducible builds, of course, +but none require hacks like this, thankfully.