121 lines
5 KiB
Markdown
121 lines
5 KiB
Markdown
|
|
---
|
|
title: "Building a Debian rootfs from an unprivileged user with debootstrap"
|
|
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/90747310f8722ca7e3b6a13af3f0c0e76cf7dd74/debootstrap#L605
|
|
|
|
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/90747310f8722ca7e3b6a13af3f0c0e76cf7dd74/functions#L1664
|
|
|
|
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 paths exist in deboostrap when this variant is
|
|
selected that side-step some checks, and fake 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.
|