Installation

Boot from an Arch Linux live USB.

Preparation

Set the keyboard layout to your language, here I am using the French keyboard layout:

loadkeys fr

Set the clock to your timezone:

timedatectl set-timezone Europe/Paris
timedatectl set-ntp true

Partitioning

List the disks:

fdisk -l

Choose the desired disk and create 3 (or 4) partitions:

  • An EFI partition (for UEFI)
  • A boot partition (for the bootloader)
  • A root partition ( / )
  • Optionally, a home partition ( /home )

Partition the disk using parted:

parted /dev/sdX

The EFI partition should be 500MiB.

With a /home partition:

mklabel gpt
mkpart primary fat32 1MiB 501MiB  # EFI partition
set 1 esp on
mkpart primary ext4 501MiB 20.5GiB # Root partition, approximately 20GiB
mkpart primary ext4 20.5GiB 100% # Home partition, using the remaining space
q

Without a /home partition:

mklabel gpt
mkpart primary fat32 1MiB 551MiB  # EFI partition
set 1 esp on
mkpart primary ext4 551MiB 100% # Root partition
q

Encryption of Partitions

Choose a passphrase (long enough) for the /root partition (and one for the /home partition if you have one) and encrypt the partition(s). We will use AES with a 512-bit key, a key derivation time of 5000 ms, and the Argon2id algorithm.

cryptsetup -v --type luks -c aes-xts-plain64 -s 512 -h sha512 --pbkdf argon2id -i 5000 -y luksFormat /dev/sdX2
cryptsetup -v --type luks -c aes-xts-plain64 -s 512 -h sha512 --pbkdf argon2id -i 5000 -y luksFormat /dev/sdX3

Mount the partition(s):

cryptsetup open /dev/sdX2 root
cryptsetup open /dev/sdX3 home

Formatting Partitions

Format the partitions as ext4 (except for the EFI partition, which is formatted as fat32):

mkfs.fat -F32 /dev/sdX1
mkfs.ext4 /dev/mapper/root
mkfs.ext4 /dev/mapper/home

Arch Linux Installation

Mount the various partitions under /mnt:

mount /dev/mapper/root /mnt
mkdir /mnt/home && mount /dev/mapper/home /mnt/home # If using /home partition
mkdir -p /mnt/boot/efi && mount /dev/sda1 /mnt/boot/efi

Install the base packages and the hardened kernel (replace linux-hardened with linux for the regular kernel):

pacstrap /mnt base base-devel linux-hardened linux-hardened-headers linux-firmware

Generate the fstab:

genfstab -U /mnt >> /mnt/etc/fstab

Arch Linux Configuration

Use the chroot command to enter our new installation as the root:

arch-chroot /mnt

Choose the hostname for the machine (here we use MachineName as an example):

echo MachineName > /etc/hostname
echo '127.0.0.1 MachineName.localdomain MachineName' >> /etc/hosts

Configure the clock:

ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime
hwclock --systohc

Generate the system’s locales:

  • In French:

    sed -i 's/#fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen
    locale-gen
    echo LANG="fr_FR.UTF-8" > /etc/locale.conf
    export LANG=fr_FR.UTF-8
    
  • In English:

    sed -i 's/#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
    locale-gen
    echo LANG="en_US.UTF-8" > /etc/locale.conf
    export LANG=en_US.UTF-8
    

Configure the keyboard layout as AZERTY:

echo KEYMAP=fr > /etc/vconsole.conf

Set a password for the root account:

passwd

Boot and Decryption Management

Install efibootmgr and a text editor:

pacman -S efibootmgr <TEXT_EDITOR>

Since we haven’t created a separate partition for /boot, we will create a unified kernel image so that our installation can be bootable.

Brief explanation of the Linux boot process

Typically, to boot a Linux system, the UEFI (the motherboard firmware) starts by loading a bootloader, a program that “prepares” the system to boot the Linux kernel. The bootloader is a .efi file located on the first partition of the disk. It loads a CPU microcode (a set of patches provided by the CPU manufacturer to ensure its stability), an initramfs (which creates an initial file system to access the root file system), and finally a Linux kernel. It also allows passing parameters to the kernel before booting. For more information, see https://wiki.archlinux.org/title/Arch_boot_process.

Some bootloaders, such as GRUB or systemd-boot, allow choosing between multiple kernels or operating systems (in the case of multiboot).

