Sunday, August 4, 2024

arch linux uefi with dm-crypt and uki

Arch Linux is known for its high level of customization, and configuring LUKS2 and LVM is a straightforward process. This guide provides a set of instructions for setting up an Arch Linux system with the following features:

  • Root file system encryption using LUKS2.
  • Logical Volume Management (LVM) for flexible storage management.
  • Unified Kernel Image (UKI) bootable via UEFI.
  • Optional: Detached LUKS header on external media for enhanced security.

Prerequisites

  • A bootable Arch Linux ISO.
  • An NVMe drive (e.g., /dev/nvme0n1).
  • (Optional) A microSD card or other external medium for the detached LUKS header.

Important Considerations

  • Data Loss: The following procedure will erase all data on the target drive. Back up any important data before proceeding.
  • Secure Boot: This guide assumes you may want to use hardware secure boot.
  • Detached LUKS Header: Using a detached LUKS header on external media adds a significant layer of security. If you lose the external media, you will lose access to your encrypted data.
  • Swap: This guide uses a swap file. You may also use a swap partition if desired.

Step-by-Step Instructions

  1. Boot into the Arch Linux ISO:

    Boot your system from the Arch Linux installation media.

  2. Set the System Clock:

    # timedatectl set-ntp true
  3. Prepare the Disk:

    • Identify your NVMe drive (e.g., /dev/nvme0n1). Use lsblk to confirm.
    • Wipe the drive:
    • # wipefs --all /dev/nvme0n1
    • Create an EFI System Partition (ESP):
    • # sgdisk /dev/nvme0n1 -n 1::+512MiB -t 1:EF00
    • Create a partition for the encrypted volume:
    • # sgdisk /dev/nvme0n1 -n 2 -t 2:8300
  4. Set up LUKS2 Encryption:

    Encrypt the second partition using LUKS2. This example uses aes-xts-plain64 and serpent-xts-plain ciphers, and SHA512 for the hash. Adjust as needed.

    # cryptsetup luksFormat --cipher aes-xts-plain64 \
      --keyslot-cipher serpent-xts-plain --keyslot-key-size 512 \
      --use-random -S 0 -h sha512 -i 4000 /dev/nvme0n1p2
    • --cipher: Specifies the cipher for data encryption.
    • --keyslot-cipher: Specifies the cipher used to encrypt the key.
    • --keyslot-key-size: Specifies the size of the key slot.
    • -S 0: Disables sparse headers.
    • -h: Specifies the hash function.
    • -i: Specifies the number of iterations.

    Open the encrypted partition:

    # cryptsetup open /dev/nvme0n1p2 root
  5. Create the File Systems and Mount:

    Create an ext4 file system on the decrypted volume:

    # mkfs.ext4 /dev/mapper/root

    Mount the root file system:

    # mount /dev/mapper/root /mnt

    Create and mount the EFI System Partition:

    # mkfs.fat -F32 /dev/nvme0n1p1
    # mount --mkdir /dev/nvme0n1p1 /mnt/efi

    Create and enable a swap file:

    # dd if=/dev/zero of=/mnt/swapfile bs=1M count=8000 status=progress
    # chmod 600 /mnt/swapfile
    # mkswap /mnt/swapfile
    # swapon /mnt/swapfile
  6. Install the Base System:

    Use pacstrap to install the necessary packages:

    # pacstrap -K /mnt base base-devel linux linux-hardened \
      linux-hardened-headers linux-firmware apparmor mesa \
      xf86-video-intel vulkan-intel git vi vim ukify
  7. Generate the fstab File:

    # genfstab -U /mnt >> /mnt/etc/fstab
  8. Chroot into the New System:

    # arch-chroot /mnt
  9. Configure the System:

    Set the timezone:

    # ln -sf /usr/share/zoneinfo/UTC /etc/localtime
    # hwclock --systohc

    Uncomment en_US.UTF-8 UTF-8 in /etc/locale.gen and generate the locale:

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

    Set the hostname:

    # echo myhostname > /etc/hostname
    # cat <<EOT >> /etc/hosts
    127.0.0.1 myhostname
    ::1 localhost
    127.0.1.1 myhostname.localdomain myhostname
    EOT

    Configure mkinitcpio.conf to include the encrypt hook:

    # sed -i 's/HOOKS.*/HOOKS=(base udev autodetect modconf kms \
      keyboard keymap consolefont block encrypt filesystems resume fsck)/' \
      /etc/mkinitcpio.conf

    Create the initial ramdisk:

    # mkinitcpio -P

    Install the bootloader:

    # bootctl install

    Set the root password:

    # passwd

    Install microcode and efibootmgr:

    # pacman -S intel-ucode efibootmgr

    Get the swap offset:

    # swapoffset=`filefrag -v /swapfile | awk '/\s+0:/ {print $4}' | \
      sed -e 's/\.\.$//'`

    Get the UUID of the encrypted partition:

    # blkid -s UUID -o value /dev/nvme0n1p2

    Create the EFI boot entry. Replace <UUID OF CRYPTDEVICE> with the actual UUID:

    # efibootmgr --disk /dev/nvme0n1p1 --part 1 --create --label "Linux" \
      --loader /vmlinuz-linux --unicode "cryptdevice=UUID=<UUID OF CRYPTDEVICE>:root \
      root=/dev/mapper/root resume=/dev/mapper/root resume_offset=$swapoffset \
      rw initrd=\intel-ucode.img initrd=\initramfs-linux.img" --verbose

    Configure the UKI presets:

    # cat <<EOT >> /etc/mkinitcpio.d/linux.preset
    ALL_kver="/boot/vmlinuz-linux"
    ALL_microcode=(/boot/*-ucode.img)
    PRESETS=('default' 'fallback')
    default_uki="/efi/EFI/Linux/arch-linux.efi"
    default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
    fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
    fallback_options="-S autodetect"
    EOT

    Create the UKI directory:

    # mkdir -p /efi/EFI/Linux

    Configure the kernel command line:

    # cat <<EOT >> /etc/kernel/cmdline
    cryptdevice=UUID=<UUID OF CRYPTDEVICE>:root root=/dev/mapper/root \
    resume=/dev/mapper/root resume_offset=51347456 rw
    EOT

    Build the UKIs:

    # mkinitcpio -p linux

    Configure the kernel install layout:

    # echo "layout=uki" >> /etc/kernel/install.conf
  10. Configure Networking (Optional):

    Create a systemd-networkd network configuration file:

    # cat <<EOT >> /etc/systemd/network/nic0.network
    [Match]
    Name=nic0
    [Network]
    DHCP=yes
    EOT
  11. Install a Desktop Environment (Optional):

    Install Xorg, Xfce, LightDM, and related packages:

    # pacman -Syu
    # pacman -S xorg xfce4 xfce4-goodies lightdm lightdm-gtk-greeter \
      libva-intel-driver mesa xorg-server xorg-xinit sudo
    # systemctl enable lightdm
    # systemctl start lightdm
  12. Enable Network Services (Optional):

    # systemctl enable systemd-resolved.service
    # systemctl enable systemd-networkd.service
    # systemctl start systemd-resolved.service
    # systemctl start systemd-networkd.service
  13. Create a User Account:

    Create a user account and add it to the wheel group:

    # useradd -m -g wheel -s /bin/bash myusername
  14. Reboot:

    Exit the chroot environment and reboot your system:

    # exit
    # umount -R /mnt
    # reboot

Saturday, April 6, 2024

Multidimensional arrays of function pointers in C

Embedded hardware typically includes an application processor and one or more adjacent processor(s) attached to the printed circuit board. The firmware that resides on the adjacent processor(s) responds to instructions or commands.  Different processors on the same board are often produced by different companies.  For the system to function properly, it is imperative that the processors communicate without any issues, and that the firmware can handle all types of possible errors.

Formal requirements for firmware related projects may include the validation and verification of the firmware on a co-processor via the application programming interface (API).  Co-processors typically run 8, 16, or 32-bit embedded operating systems.  If the co-processor manufacturer provides a development board for testing the firmware on a specific co-processor, then the development board may have it's own application processor. Familiarity with all of the applicable bus communication protocols including synchronous and asynchronous communication is important.  High-volume testing of firmware can be accomplished using function-like macros and arrays of function pointers.  Processor specific firmware is written in C and assembly - 8, 16, 32, or 64-bit.  Executing inline assembly from C is straightforward and often required.  Furthermore, handling time-constraints such as real-time execution on adjacent processors is easier to deal with in C and executing syscalls, low-level C functions, and userspace library functions, is often more efficient.  Timing analysis is often a key consideration when testing firmware, and executing compiled C code on a time-sliced OS, such as Linux, is already constrained.

To read tests based on a custom grammar, a scanner and parser in C can be used. Lex is ideal for building a computationally efficient lexical analyzer that outputs a sequence of tokens. For this case, the tokens comprise the function signatures and any associated function metadata such as expected execution time. Creating a context-free grammar and generating the associated syntax tree from the lexical input is straightforward.   Dynamic arrays of function pointers can then be allocated at run-time, and code within external object files or libraries can be executed in parallel using multiple processes or threads. The symbol table information from those files can be stored in multi-dimensional arrays. While C is a statically typed language, the above design can be used for executing generic, variadic functions at run-time from tokenized input, with constant time lookup, minimal overhead, and specific run-time expectations (stack return value, execution time, count, etc.).

At a high level, lists of pointers to type-independent, variadic functions and their associated parameters can be stored within multi-dimensional arrays.  The following C code uses arrays of function pointers to execute functions via their addresses.  The code uses list management functions from the Linux kernel which I ported to userspace.

https://github.com/brhinton/bcn