mirror of
https://github.com/linuxboot/heads.git
synced 2025-01-21 20:08:03 +00:00
d07df1e60b
Since it's not supposed to be shell safe, just display safe inside double quotes, we can allow some more characters. Also fix the escape character not being escaped.
529 lines
13 KiB
Bash
Executable File
529 lines
13 KiB
Bash
Executable File
#!/bin/sh
|
|
# Shell functions for most initialization scripts
|
|
|
|
die() {
|
|
echo >&2 "$*";
|
|
sleep 2;
|
|
exit 1;
|
|
}
|
|
|
|
warn() {
|
|
echo >&2 "$*";
|
|
sleep 1;
|
|
}
|
|
|
|
recovery() {
|
|
echo >&2 "!!!!! $*"
|
|
|
|
# Remove any temporary secret files that might be hanging around
|
|
# but recreate the directory so that new tools can use it.
|
|
|
|
#safe to always be true. Otherwise "set -e" would make it exit here
|
|
shred -n 10 -z -u /tmp/secret/* 2> /dev/null || true
|
|
rm -rf /tmp/secret
|
|
mkdir -p /tmp/secret
|
|
|
|
# ensure /tmp/config exists for recovery scripts that depend on it
|
|
touch /tmp/config
|
|
|
|
if [ "$CONFIG_TPM" = y ]; then
|
|
tpm extend -ix 4 -ic recovery
|
|
fi
|
|
|
|
while [ true ]
|
|
do
|
|
echo >&2 "!!!!! Starting recovery shell"
|
|
sleep 1
|
|
|
|
if [ -x /bin/setsid ]; then
|
|
/bin/setsid -c /bin/ash
|
|
else
|
|
/bin/ash
|
|
fi
|
|
done
|
|
}
|
|
|
|
pause_recovery() {
|
|
read -p 'Hit enter to proceed to recovery shell:'
|
|
recovery $*
|
|
}
|
|
|
|
pcrs() {
|
|
head -8 /sys/class/tpm/tpm0/pcrs
|
|
}
|
|
|
|
confirm_totp()
|
|
{
|
|
prompt="$1"
|
|
last_half=X
|
|
unset totp_confirm
|
|
|
|
while true; do
|
|
|
|
# update the TOTP code every thirty seconds
|
|
date=`date "+%Y-%m-%d %H:%M:%S"`
|
|
seconds=`date "+%s"`
|
|
half=`expr \( $seconds % 60 \) / 30`
|
|
if [ "$CONFIG_TPM" != y ]; then
|
|
TOTP="NO TPM"
|
|
elif [ "$half" != "$last_half" ]; then
|
|
last_half=$half;
|
|
TOTP=`unseal-totp` \
|
|
|| recovery "TOTP code generation failed"
|
|
fi
|
|
|
|
echo -n "$date $TOTP: "
|
|
|
|
# read the first character, non-blocking
|
|
read \
|
|
-t 1 \
|
|
-n 1 \
|
|
-s \
|
|
-p "$prompt" \
|
|
totp_confirm \
|
|
&& break
|
|
|
|
# nothing typed, redraw the line
|
|
echo -ne '\r'
|
|
done
|
|
|
|
# clean up with a newline
|
|
echo
|
|
}
|
|
|
|
enable_usb()
|
|
{
|
|
if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then
|
|
if ! lsmod | grep -q uhci_hcd; then
|
|
insmod /lib/modules/uhci-hcd.ko \
|
|
|| die "uhci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q ohci_hcd; then
|
|
insmod /lib/modules/ohci-hcd.ko \
|
|
|| die "ohci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q ohci_pci; then
|
|
insmod /lib/modules/ohci-pci.ko \
|
|
|| die "ohci_pci: module load failed"
|
|
fi
|
|
fi
|
|
if ! lsmod | grep -q ehci_hcd; then
|
|
insmod /lib/modules/ehci-hcd.ko \
|
|
|| die "ehci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q ehci_pci; then
|
|
insmod /lib/modules/ehci-pci.ko \
|
|
|| die "ehci_pci: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q xhci_hcd; then
|
|
insmod /lib/modules/xhci-hcd.ko \
|
|
|| die "xhci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q xhci_pci; then
|
|
insmod /lib/modules/xhci-pci.ko \
|
|
|| die "xhci_pci: module load failed"
|
|
sleep 2
|
|
fi
|
|
|
|
if [ "$CONFIG_USB_KEYBOARD" = y ]; then
|
|
if ! lsmod | grep -q usbhid; then
|
|
insmod /lib/modules/usbhid.ko \
|
|
|| die "usbhid: module load failed"
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
list_usb_storage()
|
|
{
|
|
stat -c %N /sys/block/sd* 2>/dev/null | grep usb |
|
|
cut -f1 -d ' ' |
|
|
sed "s/[']//g" |
|
|
while read b; do
|
|
# Ignore devices of size 0, such as empty SD card
|
|
# readers on laptops attached via USB.
|
|
if [ "$(cat "$b/size")" -gt 0 ]; then
|
|
echo "$b"
|
|
fi
|
|
done |
|
|
sed "s|/sys/block|/dev|" |
|
|
while read b; do
|
|
# If the device has a partition table, ignore it and
|
|
# include the partitions instead - even if the kernel
|
|
# hasn't detected the partitions yet. Such a device is
|
|
# never usable directly, and this allows the "wait for
|
|
# disks" loop in mount-usb to correctly wait for the
|
|
# partitions.
|
|
if fdisk -l "$b" | grep -q "doesn't contain a valid partition table"; then
|
|
# No partition table, include this device
|
|
echo "$b"
|
|
else
|
|
# Has a partition table, include partitions
|
|
ls -1 "$b"* | awk 'NR!=1 {print $0}'
|
|
fi
|
|
done
|
|
}
|
|
|
|
confirm_gpg_card()
|
|
{
|
|
read \
|
|
-n 1 \
|
|
-p "Please confirm that your GPG card is inserted [Y/n]: " \
|
|
card_confirm
|
|
echo
|
|
|
|
if [ "$card_confirm" != "y" \
|
|
-a "$card_confirm" != "Y" \
|
|
-a -n "$card_confirm" ] \
|
|
; then
|
|
die "gpg card not confirmed"
|
|
fi
|
|
|
|
# setup the USB so we can reach the GPG card
|
|
enable_usb
|
|
|
|
echo -e "\nVerifying presence of GPG card...\n"
|
|
# ensure we don't exit without retrying
|
|
errexit=$(set -o | grep errexit | awk '{print $2}')
|
|
set +e
|
|
gpg --card-status > /dev/null
|
|
if [ $? -ne 0 ]; then
|
|
# prompt for reinsertion and try a second time
|
|
read -n1 -r -p \
|
|
"Can't access GPG key; remove and reinsert, then press Enter to retry. " \
|
|
ignored
|
|
# restore prev errexit state
|
|
if [ "$errexit" = "on" ]; then
|
|
set -e
|
|
fi
|
|
# retry card status
|
|
gpg --card-status > /dev/null \
|
|
|| die "gpg card read failed"
|
|
fi
|
|
# restore prev errexit state
|
|
if [ "$errexit" = "on" ]; then
|
|
set -e
|
|
fi
|
|
}
|
|
|
|
|
|
check_tpm_counter()
|
|
{
|
|
LABEL=${2:-3135106223}
|
|
# if the /boot.hashes file already exists, read the TPM counter ID
|
|
# from it.
|
|
if [ -r "$1" ]; then
|
|
TPM_COUNTER=`grep counter- "$1" | cut -d- -f2`
|
|
else
|
|
warn "$1 does not exist; creating new TPM counter"
|
|
read -s -p "TPM Owner password: " tpm_password
|
|
echo
|
|
tpm counter_create \
|
|
-pwdo "$tpm_password" \
|
|
-pwdc '' \
|
|
-la $LABEL \
|
|
| tee /tmp/counter \
|
|
|| die "Unable to create TPM counter"
|
|
TPM_COUNTER=`cut -d: -f1 < /tmp/counter`
|
|
fi
|
|
|
|
if [ -z "$TPM_COUNTER" ]; then
|
|
die "$1: TPM Counter not found?"
|
|
fi
|
|
}
|
|
|
|
read_tpm_counter()
|
|
{
|
|
tpm counter_read -ix "$1" | tee "/tmp/counter-$1" \
|
|
|| die "Counter read failed"
|
|
}
|
|
|
|
increment_tpm_counter()
|
|
{
|
|
tpm counter_increment -ix "$1" -pwdc '' \
|
|
| tee /tmp/counter-$1 \
|
|
|| die "Counter increment failed"
|
|
}
|
|
|
|
check_config() {
|
|
if [ ! -d /tmp/kexec ]; then
|
|
mkdir /tmp/kexec \
|
|
|| die 'Failed to make kexec tmp dir'
|
|
else
|
|
rm -rf /tmp/kexec/* \
|
|
|| die 'Failed to empty kexec tmp dir'
|
|
fi
|
|
|
|
if [ ! -r $1/kexec.sig ]; then
|
|
return
|
|
fi
|
|
|
|
if [ `find $1/kexec*.txt | wc -l` -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
if [ "$2" != "force" ]; then
|
|
if ! sha256sum `find $1/kexec*.txt` | gpgv $1/kexec.sig - ; then
|
|
die 'Invalid signature on kexec boot params'
|
|
fi
|
|
fi
|
|
|
|
echo "+++ Found verified kexec boot params"
|
|
cp $1/kexec*.txt /tmp/kexec \
|
|
|| die "Failed to copy kexec boot params to tmp"
|
|
}
|
|
|
|
preserve_rom() {
|
|
new_rom="$1"
|
|
old_files=`cbfs -t 50 -l 2>/dev/null | grep "^heads/"`
|
|
|
|
for old_file in `echo $old_files`; do
|
|
new_file=`cbfs.sh -o $1 -l | grep -x $old_file`
|
|
if [ -z "$new_file" ]; then
|
|
echo "+++ Adding $old_file to $1"
|
|
cbfs -t 50 -r $old_file >/tmp/rom.$$ \
|
|
|| die "Failed to read cbfs file from ROM"
|
|
cbfs.sh -o $1 -a $old_file -f /tmp/rom.$$ \
|
|
|| die "Failed to write cbfs file to new ROM file"
|
|
fi
|
|
done
|
|
}
|
|
replace_config() {
|
|
CONFIG_FILE=$1
|
|
CONFIG_OPTION=$2
|
|
NEW_SETTING=$3
|
|
|
|
touch $CONFIG_FILE
|
|
# first pull out the existing option from the global config and place in a tmp file
|
|
awk "gsub(\"^export ${CONFIG_OPTION}=.*\",\"export ${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config > ${CONFIG_FILE}.tmp
|
|
awk "gsub(\"^${CONFIG_OPTION}=.*\",\"${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >> ${CONFIG_FILE}.tmp
|
|
|
|
# then copy any remaining settings from the existing config file, minus the option you changed
|
|
grep -v "^export ${CONFIG_OPTION}=" ${CONFIG_FILE} | grep -v "^${CONFIG_OPTION}=" >> ${CONFIG_FILE}.tmp || true
|
|
sort ${CONFIG_FILE}.tmp | uniq > ${CONFIG_FILE}
|
|
rm -f ${CONFIG_FILE}.tmp
|
|
}
|
|
combine_configs() {
|
|
cat /etc/config* > /tmp/config
|
|
}
|
|
|
|
update_checksums()
|
|
{
|
|
# ensure /boot mounted
|
|
if ! grep -q /boot /proc/mounts ; then
|
|
mount -o ro /boot \
|
|
|| recovery "Unable to mount /boot"
|
|
fi
|
|
|
|
# remount RW
|
|
mount -o rw,remount /boot
|
|
|
|
# sign and auto-roll config counter
|
|
extparam=
|
|
if [ "$CONFIG_TPM" = "y" ]; then
|
|
extparam=-r
|
|
fi
|
|
if ! kexec-sign-config -p /boot -u $extparam ; then
|
|
rv=1
|
|
else
|
|
rv=0
|
|
fi
|
|
|
|
# switch back to ro mode
|
|
mount -o ro,remount /boot
|
|
|
|
return $rv
|
|
}
|
|
|
|
print_tree() {
|
|
find ./ ! -path './kexec*' -print0 | sort -z
|
|
}
|
|
|
|
# Escape zero-delimited standard input to safely display it to the user in e.g.
|
|
# `whiptail`, `less`, `echo`, `cat`. Doesn't produce shell-escaped output.
|
|
# Most printable characters are passed verbatim (exception: \).
|
|
# These escapes are used to replace their corresponding characters: #n#r#t#v#b
|
|
# Other characters are rendered as hexadecimal escapes.
|
|
# escape_zero [prefix] [escape character]
|
|
# prefix: \0 in the input will result in \n[prefix]
|
|
# escape character: character to use for escapes (default: #); \ may be interpreted by `whiptail`
|
|
escape_zero() {
|
|
local prefix="$1"
|
|
local echar="${2:-#}"
|
|
local todo=""
|
|
local echar_hex="$(echo -n "$echar" | xxd -p -c1)"
|
|
[ ${#echar_hex} -eq 2 ] || die "Invalid escape character $echar passed to escape_zero(). Programming error?!"
|
|
|
|
echo -e -n "$prefix"
|
|
xxd -p -c1 | tr -d '\n' |
|
|
{
|
|
while IFS= read -r -n2 -d '' ; do
|
|
if [ -n "$todo" ] ; then
|
|
#REPLY == " " is EOF
|
|
[[ "$REPLY" == " " ]] && echo '' || echo -e -n "$todo"
|
|
todo=""
|
|
fi
|
|
|
|
case "$REPLY" in
|
|
00)
|
|
todo="\n$prefix"
|
|
;;
|
|
08)
|
|
echo -n "${echar}b"
|
|
;;
|
|
09)
|
|
echo -n "${echar}t"
|
|
;;
|
|
0a)
|
|
echo -n "${echar}n"
|
|
;;
|
|
0b)
|
|
echo -n "${echar}v"
|
|
;;
|
|
0d)
|
|
echo -n "${echar}r"
|
|
;;
|
|
"$echar_hex")
|
|
echo -n "$echar$echar"
|
|
;;
|
|
#interpreted characters:
|
|
2[0-9a-f]|3[0-9a-f]|4[0-9a-f]|5[0-9abd-f]|6[0-9a-f]|7[0-9a-e])
|
|
echo -e -n '\x'"$REPLY"
|
|
;;
|
|
# All others are escaped
|
|
*)
|
|
echo -n "${echar}x$REPLY"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
}
|
|
|
|
# Currently heads doesn't support signing file names with certain characters
|
|
# due to https://bugs.busybox.net/show_bug.cgi?id=14226. Also, certain characters
|
|
# may be intepreted by `whiptail`, `less` et al (e.g. \n, \b, ...).
|
|
assert_signable() {
|
|
# ensure /boot mounted
|
|
if ! grep -q /boot /proc/mounts ; then
|
|
mount -o ro /boot || die "Unable to mount /boot"
|
|
fi
|
|
|
|
find /boot -print0 > /tmp/signable.ref
|
|
local del='\001-\037\134\177-\377'
|
|
LC_ALL=C tr -d "$del" < /tmp/signable.ref > /tmp/signable.del || die "Failed to execute tr."
|
|
if ! cmp -s "/tmp/signable.ref" "/tmp/signable.del" &> /dev/null ; then
|
|
local user_out="/tmp/hash_output_mismatches"
|
|
local add="Please investigate!"
|
|
[ -f "$user_out" ] && add="Please investigate the following relative paths to /boot (where # are sanitized invalid characters):"$'\n'"$(cat "$user_out")"
|
|
recovery "Some /boot file names contain characters that are currently not supported by heads: $del"$'\n'"$add"
|
|
fi
|
|
rm -f /tmp/signable.*
|
|
}
|
|
|
|
verify_checksums()
|
|
{
|
|
local boot_dir="$1"
|
|
local gui="${2:-y}"
|
|
|
|
(
|
|
set +e -o pipefail
|
|
local ret=0
|
|
cd "$boot_dir" || ret=1
|
|
sha256sum -c "$TMP_HASH_FILE" > /tmp/hash_output || ret=1
|
|
|
|
# also make sure that the file & directory structure didn't change
|
|
# (sha256sum won't detect added files)
|
|
print_tree > /tmp/tree_output || ret=1
|
|
if ! cmp -s "$TMP_TREE_FILE" /tmp/tree_output &> /dev/null ; then
|
|
ret=1
|
|
[[ "$gui" != "y" ]] && exit "$ret"
|
|
# produce a diff that can safely be presented to the user
|
|
# this is relatively hard as file names may e.g. contain backslashes etc.,
|
|
# which are interpreted by whiptail, less, ...
|
|
escape_zero "(new) " < "$TMP_TREE_FILE" > "${TMP_TREE_FILE}.user"
|
|
escape_zero "(new) " < /tmp/tree_output > /tmp/tree_output.user
|
|
diff "${TMP_TREE_FILE}.user" /tmp/tree_output.user | grep -E '^\+\(new\).*$' | sed -r 's/^\+\(new\)/(new)/g' >> /tmp/hash_output
|
|
rm -f "${TMP_TREE_FILE}.user"
|
|
rm -f /tmp/tree_output.user
|
|
fi
|
|
exit $ret
|
|
)
|
|
return $?
|
|
}
|
|
|
|
# detect and set /boot device
|
|
# mount /boot if successful
|
|
detect_boot_device()
|
|
{
|
|
# unmount /boot to be safe
|
|
cd / && umount /boot 2>/dev/null
|
|
|
|
# check $CONFIG_BOOT_DEV if set/valid
|
|
if [ -e "$CONFIG_BOOT_DEV" ]; then
|
|
if mount -o ro $CONFIG_BOOT_DEV /boot >/dev/null 2>&1; then
|
|
if ls -d /boot/grub* >/dev/null 2>&1; then
|
|
# CONFIG_BOOT_DEV is valid device and contains an installed OS
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# generate list of possible boot devices
|
|
fdisk -l | grep "Disk /dev/" | cut -f2 -d " " | cut -f1 -d ":" > /tmp/disklist
|
|
|
|
# filter out extraneous options
|
|
> /tmp/boot_device_list
|
|
for i in `cat /tmp/disklist`; do
|
|
# remove block device from list if numeric partitions exist, since not bootable
|
|
DEV_NUM_PARTITIONS=$((`ls -1 $i* | wc -l`-1))
|
|
if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then
|
|
echo $i >> /tmp/boot_device_list
|
|
else
|
|
ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp/boot_device_list
|
|
fi
|
|
done
|
|
|
|
# iterate thru possible options and check for grub dir
|
|
for i in `cat /tmp/boot_device_list`; do
|
|
umount /boot 2>/dev/null
|
|
if mount -o ro $i /boot >/dev/null 2>&1; then
|
|
if ls -d /boot/grub* >/dev/null 2>&1; then
|
|
CONFIG_BOOT_DEV="$i"
|
|
return 0
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# no valid boot device found
|
|
echo "Unable to locate /boot files on any mounted disk"
|
|
umount /boot 2>/dev/null
|
|
return 1
|
|
}
|
|
|
|
calc()
|
|
{
|
|
awk "BEGIN { print "$*" }";
|
|
}
|
|
|
|
print_battery_health()
|
|
{
|
|
if [ -d /sys/class/power_supply/BAT* ]; then
|
|
battery_health=$(calc $(cat /sys/class/power_supply/BAT*/charge_full)/$(cat /sys/class/power_supply/BAT*/charge_full_design)*100 | awk -F "." {'print $1'})
|
|
echo "$battery_health"
|
|
fi
|
|
}
|
|
|
|
print_battery_charge()
|
|
{
|
|
if [ -d /sys/class/power_supply/BAT* ]; then
|
|
battery_charge=$(calc $(cat /sys/class/power_supply/BAT*/charge_now)/$(cat /sys/class/power_supply/BAT*/charge_full)*100 | awk -F "." {'print $1'})
|
|
echo "$battery_charge"
|
|
fi
|
|
}
|
|
|
|
generate_random_mac_address()
|
|
{
|
|
#Borrowed from https://stackoverflow.com/questions/42660218/bash-generate-random-mac-address-unicast
|
|
hexdump -n 6 -ve '1/1 "%.2x "' /dev/urandom | awk -v a="2,6,a,e" -v r="$RANDOM" 'BEGIN{srand(r);}NR==1{split(a,b,",");r=int(rand()*4+1);printf "%s%s:%s:%s:%s:%s:%s\n",substr($1,0,1),b[r],$2,$3,$4,$5,$6}'
|
|
}
|