In our case, we will configure the system to boot directly into the kernel without using a bootloader (reducing the attack surface) by compiling the CPU microcode, initramfs, the kernel, and its arguments into a single .efi file that can be launched directly by the motherboard.

Microcode Installation

Install the microcode for Intel and AMD processors:

pacman -S amd-ucode     # for AMD
pacman -S intel-ucode  # for Intel

If you are installing Arch on a bootable USB drive, you can install the microcodes for Intel and AMD to support different processors.

Kernel Boot Arguments

Retrieve the UUID of the / partition:



lsblk -f /dev/sdX2

Edit the /etc/kernel/cmdline file:

  • Add the options pti=on, page_alloc.shuffle=1, and rw.
  • Add the disk decryption information cryptdevice=UUID=<partition UUID>:root root=/dev/mapper/root (the UUID is the one where FSTYPE is crypto_LUKS in the lsblk -f /dev/sdX command)
  • Finally, add the options loglevel=3 quiet bgrt_disable

Your file should look like this:

pti=on page_alloc.shuffle=1 rw cryptdevice=UUID=XXXXXXXX-XXXX-4XXX-XXXX-XXXXXXXXXXXX:root root=/dev/mapper/root mem_sleep_default=deep loglevel=3 quiet bgrt_disable

If you are using an SSD, which is likely, you can improve performance for dm-crypt in terms of reading and writing by disabling asynchronous reads and writes and queuing mechanisms: replace the cryptdevice=UUID=<partition UUID>:root root=/dev/mapper/root part with cryptdevice=UUID=<YOUR UUID>:root:no-read-workqueue,no-write-workqueue root=/dev/mapper/root

Generating EFI Images

Edit the /etc/mkinitcpio.conf file and add the keymap and encrypt hooks, which will allow us to decrypt our root partition during boot:

HOOKS=(base udev autodetect modconf block filesystems keyboard fsck keymap encrypt)

If you have a partition for /home, modify the /etc/fstab file to avoid using the UUID of /dev/mapper/home for mounting. Comment out the corresponding line and add one last line containing:

/dev/mapper/home /home ext4 rw,relatime,data=ordered 0 2

If you have an SSD and want to improve its longevity, you can replace relatime with noatime in /etc/fstab. This will disable file access timestamp updates and reduce the number of disk writes.

Now we will create an mkinitcpio preset to generate a bootable unified image. Edit the /etc/mkinitcpio.d/linux-hardened.preset file and add the following content:

# mkinitcpio preset file for the 'linux-hardened' unified image

ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux-hardened"
ALL_microcode="/boot/*-ucode.img"

PRESETS=('default' 'fallback')

default_image="/boot/initramfs-linux-hardened.img"
default_uki="/boot/efi/EFI/Linux/archlinux-linux.efi"
default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"

fallback_image="/boot/initramfs-linux-hardened-fallback.img"
fallback_options="-S autodetect"
fallback_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
fallback_uki="/boot/efi/EFI/Linux/archlinux-linux-fallback.efi"

This preset will allow us to generate two EFI files: /boot/efi/EFI/Linux/archlinux-linux.efi and /boot/efi/EFI/Linux/archlinux-linux-fallback.efi, containing our CPU microcode, initramfs, and kernel.

Generate the EFI images:

mkdir -p /boot/efi/EFI/Linux
mkinitcpio -p linux-hardened

Adding UEFI Entries

Add the UEFI entries for our images so that we can select them in our motherboard:

efibootmgr --create --disk /dev/sdX --part 1 --label "Arch Linux" --loader 'EFI\Linux\archlinux-linux.efi' --unicode
efibootmgr --create --disk /dev/sdX --part 1 --label "Arch Linux Fallback" --loader 'EFI\Linux\archlinux-linux-fallback.efi' --unicode

At this point, your system should be bootable.

Setting Up Secure Boot

Now that our system can boot, we will set up secure boot.

Secure Boot is a security mechanism that verifies the integrity of the kernel and modules loaded during boot. It ensures that the loaded files are signed with a cryptographic key, thereby ensuring they haven’t been tampered with by an attacker.

The first step to setting up secure boot is to install a tool for managing signatures and keys. We will install sbctl:

pacman -S sbctl

Next, we will generate keys that will be used to sign our efi files:

sbctl create-keys

Then, we will enroll our keys in the motherboard:

sbctl enroll-keys

You may need to restart your system and enter the UEFI to enter setup or audit mode (depending on how your motherboard manufacturer calls it) to have the opportunity to enroll the keys.

If enrolling the keys fails, you can try using sbctl enroll-keys --microsoft to enroll the Microsoft keys in addition to the ones you have generated. Some devices have their firmware signed with Microsoft keys, and your computer might refuse to boot without them…

Now, we will sign our efi files:

sbctl sign -s /boot/efi/EFI/Linux/archlinux-linux.efi
sbctl sign -s /boot/efi/EFI/Linux/archlinux-linux-fallback.efi

We can now verify that the files are properly signed:

sbctl verify

Finally, reboot, enable secure boot in the UEFI, and verify that the system boots correctly.

To ensure that everything is properly configured, you can use sbctl status to check if secure boot is enabled and the keys are enrolled.

The sbctl sign-all command can be used to sign all previously signed efi files. If you have modified any of the efi files, for example with mkinitcpio, you can use this command to sign them again.

Disk Decryption via SSH

If you want to be able to use your computer remotely (e.g., by turning it on via Wake-on-LAN), you will need to decrypt the disks before even launching the kernel. For this, we can use Dropbear, an SSH client that starts with the bootloader.

Installing Dependencies

pacman -S mkinitcpio-dropbear mkinitcpio-utils mkinitcpio-netconf

Generating Keys

Generate an Ed25519 key pair that will be used for SSH login:

ssh-keygen -t ed25519 -f ~/.ssh/dropbear_ssh

Copy the public key to the Dropbear configuration folder:

cp ~/.ssh/dropbear_ssh.pub /etc/dropbear/root_key

Copy the private key to the device you will use to decrypt your disk.

Configuring mkinitcpio

Add the netconf, dropbear, and encryptssh hooks to the `/etc/mk

initcpio.conf` file:

HOOKS=(base udev autodetect modconf block netconf dropbear keyboard keymap encryptssh filesystems fsck)

Modify the /etc/kernel/cmdline file and add an ip field:

ip=192.168.1.1:::::eth0:none

The ip field has the following format: ip=<ip>::<gateway>:<mask>::<interface>:<none|dhcp>.

For example, to have your computer start with the IP address 192.168.1.22 on the 192.168.1.0/24 network with a gateway at 192.168.1.254:

ip=192.168.1.22::192.168.1.254:255.255.255.0::eth0:none

To automatically detect the network configuration and use DHCP:

ip=:::::eth0:none

Generating EFI Images

Regenerate the EFI files:

mkinitcpio -p linux-hardened

Disk Decryption

On the next boot, you will have the option to connect to your computer via SSH using the generated key. You only need to enter the decryption key for your disk, and your computer will boot.

Installing Base Packages and Tweaks

Network

If you want to install additional packages on your installation, you will need internet access. Before exiting the installation media, install NetworkManager:

pacman -S networkmanager
systemctl enable NetworkManager

Hardened Malloc

If you want additional protection against memory-related security vulnerabilities (such as buffer overflow), you can replace the glibc malloc implementation with the one provided by the GrapheneOS team: hardened_malloc.

Hardened Malloc is not available in the official Arch Linux repositories but can be found in the community repositories (https://aur.archlinux.org/packages/hardened_malloc). Unfortunately, at the time of writing this tutorial, the maintainer’s signature is no longer valid. Therefore, we will install it manually.

Clone the repository and compile the library:

git clone https://github.com/GrapheneOS/hardened_malloc/
cd hardened_malloc
git checkout tags/11 # use the latest version (in this case, version 11)
make # compile hardened_malloc

To ensure there are no issues, increase the number of usable memory maps on the system:

echo 'vm.max_map_count = 1048576' > /etc/sysctl.d/hardened_malloc.conf

Install the library on the system:

cp out/libhardened_malloc.so /usr/local/lib/libhardened_malloc.so

Ensure that hardened_malloc is loaded for each program:

echo '/usr/local/lib/libhardened_malloc.so' >> /etc/ld.so.preload

Congratulations! You have installed hardened_malloc on your system. To verify, you can run a program and check if libhardened_malloc.so is being loaded:

cat /proc/<PID>/maps | grep hardened_malloc