Ready to Start Your Career?

Part 2: Protecting Your Data in Linux - A Deeper Look at Disk Encryption

zhak 's profile image

By: zhak

January 12, 2016


Creating initramfs

These steps are to be performed in chroot environment during installation of Linux distribution of your choice.Finally, comes the most exciting (and complicated) part – creation of initramfs!Initramfs is a root filesystem that's embedded into the kernel and loaded at an early stage of the boot process. It provides early userspace, which can do things the kernel can't easily do by itself during the boot process.When the kernel mounts the initramfs, your target root partition is not yet mounted, so you can't access any of your files. That means there's nothing but the initramfs. Everything you need, everything you want, you have to include it in your initramfs.If you want a shell, you have to include it in your initramfs. If you want to mount something, you need a mount utility. If you need to load a module, your initramfs has to provide both the module, as well as a utility to load it. If the utility depends on libraries in order to work, you have to include the libraries as well.Initramfs must contain /init file, which can be either a binary executable, or a shell script. It's executed by the kernel and does all the job. In our case, it will decrypt and map hard disk drive, find root partition on that drive, mount it, make it new OS root, and then pass execution to init file located on that partition to proceed with OS loading.There are lots of tools to automate initramfs creation process, like genkernel, dracut, mkinitcpio, etc. But, even if you won't ever create initramfs manually, it's still good to know how to do it.Well, enough of theory, lets create our initramfs already! We'll create initramfs from source files and embed it into kernel. Kernel then will be loaded directly from the bootable USB stick. No boot loaders involved.Create a new folder for initramfs sources:# mkdir /usr/src/initramfsThen, create initramfs directories structure:# mkdir -p /usr/src/initramfs/{bin,dev,etc,lib64,mnt/root,proc,root,run,sys,usr}And, symlinks for bin and lib64:# cd /usr/src/initramfs# ln -s bin sbin# ln -s bin usr/bin# ln -s bin usr/sbin# ln -s lib64 libThe next step is init script creation, which will:
  • mount proc, sys, and devtmpfs
  • find hard disk with installed Linux distro and decrypt it
  • find root partition on hard disk and mount it
  • switch to new root and execute /sbin/init located there
  • if error occurs, shell (bash) will be executed
