postmarketos-initramfs: debug-shell 3.0 (MR 5000)

This incorprates the debug-shell functionality directly into the
initramfs, so it's no longer necessary to build and boot a custom
initramfs in order to debug your device.

Additionally, the behaviour of the debug-shell is entirely reworked, It
now creates an ACM serial gadget which can be accessed via any normal
terminal emulator (picocom, minicom, etc; or PuTTY on windows). Rather
than just invoking sh, the debug-shell now creates a respawning getty on
both the new virtual console and the active console (this will either be
the UART console or tty0/1).

It is necessary to spawn these shells via getty since the logging rework
means we can no longer assume that stdin/out/err reference a TTY.

In addition to the above, it is now possible to trigger a log dump by
holding volume up during boot. This can be useful for helping users
debug their devices if the issue doesn't result in a failure that can be
detected in the initramfs.

With these changes, the console-shell and debug-shell hook packages are
reduced to only adding the additional tools/features. console-shell is
still required for fbkeyboard, and debug-shell for the setup_usb_storage
tool.

Co-Developed-by: Clayton Craft <craftyguy@postmarketos.org>
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-04-10 03:28:08 +02:00 committed by Clayton Craft
parent f66238f045
commit dd28314ba2
No known key found for this signature in database
GPG key ID: 4A4CED6D7EDF950A
9 changed files with 285 additions and 128 deletions

View file

