Skip to content

Commit de7e630

Browse files
committed
etc/functions: add wait_for_usb_devices helper to fix gpg --card-status failing because race condition without sleep
Signed-off-by: Thierry Laurion <insurgo@riseup.net>
1 parent 882370f commit de7e630

File tree

1 file changed

+90
-3
lines changed

1 file changed

+90
-3
lines changed

initrd/etc/functions

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,16 @@ confirm_gpg_card() {
195195

196196
# setup the USB so we can reach the USB Security dongle's OpenPGP smartcard
197197
enable_usb
198+
# Wait for USB enumeration before accessing GPG card to avoid race condition
199+
wait_for_usb_devices
198200

199201
echo -e "\nVerifying presence of GPG card...\n"
200202
# ensure we don't exit without retrying
201203
errexit=$(set -o | grep errexit | awk '{print $2}')
202204
set +e
203-
gpg_output=$(gpg --card-status 2>&1)
204-
if [ $? -ne 0 ]; then
205+
DEBUG "Attempting gpg card detection (bounded wait)"
206+
if ! wait_for_gpg_card; then
207+
DEBUG "GPG card access failed with output: $gpg_output"
205208
# prompt for reinsertion and try a second time
206209
read -n1 -r -p \
207210
"Can't access GPG key; remove and reinsert, then press Enter to retry. " \
@@ -211,8 +214,10 @@ confirm_gpg_card() {
211214
set -e
212215
fi
213216
# retry card status
214-
gpg_output=$(gpg --card-status 2>&1) ||
217+
DEBUG "Retrying gpg --card-status after reinsertion (bounded wait)"
218+
wait_for_gpg_card ||
215219
die "gpg card read failed"
220+
DEBUG "Retry succeeded"
216221
fi
217222

218223
# Extract and display GPG PIN retry counters
@@ -419,7 +424,89 @@ enable_usb() {
419424
insmod /lib/modules/ehci-pci.ko || die "ehci_pci: module load failed"
420425
insmod /lib/modules/xhci-hcd.ko || die "xhci_hcd: module load failed"
421426
insmod /lib/modules/xhci-pci.ko || die "xhci_pci: module load failed"
427+
}
428+
429+
# Wait for USB bus enumeration to complete after enable_usb() loads modules.
430+
# Uses time-bounded polling (max 2s) to avoid race conditions where device
431+
# nodes haven't been created yet. No hardcoded sleep - checks actual readiness.
432+
# Waits for actual USB peripheral devices (e.g., 1-1, 5-3), not just hubs/controllers.
433+
wait_for_usb_devices() {
434+
TRACE_FUNC
435+
if [ ! -d /sys/bus/usb/devices ] || [ ! -r /proc/uptime ]; then
436+
DEBUG "USB sysfs or uptime not available, skipping wait"
437+
return
438+
fi
439+
440+
local start now elapsed
441+
start=$(awk '{print $1}' /proc/uptime)
442+
DEBUG "Waiting for USB peripheral devices (not just hubs) - max 2s timeout"
443+
444+
local iteration=0
445+
while :; do
446+
iteration=$((iteration + 1))
447+
448+
# Check for actual USB peripheral devices (format: bus-port like 1-1, 5-3)
449+
# Root hubs are named usb1, usb2, etc. - we want devices downstream from them
450+
# Pattern: /sys/bus/usb/devices/[0-9]*-[0-9]*/idVendor (e.g., 1-1, 5-3.2)
451+
local peripheral_count=0
452+
if [ -d /sys/bus/usb/devices ]; then
453+
# Count devices matching bus-port pattern (not usb* root hubs)
454+
for dev in /sys/bus/usb/devices/*-*/idVendor; do
455+
if [ -r "$dev" ]; then
456+
peripheral_count=$((peripheral_count + 1))
457+
fi
458+
done
459+
fi
460+
461+
now=$(awk '{print $1}' /proc/uptime)
462+
elapsed=$(awk -v s="$start" -v n="$now" 'BEGIN{printf "%.3f", n - s}')
463+
464+
if [ $peripheral_count -gt 0 ]; then
465+
DEBUG "USB peripheral devices ready after ${elapsed}s (iteration $iteration): found $peripheral_count device(s)"
466+
return
467+
fi
468+
469+
# Timeout after 2 seconds
470+
if awk -v s="$start" -v n="$now" 'BEGIN{exit (n - s > 2.0) ? 0 : 1}'; then
471+
DEBUG "USB wait timeout at ${elapsed}s (iter $iteration): only found $peripheral_count peripheral device(s)"
472+
return
473+
fi
474+
done
475+
}
422476

477+
# Wait for gpg --card-status to succeed (bounded, no sleep).
478+
# Sets global gpg_output with the last command output.
479+
wait_for_gpg_card() {
480+
TRACE_FUNC
481+
if [ ! -r /proc/uptime ]; then
482+
gpg_output=$(gpg --card-status 2>&1)
483+
return $?
484+
fi
485+
486+
local start now elapsed
487+
start=$(awk '{print $1}' /proc/uptime)
488+
local attempt=0
489+
while :; do
490+
attempt=$((attempt + 1))
491+
gpg_output=$(gpg --card-status 2>&1)
492+
if [ $? -eq 0 ]; then
493+
now=$(awk '{print $1}' /proc/uptime)
494+
elapsed=$(awk -v s="$start" -v n="$now" 'BEGIN{printf "%.3f", n - s}')
495+
DEBUG "gpg --card-status succeeded after ${elapsed}s (attempt $attempt)"
496+
return 0
497+
fi
498+
499+
now=$(awk '{print $1}' /proc/uptime)
500+
elapsed=$(awk -v s="$start" -v n="$now" 'BEGIN{printf "%.3f", n - s}')
501+
if awk -v s="$start" -v n="$now" 'BEGIN{exit (n - s > 2.0) ? 0 : 1}'; then
502+
DEBUG "gpg --card-status timeout at ${elapsed}s (attempt $attempt)"
503+
return 1
504+
fi
505+
done
506+
}
507+
508+
enable_usb_keyboard() {
509+
TRACE_FUNC
423510
# For resiliency, test CONFIG_USB_KEYBOARD_REQUIRED explicitly rather
424511
# than having it imply CONFIG_USER_USB_KEYBOARD at build time.
425512
# Otherwise, if a user got CONFIG_USER_USB_KEYBOARD=n in their

0 commit comments

Comments
 (0)