Faking /dev for rootless build environments

Published: 2026/09/07

I've been working on stuffing kiss linux into the BIOS flash of a Thinkpad X220.

The cliff notes for that project for anyone interested:

For this I need to create a separated build environment (think container) for constructing the initramfs which the kernel sets up on boot. Now the typical way of setting this up would use chroot and bind mounts in the host's pseudo file systems such as /dev. And that would've worked just fine, though I really wanted the build script to not require escalating to root privileges.

Why? Because I thought it would be an interesting experiment and if I'm being honest a little piece of me cringes every time a script asks for a password because it's trying to run sudo.

Though to be fair I could've just used sudo to run chroot and things would've been fine. Whilst there some security use-cases where going to the effort to avoid privilege escalation is required, generally as long as you're comfortable with what a script is doing (always make sure to read scripts you download from the internet before running them) there isn't a huge threat to a script to use sudo.

Unshare

So to replace chroot I looked at the unshare tool. Unshare is used for unsharing namespaces from a process.

Linux namespaces are a way of partitioning resources. There's namespaces for mounts, networking, PID, users, etc. In this context it gives us a way of separating the subsystem for the build container from the host. E.g. Also

And with user namespaces enabled, regular users are allowed to create namespaces without requiring extra permissions. This means you can achieve the same functionality of chroot using

$ unshare --fork --user --map-root-user --mount --pid --root $ROOTFS

Without requiring root.

Devfakefs

Now we have a different problem. It's common for scripts and tools to utilize files such as /dev/null or /dev/urandom which aren't present in our container. Usually one would bind mount the host's /dev

# mount --bind /dev $ROOTFS/dev

Though bind mounting requires root permissions.

Another option would be to use mknod to create the required character devices

# mknod $ROOTFS/dev/null c 1 3

But again it requires root permissions.

We could try being cheeky and just creating a regular file for /dev/null.

$ touch $ROOTFS/dev/null

This doesn't require root privileges though it only works if you only ever write to /dev/null, things tend to freak out when they read /dev/null and gets something in return (as I found out when trying to compile busybox which throws all sorts of weird errors when /dev/null isn't null)

$ echo "hi" > $ROOTFS/dev/null
$ cat $ROOTFS/dev/null
hi

However FUSE file systems don't require root permissions to mount as the FUSE mount utility uses a SUID binary. So if there was a FUSE file system which could mimic the behaviour of common /dev character devices, our scripts would work without needing to use sudo to setup the build environment.

That's what devfakefs is.

$ devfakefs $ROOTFS/dev
$ echo "hi" > $ROOTFS/dev/null
$ cat $ROOTFS/dev/null
<nothing>