I will first provide init script source code, and then go through each and every line in details.
#!/bin/bashexport PATH=/bin:/sbin:/usr/bin:/usr/sbinshell() {setsid bash -c 'exec bash </dev/tty1 >/dev/tty1 2>&1'}mount -t proc proc /procmount -t sysfs sysfs /sysmount -t devtmpfs devtmpfs /devecho 0 > /proc/sys/kernel/printkROOT_DEV=$(blkid -U 12345678-0123-4567-0123456789ab)cp –a /dev/tty /dev/tty.origcp -a /dev/console /dev/ttygpg -d /root/boot.key | cryptsetup -d – luksOpen ${ROOT_DEV} sda || shellcp –a /dev/tty.orig /dev/ttyrm /dev/tty.origkpartx -u /dev/dm-0[ -e /dev/dm-1 ] && mount /dev/dm-1 /mnt/root || shellexec switch_root /mnt/root /sbin/init
After creation, init script should be granted execute permission:# chmod +x init Let's go through init step by step:shell() {setsid bash -c 'exec bash </dev/tty1 >/dev/tty1 2>&1'}This line calls bash shell on tty1. The problem is that /init script is not supposed to run in interactive mode and is executed on /dev/console. Kernel doesn't provide controlling terminal for this device, but the shell (bash in our case) should be interactive in normal operation mode. To achieve interactiveness and the ability to run commands user types in command prompt, it should be run on a normal tty and be a session leader. setsid command is here to make our shell a leader of its own new session. To exec these lines, the following binaries should be added to initramfs: /bin/bash, /usr/bin/setsid.mount -t proc proc /procmount -t sysfs sysfs /sysmount -t devtmpfs devtmpfs /devThis should not rise any questions. Simply mount vital file systems. To exec these lines, the following binary should be added to initramfs: /bin/mountecho 0 > /proc/sys/kernel/printkStop kernel from displaying status messagesROOT_DEV=$(blkid -U 12345678-0123-4567-0123456789ab)When you boot from USB memory stick, kernel discovers both hard disk (SSD) and memory stick, then creates device nodes. Unless you use udev and specify the exact rules (which we don't do), either USB drive or SSD can be discovered first and assigned /dev/sda node. In simple words, each new boot /dev/sda can be either USB stick or SSD.The device discovered second will become /dev/sdb. We need to determine what node our hard disk was assigned by kernel, and we do it by device's UUID. Devices formatted with LUKS have UUIDs, which can be found with the help of blkid command:# blkid /dev/sda To exec this line, the following binary should be added to initramfs: /sbin/blkidcp –a /dev/tty /dev/tty.origcp -a /dev/console /dev/ttygpg -d /root/boot.key | cryptsetup -d – luksOpen ${ROOT_DEV} sda || shellcp –a /dev/tty.orig /dev/ttyrm /dev/tty.origCrypto key file created at step 3 will reside inside initramfs, but keeping it in plain mode is very insecure. If anyone gets or copies your bootable USB stick, they can gain access to all your data as easily as if there was no encryption applied. We will secure crypto key file by encrypting it a bit later. Each time on boot you will be prompted to enter the passphrase to unlock key file. Otherwise, the system won't boot.To encrypt/decrypt key file we will uses GnuPG tool. But, newer GnuPG 2.x became rather complex and requires gpg-agent daemon to run. Embedding it into initramfs is really a pain, thus you should get an older 1.4.x version of GnuPG (1.4.19 is the latest).However, there's an issue with this tool. If you try to simply run it from the init script, it will fail with an error "gpg: cannot open tty '/dev/tty': No such device or address". A workaround is to copy /dev/console the init script is running on to /dev/tty prior to executing gpg command. /dev/tty should be backed up and restored after gpg has finished its task.One more note on gpg: it checks for config files and creates ones that are missing. Config is created from /usr/share/gnupg/options.skel file, which must also be added to initramfs. To exec these lines, the following binaries should be added to initramfs: /bin/cp, /usr/bin/gpg, /sbin/cryptsetup, /bin/rmkpartx -u /dev/dm-0As you remember, opening a LUKS device with cryptsetup luksOpen command creates /dev/mapper/{name} device node, which is a symlink of /dev/dm-0. As you also remember, luksOpen only creates a device mapping. It's like hot plugging a new hard disk to your system – in order to access partitions on it, you should first discover them and notify kernel about it. This can be done by various tools like partprobe, hdparm, blockdev, kpartx, etc.The problem with initramfs is that discovering partitions on /dev/mapper/{name} doesn't work. Don't ask me why. This is what I don't know. You will need to discover partitions on /dev/dm-0 device. Discovered partition will appear as /dev/dm-1. Another problem with initramfs is that not all tools work. While kpartx works on all my systems, it could probably fail on yours. Therefore, try other tools if kpartx fails for you. To exec this line, the following binary should be added to initramfs: /sbin/kpartx[ -e /dev/dm-1 ] && mount /dev/dm-1 /mnt/root || shellIf partition has been discovered successfully, then mount it. Otherwise fall back to shell. To exec this line, the following binary should be added to initramfs: /bin/mountexec switch_root /mnt/root /sbin/initFinally, switch to original root file system and continue system initialization by executing /sbin/init. To exec this line, the following binary should be added to initramfs: /sbin/switch_rootThis is the minimal init required to boot the system. You can further expand it to your needs.Now, it's time to fill /bin and /lib64 directories. According to init script, we'll need the following binaries included:/bin/bash/bin/cp/bin/mount/bin/rm/sbin/blkid/sbin/cryptsetup/sbin/kpartx/sbin/switch_root/usr/bin/gpg/usr/bin/setsidHowever, you would probably like to include a bunch of other tools like ls, nano, what-you-name.All above binaries should be copied to /usr/src/initramfs/bin directory and all shared libraries those binaries depend on should be copied to /usr/src/initramfs/lib64 directory. To avoid numerous dependencies on shared libraries, it's better to include statically built binaries to initramfs. To check if and what libraries a binary uses, use ldd tool. For example:# ldd /bin/ (0x00007ffcc5cb1000) => /lib64/ (0x00007fd8e7099000) => /lib64/ (0x00007fd8e6cfe000) => /lib64/ (0x00007fd8e6abb000)/lib64/ (0x00007fd8e72e2000) => /lib64/ (0x00007fd8e68b6000)As such, to be able to use mount utility in initramfs, you should copy it along with all libraries except, which is provided by kernel. The final point is to encrypt key file with GnuPG and include it in initramfs:# gpg --symmetric --cipher-algo AES256 --output /usr/src/initramfs/root/boot.key /mnt/usb-boot/key.fileDon't forget to delete unsecured key file from USB stick when all is done.
That's it for now. Initramfs sources are ready to be included in kernel, which we'll do in the next part.
Schedule Demo