From a6788be01a3f942b10566e30fa5c7be380f20898 Mon Sep 17 00:00:00 2001 From: Naïm Favier Date: Wed, 26 May 2021 15:43:38 +0200 Subject: nixos/luksroot: add bypassWorkqueues (#118114) https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance --- nixos/modules/system/boot/luksroot.nix | 107 +++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 44 deletions(-) (limited to 'nixos/modules/system/boot/luksroot.nix') diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index fa14d86e253..8c82b4bcf5d 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -140,24 +140,27 @@ let umount /crypt-ramfs 2>/dev/null ''; - openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, preOpenCommands, postOpenCommands,... }: assert name' == name; + openCommand = name: dev: assert name == dev.name; let - csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; - cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; + csopen = "cryptsetup luksOpen ${dev.device} ${dev.name}" + + optionalString dev.allowDiscards " --allow-discards" + + optionalString dev.bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue" + + optionalString (dev.header != null) " --header=${dev.header}"; + cschange = "cryptsetup luksChangeKey ${dev.device} ${optionalString (dev.header != null) "--header=${dev.header}"}"; in '' # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. # if on a USB drive. - wait_target "device" ${device} || die "${device} is unavailable" + wait_target "device" ${dev.device} || die "${dev.device} is unavailable" - ${optionalString (header != null) '' - wait_target "header" ${header} || die "${header} is unavailable" + ${optionalString (dev.header != null) '' + wait_target "header" ${dev.header} || die "${dev.header} is unavailable" ''} do_open_passphrase() { local passphrase while true; do - echo -n "Passphrase for ${device}: " + echo -n "Passphrase for ${dev.device}: " passphrase= while true; do if [ -e /crypt-ramfs/passphrase ]; then @@ -166,7 +169,7 @@ let break else # ask cryptsetup-askpass - echo -n "${device}" > /crypt-ramfs/device + echo -n "${dev.device}" > /crypt-ramfs/device # and try reading it from /dev/console with a timeout IFS= read -t 1 -r passphrase @@ -182,7 +185,7 @@ let fi fi done - echo -n "Verifying passphrase for ${device}..." + echo -n "Verifying passphrase for ${dev.device}..." echo -n "$passphrase" | ${csopen} --key-file=- if [ $? == 0 ]; then echo " - success" @@ -202,13 +205,13 @@ let # LUKS open_normally() { - ${if (keyFile != null) then '' - if wait_target "key file" ${keyFile}; then - ${csopen} --key-file=${keyFile} \ - ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \ - ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"} + ${if (dev.keyFile != null) then '' + if wait_target "key file" ${dev.keyFile}; then + ${csopen} --key-file=${dev.keyFile} \ + ${optionalString (dev.keyFileSize != null) "--keyfile-size=${toString dev.keyFileSize}"} \ + ${optionalString (dev.keyFileOffset != null) "--keyfile-offset=${toString dev.keyFileOffset}"} else - ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable" + ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable" echo " - failing back to interactive password prompt" do_open_passphrase fi @@ -217,7 +220,7 @@ let ''} } - ${optionalString (luks.yubikeySupport && (yubikey != null)) '' + ${optionalString (luks.yubikeySupport && (dev.yubikey != null)) '' # YubiKey rbtohex() { ( od -An -vtx1 | tr -d ' \n' ) @@ -243,16 +246,16 @@ let local new_response local new_k_luks - mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \ + mount -t ${dev.yubikey.storage.fsType} ${dev.yubikey.storage.device} /crypt-storage || \ die "Failed to mount YubiKey salt storage device" - salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')" - iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')" + salt="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 1p | tr -d '\n')" + iterations="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 2p | tr -d '\n')" challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" - response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" + response="$(ykchalresp -${toString dev.yubikey.slot} -x $challenge 2>/dev/null)" for try in $(seq 3); do - ${optionalString yubikey.twoFactor '' + ${optionalString dev.yubikey.twoFactor '' echo -n "Enter two-factor passphrase: " k_user= while true; do @@ -278,9 +281,9 @@ let ''} if [ ! -z "$k_user" ]; then - k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" + k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)" else - k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" + k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)" fi echo -n "$k_luks" | hextorb | ${csopen} --key-file=- @@ -302,7 +305,7 @@ let [ "$opened" == false ] && die "Maximum authentication errors reached" echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." - for i in $(seq ${toString yubikey.saltLength}); do + for i in $(seq ${toString dev.yubikey.saltLength}); do byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; new_salt="$new_salt$byte"; echo -n . @@ -310,25 +313,25 @@ let echo "ok" new_iterations="$iterations" - ${optionalString (yubikey.iterationStep > 0) '' - new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))" + ${optionalString (dev.yubikey.iterationStep > 0) '' + new_iterations="$(($new_iterations + ${toString dev.yubikey.iterationStep}))" ''} new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" - new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)" + new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)" if [ ! -z "$k_user" ]; then - new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" + new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)" else - new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" + new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)" fi echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key if [ $? == 0 ]; then - echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path} + echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path} else echo "Warning: Could not update LUKS key, current challenge persists!" fi @@ -338,7 +341,7 @@ let } open_with_hardware() { - if wait_yubikey ${toString yubikey.gracePeriod}; then + if wait_yubikey ${toString dev.yubikey.gracePeriod}; then do_open_yubikey else echo "No YubiKey found, falling back to non-YubiKey open procedure" @@ -347,7 +350,7 @@ let } ''} - ${optionalString (luks.gpgSupport && (gpgCard != null)) '' + ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) '' do_open_gpg_card() { # Make all of these local to this function @@ -355,12 +358,12 @@ let local pin local opened - gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null + gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null gpg --card-status > /dev/null 2> /dev/null for try in $(seq 3); do - echo -n "PIN for GPG Card associated with device ${device}: " + echo -n "PIN for GPG Card associated with device ${dev.device}: " pin= while true; do if [ -e /crypt-ramfs/passphrase ]; then @@ -382,8 +385,8 @@ let fi fi done - echo -n "Verifying passphrase for ${device}..." - echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null + echo -n "Verifying passphrase for ${dev.device}..." + echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${dev.device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null if [ $? == 0 ]; then echo " - success" ${if luks.reusePassphrases then '' @@ -403,7 +406,7 @@ let } open_with_hardware() { - if wait_gpgcard ${toString gpgCard.gracePeriod}; then + if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then do_open_gpg_card else echo "No GPG Card found, falling back to normal open procedure" @@ -412,15 +415,15 @@ let } ''} - ${optionalString (luks.fido2Support && (fido2.credential != null)) '' + ${optionalString (luks.fido2Support && (dev.fido2.credential != null)) '' open_with_hardware() { local passsphrase - ${if fido2.passwordLess then '' + ${if dev.fido2.passwordLess then '' export passphrase="" '' else '' - read -rsp "FIDO2 salt for ${device}: " passphrase + read -rsp "FIDO2 salt for ${dev.device}: " passphrase echo ''} ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' @@ -428,7 +431,7 @@ let echo "Please move your mouse to create needed randomness." ''} echo "Waiting for your FIDO2 device..." - fido2luks open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase + fido2luks open ${dev.device} ${dev.name} ${dev.fido2.credential} --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase if [ $? -ne 0 ]; then echo "No FIDO2 key found, falling back to normal open procedure" open_normally @@ -437,16 +440,16 @@ let ''} # commands to run right before we mount our device - ${preOpenCommands} + ${dev.preOpenCommands} - ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then '' + ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && (dev.fido2.credential != null)) then '' open_with_hardware '' else '' open_normally ''} # commands to run right after we mounted our device - ${postOpenCommands} + ${dev.postOpenCommands} ''; askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' @@ -621,6 +624,17 @@ in ''; }; + bypassWorkqueues = mkOption { + default = false; + type = types.bool; + description = '' + Whether to bypass dm-crypt's internal read and write workqueues. + Enabling this should improve performance on SSDs; see + here + for more information. Needs Linux 5.9 or later. + ''; + }; + fallbackToPassword = mkOption { default = false; type = types.bool; @@ -833,6 +847,11 @@ in { assertion = !(luks.fido2Support && luks.yubikeySupport); message = "FIDO2 and YubiKey may not be used at the same time."; } + + { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices) + -> versionAtLeast kernelPackages.kernel.version "5.9"; + message = "boot.initrd.luks.devices..bypassWorkqueues is not supported for kernels older than 5.9"; + } ]; # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested -- cgit 1.4.1