Using QEMU and binfmt_misc to chroot into an aarch64 file system
The initial situation
This is where I was when I started this adventure:
- I have a Raspberry Pi 4 running Arch Linux ARM. The initramfs is generated using
dracut
, because I’m usingdracut-sshd
to remotely unlock a LUKS2-encrypted volume. - I changed something in my
dracut
configuration, manually regenerated the initramfs, rebooted, and was unable to SSH into the RPi.
My conclusion was that I probably messed up the command to manually create the initramfs. An easy
way to fix it should be to re-install the linux-rpi
package, which automatically results in an
updated initramfs being generated.
So I did the following:
- Turn off the RPi, remove the SD card and connect it to my PC using a USB adapter.
- Mount the card’s data partition at
/mnt
, and the boot partition at/mnt/boot
. - Chroot into the filesystem using
arch-chroot
.
When running step 3, I was greeted by this error:
chroot: failed to run command ‘/bin/bash’: Exec format error
I then realized that I was trying to chroot into a filesystem belonging to an aarch64 machine using an x64 machine. I can’t just execute the binaries in the chroot, as they were compiled for a different architecture.
The almost-solution
Searching for solutions online led me to to this StackOverflow answer, which I’ll reproduce here:
- Install the
qemu-user-static
package (or whatever the name is in your Linux distribution). - Copy the newly installed
qemu-$YOUR_ARCH_HERE-static
binary to the mounted filesystem, in my case:sudo cp /usr/bin/qemu-aarch64-static /mnt/usr/bin
. - Run
sudo arch-chroot /mnt qemu-aarch64-static /bin/bash
to use QEMU to run the nativebash
executable.
Depending on which distribution you use, this might already be all you need to do (read on for why). In my case (Arch Linux) however, the above instructions got me a working shell inside the chroot, but trying to execute any command again gave the “Exec format” error:
bash: /usr/bin/ls: cannot execute binary file: Exec format error
I suppose this is because bash
spawns the process that runs the command in such a way that it ends
up outside of the usermode emulation again.
You can get around this by manually using qemu-aarch64-static
again, e.g. running
qemu-aarch64-static /bin/ls
, but this is (a) cumbersome and (b) doesn’t work if you want to run a
shell script.
The solution
Luckily, the Linux kernel has a way that enables you to seamlessly run binaries compiled for other
architectures, if you have the right interpreter at hand (QEMU in our case) and configure the kernel
correctly. This nifty feature is called binfmt_misc
.
First, check if the feature is already active on your machine by verifying the
/proc/sys/fs/binfmt_misc
directory exists. If it doesn’t, you can enable the feature with the
following command:
As we already have the interpreter installed, we just need to do the configuration part. If you’re
interested in the details, feel free to read the binfmt_misc
documentation linked above, I’ll just
paste the script that configures everything here. I took the magic and mask values from this QEMU
script. If you want use this yourself for a different architecture, you’ll need to use the correct magic
, mask
, and arch
values.
magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'
mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'
arch='aarch64'
interpreter="/usr/bin/qemu- -static"
|
There should now be a /proc/sys/fs/binfmt_misc/qemu-$arch
file with contents similar to these:
enabled
interpreter /usr/bin/qemu-aarch64-static
flags:
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff
And now, we can simply run sudo arch-chroot /mnt
, and everything should work as expected!
Of course, after I’d done the manual setup, I found this page in the Arch Linux
Wiki, where I learnt that installing the qemu-user-binfmt
package and then running sudo systemctl restart systemd-binfmt
would’ve done the configuration for me.
I don’t regret doing it manually, though, because I now know a bit more about binfmt_misc
that I
would have otherwise, and also using the config from that package doesn’t work for me. arch-chroot
always exits with this error:
chroot: failed to run command ‘/bin/bash’: No such file or directory
I suspect this is because the package’s config sets the P
flag, and I didn’t do that in my manual
config. This can probably be fixed somehow, but for now I’m happy with using my manual config.