Add post on unprivileged debootstrap
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
fmaury 2022-09-08 10:15:54 +00:00 committed by Florian Maury
parent 149c0375c8
commit 543f0ba1e1
No known key found for this signature in database

121
posts/debootscrap.md Normal file
View file

@ -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 <<EOF > 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.