From 9070da320fda2325fc15662256a6a98b4feba811 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Wed, 3 Jun 2026 21:58:46 +0530 Subject: [PATCH] send-to-lava: protect LAVA signal emission from printk injection Kernel printk messages can be interleaved into userspace stdout on noisy systems and corrupt LAVA TESTCASE signal lines. This can cause LAVA to miss or misparse otherwise valid test results. Update send-to-lava.sh to validate and buffer result-file entries before emitting signals, sanitize testcase IDs, and briefly lower the kernel console loglevel only around the individual LAVA signal printf when /proc/sys/kernel/printk is writable. The original printk settings are saved and restored immediately after each signal line. Signed-off-by: Srikanth Muppandam --- Runner/utils/send-to-lava.sh | 171 +++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 16 deletions(-) diff --git a/Runner/utils/send-to-lava.sh b/Runner/utils/send-to-lava.sh index a89a2e21..10c7d99a 100755 --- a/Runner/utils/send-to-lava.sh +++ b/Runner/utils/send-to-lava.sh @@ -1,44 +1,183 @@ #!/bin/sh -#Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -#SPDX-License-Identifier: BSD-3-Clause -RESULT_FILE="$1" -SIGNAL_FILE="/tmp/lava_signals_$$.log" +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# Emit LAVA testcase signals from a result file. +# +# LAVA parses TESTCASE signals from the serial console. On noisy systems, +# asynchronous kernel printk messages can be interleaved into userspace stdout +# and corrupt the signal line, for example: +# +# <<>> +# +# This script minimizes that risk by: +# - validating/sanitizing result-file content before emitting signals +# - lowering kernel console_loglevel only for the tiny critical section where +# the LAVA signal line is printed, when /proc/sys/kernel/printk is writable +# - emitting each LAVA signal as one userspace printf operation +# - restoring the exact original printk settings immediately afterwards +# - removing the temporary signal buffer explicitly after emission +# +# Important: this does not suppress kernel logs during test execution. The +# printk loglevel is changed only around the LAVA signal printf, after the test +# has already completed and is reporting the result. Kernel messages generated +# during that short window remain available in the kernel ring buffer via dmesg. +# +# If /proc/sys/kernel/printk is not writable, the script does not change printk +# settings and falls back to the existing safe printf behavior. It intentionally +# does not use "dmesg -n" as a fallback because that cannot restore the exact +# original console loglevel. + +RESULT_FILE="${1:-}" +SIGNAL_FILE="${TMPDIR:-/tmp}/lava_signals_$$.log" +PRINTK_SAVED="" +PRINTK_CHANGED=0 valid_result() { case "$1" in - PASS|FAIL|SKIP|UNKNOWN) return 0 ;; - *) return 1 ;; + PASS|FAIL|SKIP|UNKNOWN) + return 0 + ;; + *) + return 1 + ;; esac } -# Collect signals in buffer +sanitize_testcase_id() { + # LAVA testcase IDs should be token-safe. Keep only characters that do not + # interfere with signal parsing. + printf '%s' "$1" | tr -dc '[:alnum:]_.:-' +} + +save_and_quiet_kernel_console() { + PRINTK_SAVED="" + PRINTK_CHANGED=0 + + # /proc/sys/kernel/printk format: + # console_loglevel default_message_loglevel minimum_console_loglevel default_console_loglevel + # + # Set only console_loglevel to 1 while preserving the remaining fields. + # This keeps only KERN_EMERG on the console during the LAVA signal printf. + if [ -r /proc/sys/kernel/printk ] && [ -w /proc/sys/kernel/printk ]; then + PRINTK_SAVED="$(cat /proc/sys/kernel/printk 2>/dev/null || true)" + + if [ -n "$PRINTK_SAVED" ]; then + quiet_printk="$( + printf '%s\n' "$PRINTK_SAVED" | + awk 'NF >= 4 { $1 = 1; print }' + )" + + if [ -n "$quiet_printk" ]; then + if printf '%s\n' "$quiet_printk" > /proc/sys/kernel/printk 2>/dev/null; then + PRINTK_CHANGED=1 + fi + fi + fi + fi + + return 0 +} + +restore_kernel_console() { + if [ "$PRINTK_CHANGED" = "1" ] && + [ -n "$PRINTK_SAVED" ] && + [ -w /proc/sys/kernel/printk ]; then + printf '%s\n' "$PRINTK_SAVED" > /proc/sys/kernel/printk 2>/dev/null || true + fi + + PRINTK_SAVED="" + PRINTK_CHANGED=0 +} + +cleanup() { + restore_kernel_console + rm -f "$SIGNAL_FILE" +} + +trap cleanup EXIT HUP INT TERM + +# Remove any stale same-PID file before use. The EXIT trap is kept as backup, +# and the file is also removed explicitly after signal emission. +rm -f "$SIGNAL_FILE" + +if [ -z "$RESULT_FILE" ]; then + echo "[WARNING] Result file argument missing" >&2 + exit 0 +fi + +# Collect validated signals in a buffer first. +# +# Expected result-file formats: +# +# ... +# +# The first field is testcase id and the last field is result. if [ -f "$RESULT_FILE" ]; then while IFS= read -r line || [ -n "$line" ]; do - testcase=$(echo "$line" | awk '{print $1}') - result=$(echo "$line" | awk '{print $NF}' | tr '[:lower:]' '[:upper:]') - testcase_clean=$(echo "$testcase" | tr -dc '[:alnum:]_-') + # Result files are token-based: + # first token = testcase id + # last token = result + # + # Intentional word splitting is used here to parse token fields. + # shellcheck disable=SC2086 + set -- $line + + [ "$#" -gt 0 ] || continue + + case "$1" in + \#*) + continue + ;; + esac + + if [ "$#" -lt 2 ]; then + echo "[WARNING] Ignoring malformed result line: $line" >&2 + continue + fi + + testcase="$1" + + result="" + for token in "$@"; do + result="$token" + done + + result="$(printf '%s' "$result" | tr '[:lower:]' '[:upper:]')" + testcase_clean="$(sanitize_testcase_id "$testcase")" + + if [ -z "$testcase_clean" ]; then + echo "[WARNING] Ignoring result line with invalid testcase id: $line" >&2 + continue + fi if valid_result "$result"; then printf '<<>>\n' \ "$testcase_clean" "$result" >> "$SIGNAL_FILE" + else + echo "[WARNING] Ignoring result line with invalid result: $line" >&2 fi done < "$RESULT_FILE" else echo "[WARNING] Result file missing: $RESULT_FILE" >&2 fi -# Emit signals using shell builtin printf. -# Do not use cat. Do not touch printk/dmesg. +# Emit signals with the smallest possible critical section. +# +# Kernel console quieting is applied only for the individual printf and restored +# immediately afterwards. Test execution logs remain visible; only asynchronous +# printk injection into the LAVA protocol line is avoided. if [ -s "$SIGNAL_FILE" ]; then while IFS= read -r signal_line || [ -n "$signal_line" ]; do [ -n "$signal_line" ] || continue - - # Blank lines help if previous console output did not end cleanly. - # The signal itself is still emitted only once. + + save_and_quiet_kernel_console printf '\n%s\n\n' "$signal_line" + restore_kernel_console done < "$SIGNAL_FILE" fi -# Cleanup +# Explicit cleanup after signal emission. The trap remains as backup for early +# exits or interruptions. rm -f "$SIGNAL_FILE"