@ -21,7 +21,6 @@ sh_files="
./main/postmarketos-installkernel/installkernel-pmos
./main/postmarketos-initramfs/init.sh
./main/postmarketos-initramfs/init_functions.sh
./main/postmarketos-mkinitfs-hook-debug-shell/20-debug-shell.sh
./main/postmarketos-mkinitfs-hook-debug-shell/setup_usb_storage.sh
./main/postmarketos-mkinitfs-hook-netboot/netboot.sh
./main/ttyescape/*.post-install

View file

@ -5,6 +5,7 @@
/etc/unudhcpd.conf
/lib/mdev/persistent-storage
/sbin/blkid
/usr/bin/iskey
/usr/bin/unudhcpd
/usr/sbin/kpartx
/usr/share/deviceinfo/deviceinfo

View file

@ -1,7 +1,7 @@
# Maintainer: Oliver Smith <ollieparanoid@postmarketos.org>
# Co-Maintainer: Clayton Craft <clayton@craftyguy.net>
pkgname=postmarketos-initramfs
pkgver=2.7.1
pkgver=3.0.0
pkgrel=0
pkgdesc="Base files for the postmarketOS initramfs / initramfs-extra"
url="https://postmarketos.org"
@ -19,6 +19,7 @@ depends="
e2fsprogs
e2fsprogs-extra
f2fs-tools
iskey
lz4
mdevd
mdev-conf
@ -89,10 +90,10 @@ package() {
sha512sums="
59be0649ed87a72d93624bd8a2e3f8c99a0f32f7b7a26f99436de782beba55671472c269eeee86440efc87e0d7148a0bb335fa537791092e73878ca21330544a 00-default.modules
9c0e8f6f61d5da191e03a1aa9d5d0ceb5baf1eae6dbb9bfb0af59817783525119ac8394b135f303f7b6434a3eab0b49185fb90379e06823db847a4999c75ce33 00-initramfs-base.dirs
ab41b45b0613f25a61114ed8c8b92bc53c60838f6e2e0ba18c76e5369b2984e6023a0661887692673aca3f647f268c468a468f6b1ac424cfee609017a89481dd 00-initramfs-base.files
0fed7dfdf940f5de15ca2fa636214d44ee8549afe5001b81beb0e337e865eb737916e158bc0ed7a7803adc81176386ae8edfc21150de62bb136fdfcdcb888b8b 00-initramfs-base.files
8a4adad3785af474b36a09a05f6a3b2c4b4f43aac331a53b903abfa51ea12be1e3d1d807b7a6e66a1346815f3b0044daf8cd62e21e2dc75d2db13ee265a72985 00-initramfs-extra-base.files
6bda477e9898420d4d9ef09a0abe4660f9d6bb483982df768e7142c94a14110fa1eee613c6cd12df5387250ce6ceee3061e7fffeac789a652ed0ac69a5e57f58 init.sh
cd2405db17311ee31ac0b8c968a916ec2bb7919b38c56269d01229a4d7a82559e4abd7199d66954c8fde70713b5458b4b4656da8f713e15375864b1b256c5e4f init_functions.sh
a993c8d3dadf54c7ef942c726f8e238194cf7ad8f9d8d1c43991f1ac9d5f4ec57a72f8005b122ee06bcf5b6fdf22a635cdb234aee2173d073196f7a6f7788ffe init.sh
817c58403af779401932758d70a7c84d6d76a457b60327c1913e1758b66e6dbb19c4fcea8f0d4268e07f86f1b041e8fba47f9b01543c732c84d7c13cca1ff622 init_functions.sh
ba3275a9af788c7c782322a22a0f144d5e50e3498ea6886486a29331f23ae89cd32d500a3635cfa7cab369afba92edc18aeca64ccbf0cd589061cce23d15b46c unudhcpd.conf
675e7d5bee39b2df7d322117f8dcaccc274d61beaf4d50ead19bbf2109446d64b1c0aa0c5b4f9846eb6c1c403418f28f6364eff4537ba41120fbfcbc484b7da7 mdev.conf
"

View file

@ -46,12 +46,15 @@ if [ "$IN_CI" = "true" ]; then
fail_halt_boot
fi
# Always run dhcp daemon/usb networking for now (later this should only
# be enabled, when having the debug-shell hook installed for debugging,
# or get activated after the initramfs is done with an OpenRC service).
setup_usb_network
start_unudhcpd
if grep -q "pmos.debug-shell" /proc/cmdline; then
debug_shell
fi
check_keys
mount_subpartitions
wait_boot_partition
@ -91,14 +94,29 @@ setup_bootchart2
# Switch root
run_hooks /hooks-cleanup
# Restore stdout and stderr to their original values
echo "Switching root"
# Restore stdout and stderr to their original values if they
# were stashed
if [ -e "/proc/1/fd/3" ]; then
exec 1>&3 2>&4
elif ! grep -q "pmos.debug-shell" /proc/cmdline; then
echo "$LOG_PREFIX Disabling console output again (use 'pmos.debug-shell' to keep it enabled)"
exec >/dev/null 2>&1
fi
# Re-enable kmsg ratelimiting (might have been disabled for logging)
echo ratelimit > /proc/sys/kernel/printk_devkmsg
killall mdev udevd syslogd 2>/dev/null
# Kill any getty shells that might be running
for pid in $(pidof sh); do
if ! [ "$pid" = "1" ]; then
kill -9 "$pid"
fi
done
# shellcheck disable=SC2093
exec switch_root /sysroot "$init"

View file

@ -6,18 +6,45 @@ PMOS_BOOT=""
PMOS_ROOT=""
CONFIGFS=/config/usb_gadget
CONFIGFS_ACM_FUNCTION="acm.usb0"
HOST_IP="${unudhcpd_host_ip:-172.16.42.1}"
deviceinfo_getty=""
deviceinfo_name=""
deviceinfo_codename=""
# Redirect stdout and stderr to logfile
setup_log() {
local console
console="$(cat /sys/devices/virtual/tty/console/active)"
local warn_null_console=""
# Stash fd1/2 so we can restore them before switch_root, but only if the
# console is not null
if [ -n "$console" ] ; then
# The kernel logs go to the console, and we log to the kernel. Avoid printing everything
# twice.
console="/dev/null"
exec 3>&1 4>&2
else
# Setting console=null is a trick used on quite a few pmOS devices. However it is generally a really
# bad idea since it makes it impossible to debug kernel panics, and it makes our job logging in the
# initramfs a lot harder. Let's encourage people to stop using this by printing a warning to dmesg
# and logging to every console we can.
# Instead folks should add 'quiet' or 'silent' to the kernel cmdline to disable logging.
console="/dev/$(echo "$deviceinfo_getty" | cut -d';' -f1)"
if ! [ -e "$console" ]; then
console="/dev/null"
fi
warn_null_console="true"
fi
# Disable kmsg ratelimiting for userspace (it gets re-enabled again before switch_root)
echo on > /proc/sys/kernel/printk_devkmsg
# Spawn syslogd to log to the kernel
syslogd -K
# Stash fd1/2 so we can restore them before switch_root
exec 3>&1 4>&2
local pmsg="/dev/pmsg0"
if ! [ -e "$pmsg" ]; then
@ -28,7 +55,16 @@ setup_log() {
# as to the kernel ringbuffer and pstore (if available).
# Process substitution is technically non-POSIX, but is supported by busybox
# shellcheck disable=SC3001
exec > >(tee /pmOS_init.log "$pmsg" | logger -t "$LOG_PREFIX" -p user.info) 2>&1
exec > >(tee /pmOS_init.log "$pmsg" "$console" | logger -t "$LOG_PREFIX" -p user.info) 2>&1
if [ -n "$warn_null_console" ]; then
# Log to the display as well just to be sure.
echo "postmarketOS: ****************************************************" | tee /dev/tty1
echo "WARNING: 'console=null' on kernel cmdline. This is NOT supported!" | tee /dev/tty1
echo "WARNING: Use 'quiet' instead if you want to disable logging." | tee /dev/tty1
echo "WARNING: Logging initramfs output to $console, as a fallback." | tee /dev/tty1
echo "postmarketOS: ****************************************************" | tee /dev/tty1
fi
}
mount_proc_sys_dev() {
@ -718,9 +754,8 @@ start_unudhcpd() {
return
fi
local host_ip="${unudhcpd_host_ip:-172.16.42.1}"
local client_ip="${unudhcpd_client_ip:-172.16.42.2}"
echo "Starting unudhcpd with server ip $host_ip, client ip: $client_ip"
echo "Starting unudhcpd with server ip $HOST_IP, client ip: $client_ip"
# Get usb interface
usb_network_function="${deviceinfo_usb_network_function:-ncm.usb0}"
@ -735,12 +770,12 @@ start_unudhcpd() {
INTERFACE=""
fi
if [ -n "$INTERFACE" ]; then
ifconfig "$INTERFACE" "$host_ip"
elif ifconfig rndis0 "$host_ip" 2>/dev/null; then
ifconfig "$INTERFACE" "$HOST_IP"
elif ifconfig rndis0 "$HOST_IP" 2>/dev/null; then
INTERFACE=rndis0
elif ifconfig usb0 "$host_ip" 2>/dev/null; then
elif ifconfig usb0 "$HOST_IP" 2>/dev/null; then
INTERFACE=usb0
elif ifconfig eth0 "$host_ip" 2>/dev/null; then
elif ifconfig eth0 "$HOST_IP" 2>/dev/null; then
INTERFACE=eth0
fi
@ -754,10 +789,215 @@ start_unudhcpd() {
echo " Using interface $INTERFACE"
echo " Starting the DHCP daemon"
(
unudhcpd -i "$INTERFACE" -s "$host_ip" -c "$client_ip"
unudhcpd -i "$INTERFACE" -s "$HOST_IP" -c "$client_ip"
) &
}
setup_usb_acm_configfs() {
active_udc="$(cat $CONFIGFS/g1/UDC)"
if ! [ -e "$CONFIGFS" ]; then
echo " /config/usb_gadget does not exist, can't set up serial gadget"
return 1
fi
# unset UDC
echo "" > /config/usb_gadget/g1/UDC
# Create acm function
mkdir "$CONFIGFS/g1/functions/$CONFIGFS_ACM_FUNCTION" \
|| echo " Couldn't create $CONFIGFS/g1/functions/$CONFIGFS_ACM_FUNCTION"
# Link the acm function to the configuration
ln -s "$CONFIGFS/g1/functions/$CONFIGFS_ACM_FUNCTION" "$CONFIGFS/g1/configs/c.1" \
|| echo " Couldn't symlink $CONFIGFS_ACM_FUNCTION"
return 0
}
# Spawn a subshell to restart the getty if it exits
# $1: tty
run_getty() {
{
# Due to how the Linux host ACM driver works, we need to wait
# for data to be sent from the host before spawning the getty.
# Otherwise our README message will be echo'd back all garbled.
# On Linux in particular, there is a hack we can use: by writing
# something to the port, it will be echo'd back at the moment the
# port on the host side is opened, so user input won't even be
# needed in most cases. For more info see the blog posts at:
# https://michael.stapelberg.ch/posts/2021-04-27-linux-usb-virtual-serial-cdc-acm/
# https://connolly.tech/posts/2024_04_15-broken-connections/
if [ "$1" = "ttyGS0" ]; then
echo " " > /dev/ttyGS0
# shellcheck disable=SC3061
read -r < /dev/ttyGS0
fi
while /sbin/getty -n -l /sbin/pmos_getty "$1" 115200 vt100; do
sleep 0.2
done
} &
}
debug_shell() {
echo "Entering debug shell"
# if we have a UDC it's already been configured for USB networking
local have_udc
have_udc="$(cat $CONFIGFS/g1/UDC)"
if [ -n "$have_udc" ]; then
setup_usb_acm_configfs
fi
# mount pstore, if possible
if [ -d /sys/fs/pstore ]; then
mount -t pstore pstore /sys/fs/pstore || true
fi
mount -t debugfs none /sys/kernel/debug || true
# make a symlink like Android recoveries do
ln -s /sys/kernel/debug /d
cat <<-EOF > /README
postmarketOS debug shell
https://postmarketos.org/debug-shell
Device: $deviceinfo_name ($deviceinfo_codename)
Kernel: $(uname -r)
OS ver: $VERSION
initrd: $INITRAMFS_PKG_VERSION
Run 'pmos_continue_boot' to continue booting.
EOF
# Add pmos_logdump message only if relevant
if [ -n "$have_udc" ]; then
echo "Run 'pmos_logdump' to generate a log dump and expose it over USB." >> /README
fi
if [ -n "$have_udc" ] && [ -f /usr/bin/setup_usb_storage ]; then
cat <<-EOF >> /README
You can expose storage devices over USB with
'setup_usb_storage /dev/DEVICE'
EOF
fi
# Display some info
cat <<-EOF > /etc/profile
cat /README
. /init_functions.sh
EOF
cat <<-EOF > /sbin/pmos_getty
#!/bin/sh
/bin/sh -l
EOF
chmod +x /sbin/pmos_getty
cat <<-EOF > /sbin/pmos_continue_boot
#!/bin/sh
echo "Continuing boot..."
touch /tmp/continue_boot
pkill -f telnetd.*:23
while sleep 1; do :; done
EOF
chmod +x /sbin/pmos_continue_boot
cat <<-EOF > /sbin/pmos_logdump
#!/bin/sh
echo "Dumping logs, check for a new mass storage device"
touch /tmp/dump_logs
EOF
chmod +x /sbin/pmos_logdump
# Get the console (ttyX) associated with /dev/console
local active_console
active_console="$(cat /sys/devices/virtual/tty/tty0/active)"
# Get a list of all active TTYs include serial ports
local serial_ports
serial_ports="$(cat /sys/devices/virtual/tty/console/active)"
# Get the getty device too (might not be active)
local getty
getty="$(echo "$deviceinfo_getty" | cut -d';' -f1)"
# Run getty's on the consoles
for tty in $serial_ports; do
# Some ports we handle explicitly below to make sure we don't
# accidentally spawn two getty's on them
if echo "tty0 tty1 ttyGS0 $getty" | grep -q "$tty" ; then
continue
fi
run_getty "$tty"
done
if [ -n "$getty" ]; then
run_getty "$getty"
fi
# Rewrite tty to tty1 if tty0 is active
if [ "$active_console" = "tty0" ]; then
active_console="tty1"
fi
# Getty on the display
hide_splash
run_getty "$active_console"
# And on the usb acm port (if it exists)
if [ -e /dev/ttyGS0 ]; then
run_getty ttyGS0
fi
# To avoid racing with the host PC opening the ACM port, we spawn
# the getty first. See the comment in run_getty for more details.
setup_usb_configfs_udc
# Spawn telnetd for those who prefer it. ACM gadget mode is not
# supported on some old kernels so this exists as a fallback.
telnetd -b "${HOST_IP}:23" -l /sbin/pmos_getty &
# wait until we get the signal to continue boot
while ! [ -e /tmp/continue_boot ]; do
sleep 0.2
if [ -e /tmp/dump_logs ]; then
rm -f /tmp/dump_logs
export_logs
fi
done
# Remove the ACM gadget device
# FIXME: would be nice to have a way to keep this on and
# pipe kernel/init logs to it.
rm -f $CONFIGFS/g1/configs/c.1/"$CONFIGFS_ACM_FUNCTION"
rmdir $CONFIGFS/g1/functions/"$CONFIGFS_ACM_FUNCTION"
setup_usb_configfs_udc
show_splash "Loading..."
pkill -f fbkeyboard || true
}
# Check if the user is pressing a key and either drop to a shell or halt boot as applicable
check_keys() {
{
# If the user is pressing either the left control key or the volume down
# key then drop to a debug shell.
if iskey KEY_LEFTCTRL KEY_VOLUMEDOWN; then
debug_shell
# If instead they're pressing left shift or volume up, then fail boot
# and dump logs
elif iskey KEY_LEFTSHIFT KEY_VOLUMEUP; then
fail_halt_boot
fi
touch /tmp/debug_shell_exited
} &
while ! [ -e /tmp/debug_shell_exited ]; do
sleep 1
done
}
# $1: Message to show
show_splash() {
# Skip for non-framebuffer devices
@ -930,6 +1170,7 @@ export_logs() {
fail_halt_boot() {
export_logs
debug_shell
echo "Looping forever"
while true; do
sleep 1

View file

@ -1,18 +1,16 @@
# Maintainer: Ferenc Bakonyi <bakonyi.ferenc@gmail.com>
pkgname=postmarketos-mkinitfs-hook-console-shell
pkgver=0.3.1
pkgver=0.4.0
pkgrel=0
pkgdesc="Root console shell in the initramfs (security hole, for debugging only)"
url="https://postmarketos.org"
depends="postmarketos-mkinitfs devicepkg-utils fbdebug evtest linuxconsoletools reboot-mode fbkeyboard font-dejavu"
source="console-shell.sh console-shell.files console-shell.modules"
source="console-shell.files console-shell.modules"
arch="noarch"
license="GPL-2.0-or-later"
options="!check" # No tests
package() {
install -Dm644 "$srcdir"/console-shell.sh \
"$pkgdir"/usr/share/mkinitfs/hooks/30-console-shell.sh
install -Dm644 "$srcdir"/console-shell.files \
"$pkgdir"/usr/share/mkinitfs/files/30-console-shell.files
install -Dm644 "$srcdir"/console-shell.modules \
@ -20,7 +18,6 @@ package() {
}
sha512sums="
0753bf2e2bb011309a906db08b5fc13dba90e55bd5d11c8da0c34c89faf04dbf5915785cc0ed2a164f2e4c5fede06c7e80b5f592f4deb306cf5cec2371ef88f9 console-shell.sh
17b65cb24103e4c1459ae72bc036c1f06cdfcccf480389ecf6a28253d104b9b06d394cf53314a1ef4ace4ffc88b6b1384ef4894b7270d6b2cfdfc83592e12b2c console-shell.files
a9b069ed121ffeee887e0583d8cb46035ecf1fa90a26a4ecb3aa11ff03178b2b08621f6676db6b2350f290694c04aabcf36f2ce3e0813a76dde9a33555edb112 console-shell.modules
"

View file

@ -1,20 +0,0 @@
#!/bin/sh
# shellcheck disable=SC1091
. ./init_functions.sh
. /usr/share/misc/source_deviceinfo
# mount pstore, if possible
if [ -d /sys/fs/pstore ]; then
mount -t pstore pstore /sys/fs/pstore || true
fi
if tty -s; then
tty=/dev/tty0
modprobe uinput
fbkeyboard -r $(cat /sys/class/graphics/fbcon/rotate) 2>$tty &
echo "Exit the shell to continue booting:" > $tty
sh +m <$tty >$tty 2>$tty
pkill -f fbkeyboard
else
echo "No tty attached, exiting."
fi

View file

@ -1,77 +0,0 @@
#!/bin/sh
# shellcheck disable=SC1091
. ./init_functions.sh
. /usr/share/misc/source_deviceinfo
TELNET_PORT=23
setup_usb_network
start_unudhcpd
show_splash "WARNING: debug-shell is active\\nhttps://postmarketos.org/debug-shell"
echo "Create 'pmos_continue_boot' script"
{
echo "#!/bin/sh"
#Disable any active usb mass storage
echo "if [ -d /config/usb_gadget/g1/functions/mass_storage.0 ]; then setup_usb_storage; fi"
echo "pkill -9 -f pmos_shell"
echo "pkill -f pmos_fail_halt_boot"
} >/usr/bin/pmos_continue_boot
chmod +x /usr/bin/pmos_continue_boot
echo "Create 'pmos_shell' script"
{
echo "#!/bin/sh"
echo "sh"
} >/usr/bin/pmos_shell
chmod +x /usr/bin/pmos_shell
echo "Create 'pmos_fail_halt_boot' script"
{
echo "#!/bin/sh"
echo '. /init_functions.sh'
echo "fail_halt_boot"
} >/usr/bin/pmos_fail_halt_boot
chmod +x /usr/bin/pmos_fail_halt_boot
echo "Create debug-shell cleanup script"
{
echo "#!/bin/sh"
# shellcheck disable=SC2016
echo 'kill `cat /run/debug-shell-telnet.pid`'
} > /hooks-cleanup/debug-shell-cleanup.sh
chmod +x /hooks-cleanup/debug-shell-cleanup.sh
echo "Start the telnet daemon"
{
echo "#!/bin/sh"
echo "echo \"Type 'pmos_continue_boot' to continue booting:\""
echo "sh"
} >/telnet_connect.sh
chmod +x /telnet_connect.sh
host_ip="${unudhcpd_host_ip:-172.16.42.1}"
# Run telnetd with -F (foreground) instead of as a daemon so we can get it's PID
telnetd -F -b "${host_ip}:${TELNET_PORT}" -l /telnet_connect.sh &
echo "$!" > /run/debug-shell-telnet.pid
# mount pstore, if possible
if [ -d /sys/fs/pstore ]; then
mount -t pstore pstore /sys/fs/pstore || true
fi
# mount debugfs - very helpful for debugging
mount -t debugfs none /sys/kernel/debug || true
# make a symlink like Android recoveries do
ln -s /sys/kernel/debug /d
echo "---"
echo "WARNING: debug-shell is active on ${host_ip}:${TELNET_PORT}."
echo "This is a security hole! Only use it for debugging, and"
echo "uninstall the debug-shell hook afterwards!"
echo "You can expose storage devices using 'setup_usb_storage /dev/DEVICE'"
echo "---"
pmos_shell
# Show "Loading" splash again when continuing
show_splash "Loading..."

View file

@ -1,17 +1,15 @@
pkgname=postmarketos-mkinitfs-hook-debug-shell
pkgver=0.6.1
pkgver=0.7.0
pkgrel=0
pkgdesc="Root shell in the initramfs (security hole, for debugging only)"
url="https://postmarketos.org"
depends="postmarketos-mkinitfs devicepkg-utils fbdebug evtest linuxconsoletools reboot-mode libinput libinput-tools"
source="20-debug-shell.sh 20-debug-shell.files setup_usb_storage.sh"
source="20-debug-shell.files setup_usb_storage.sh"
arch="noarch"
license="GPL2"
options="!check"
package() {
install -Dm644 "$srcdir"/20-debug-shell.sh \
"$pkgdir"/usr/share/mkinitfs/hooks/20-debug-shell.sh
install -Dm644 "$srcdir"/20-debug-shell.files \
"$pkgdir"/usr/share/mkinitfs/files/20-debug-shell.files
install -Dm755 "$srcdir"/setup_usb_storage.sh \
@ -19,7 +17,6 @@ package() {
}
sha512sums="
46c7a77cc07e3f2acb733583a2710ea50366308c1018ead3081f910a243381a52eb2e584f7b11eb159b7fb16891b6d9acf2ffe3ace1588fcce9aad617787f675 20-debug-shell.sh
a739bc47d905d189edb26d9ebfd062023720fefdaab27207471c16d53a9c12ea8b81092a1047d8f2300e42ba500bdf6c5a3343aca55aab5bf8e84d68eb5680ab 20-debug-shell.files
75d485c2e9f352cfd717b7a92753a9dfc4a72526a44bcbb784eacb4ef9011072b3ffa1c42a317c0940598cc076fb6c61676c440e5b188378b19ca08d882c1338 setup_usb_storage.sh
"