The exploration logs are found at here.

Building the Kernel

Of course the small kernel must still be functional, so we will test it using qemu. One can also use virtualbox but I chose qemu because I will be working remotely using SSH and VNC takes too much mobile data lol.

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git --depth 1 --branch v6.1.4
$ cd linux
$ git log
commit 2cb8e624295ffa0c4d659fcec7d9e7a6c48de156 (grafted, HEAD, tag: v6.1.4)
    Author: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
        Date:   Sat Jan 7 11:12:04 2023 +0100
	Linux 6.1.4
$ apt install -y libncurses5 libncurses5-dev bison flex
$ make menuconfig 
# go enable PVH if u want to run in QEMU
$ vim .config # remove "debian" things, and the "certs/signing_key.pem"
$ apt install -y libssl-dev libelf-dev
$ make -j$(nproc) # change -jN to your liking
# if they prompt something involving certs, just press enter (default choices)

Important: Don’t forget to enable CONFIG_PVH=y in .config if you want to run in QEMU.

$ ls -lh vmlinux

vmlinux should exist, if it doesn’t, something went wrong and go back to build.

Otherwise, let’s run our kernel!

Running in QEMU

We need an Initrd? What’s Init RAM Disk? It is the first thing that the kernel looks for when it wakes up.

You can try the following command and see it complain that it was unable to find an init.

$ qemu-system-x86_64 -kernel ./vmlinux -nographic --append "console=tty0 console=ttyS0 panic=1 root=/dev/sda rootfstype=ext2" -hda rootfs.ext2 -m 1024 -vga none -display none -serial mon:stdio -no-reboot 
# Some kernel logs before we see
[    1.512696] Run /sbin/init as init process
[    1.513360] Run /etc/init as init process
[    1.513575] Run /bin/init as init process
[    1.513772] Run /bin/sh as init process
[    1.514056] Kernel panic - not syncing: No working init found.  Try passing init= option to kernel.

The kernel was frantically looking for an init executable in /sbin,/etc,/bin. Really just any executable (can be a shell script, or a binary executable).

Our own init

Let’s write our own init for now. The sleep is to prevent the kernel from panicing (it will panic if init exits).

$ mkdir vfs && cd vfs
$ cat << EOF > hello-kernel.c
#include <stdio.h>

int main(){
        printf("Hello, kernel!\n");
        sleep(9999999999999);
}
EOF
$ gcc --static hello-kernel.c -o init

Now we just need to package it up into a cpio format.

$ find . | cpio -o -H newc | gzip > root.cpio.gz

Then we can run QEMU successfully

$ qemu-system-x86_64 -kernel ./vmlinux -nographic --append "console=tty0 console=ttyS0 panic=1 root=/dev/sda rootfstype=ext2" -hda rootfs.ext2 -m 1024 -vga none -display none -serial mon:stdio -no-reboot -initrd initrd/root.cpio.gz
[    1.461412] x86/mm: Checked W+X mappings: passed, no W+X pages found.
[    1.461651] Run /init as init process
Hello, kernel!
[    1.946056] tsc: Refined TSC clocksource calibration: 3399.960 MHz
[    1.946430] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x3102293934f, max_idle_ns: 440795316s
[    1.946791] clocksource: Switched to clocksource tsc

Great! But now we don’t just want our init to say hello, we want our init to be able to put us into a shell and let us navigate and do productive work! That is where busybox comes in!

Busybox

Git clone and compile Busybox

$ cd .. # don't do stuff in the linux directory, git will be confused
$ git clone --depth=1 https://github.com/mirror/busybox.git && cd busybox
$ make defconfig 
$ vi .config # set CONFIG_STATIC=y
$ make -j$(nproc)
$ make CONFIG_PREFIX=$PWD/BUSYBOX install
$ ls BUSYBOX 
bin  linuxrc  sbin  usr

Then we make out initrd with busybox

$ cd ../linux # return back to linux directory
$ mkdir initrd && cd initrd # like vfs (from previously), this will house our new initrd
$ mkdir etc proc sys
$ cat << EOF > init                              
#!/bin/sh
                           
mount -t proc proc /proc
mount -t sysfs none /sys
                                                      
# https://busybox.net/FAQ.html#job_control
                                                      
mknod /dev/ttyS0 c 4 64
setsid sh -c 'exec sh </dev/ttyS0 >/dev/ttyS0 2>&1'
EOF                                                   
$ chmod +x init 

The above init shell script will be our entrypoint

$ cp ../../busybox/BUSYBOX/* . # copy over our compiled busybox utilities
$ find . | cpio -o -H newc | gzip > root.cpio.gz # package it

Basically, init will call our busybox utilities! Internally, /bin/ls is a symlink to /bin/busybox ls

Fun fact: Alpine linux uses busybox, Alpine linux is popular in docker containers due to it’s small size.

Booting

$ qemu-system-x86_64 -kernel ./vmlinux -nographic --append "console=tty0 console=ttyS0 panic=1 root=/dev/sda rootfstype=ext2" -hda rootfs.ext2 -m 1024 -vga none -display none -serial mon:stdio -no-reboot -initrd initrd/root.cpio.gz 

We should be dropped into a shell and now we can type busybox to see the commands we can use.

Running the Kernel on Hardware

If you’re on ubuntu, you can replace your current kernel (uname -r) with the one you compiled

$ update-grub2 # make sure you're using the grub bootloader
$ make modules_install
$ make install

When booting up, press and hold <Shift> to go to the grub menu, then go to advanced options to select the kernel you want to boot with.

Minimizing the Kernel Size

Let’s try to compile a kernel that is as small as possible.

Measuring Size

The kernel size can be determined using

$ ls -lh vmlinux
-rwxr-xr-x 1 tch tch 1.1G Jan 31 16:19 vmlinux

In this case, I will measure the size of vmlinux instead of vmlinuz since compression is cheating.

Using the default, we get a size of 1103946480 or 1.1G.

My Personal Best

I managed to get it down to 51M with this config.

ls -lh vmlinux
-rwxrwxr-x 1 tch tch 51M Feb  4 11:49 vmlinux

Theoretically it should be able to get down to around 10M as that is what I saw on some embedded linux forums. But I have other projects on hand that require my immediate attention, I’ll come back to it one day…

Happy Coding!

If you run into any errors, you may drop me a text on telegram!