diff --git a/Runner/config/pkg_command_map.conf b/Runner/config/pkg_command_map.conf index c7e2d8e3..dc62269b 100755 --- a/Runner/config/pkg_command_map.conf +++ b/Runner/config/pkg_command_map.conf @@ -212,6 +212,24 @@ opkg:Sensors:sns_test=qcom-sensors-api qcom-sensors-core qcom-sensors-registry q debian:package-set:fastrpc=fastrpc-support fastrpc-tests ubuntu:package-set:fastrpc=fastrpc-support fastrpc-tests +# --------------------------------------------------------------------------- +# Qualcomm QTEE PKCS#11 validation packages. +# +# xtest-qtee depends on libckqteec0 and the MinkIPC client libraries. The +# qteesupplicant package is listed explicitly because the PKCS#11 Trusted +# Application needs the running QTEE listener services during validation. +# +# These packages are currently recovered from qli-staging by the PKCS#11 +# runner on Debian and Ubuntu. Yocto, CentOS, and other distributions continue +# to use image-provided components until confirmed package mappings exist. +# --------------------------------------------------------------------------- + +debian:package-set:minkipc-pkcs11=minkipc-qteesupplicant xtest-qtee +ubuntu:package-set:minkipc-pkcs11=minkipc-qteesupplicant xtest-qtee + +debian:xtest_qtee=xtest-qtee +ubuntu:xtest_qtee=xtest-qtee + # --------------------------------------------------------------------------- # Qualcomm userspace-resource-manager package set. # diff --git a/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/MinkIPC_PKCS11_Multi_Client_Validation.yaml b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/MinkIPC_PKCS11_Multi_Client_Validation.yaml new file mode 100644 index 00000000..6db8286e --- /dev/null +++ b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/MinkIPC_PKCS11_Multi_Client_Validation.yaml @@ -0,0 +1,24 @@ +metadata: + name: MinkIPC_PKCS11_Multi_Client_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate concurrent MinkIPC QTEE PKCS#11 clients and bounded recovery from serialized TA open contention" + maintainer: + - "Qualcomm Linux Testkit" + os: + - linux + scope: + - functional + +params: + CLIENT: "" + TEE_ID: "" + CLIENTS: "2" + TIMEOUT: "300" + BUSY_RETRIES: "5" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation + - XTEST_QTEE="${CLIENT}" XTEST_TEE_ID="${TEE_ID}" XTEST_CLIENT_COUNT="${CLIENTS}" XTEST_TIMEOUT="${TIMEOUT}" XTEST_BUSY_RETRIES="${BUSY_RETRIES}" ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh MinkIPC_PKCS11_Multi_Client_Validation.res diff --git a/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/README.md b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/README.md new file mode 100644 index 00000000..62c51b5c --- /dev/null +++ b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/README.md @@ -0,0 +1,41 @@ +# MinkIPC QTEE PKCS#11 Multi-Client Validation + +This suite starts multiple `xtest_qtee` processes concurrently to validate +MinkIPC and QTEE PKCS#11 multi-client handling. Each process runs only the +non-mutating cases: + +- 1000: initialize and close the Cryptoki library. +- 1001: enumerate slots, token information, and mechanisms. +- 1002: open, inspect, and close sessions. + +Token initialization and object-mutating cases are intentionally excluded so +the concurrent clients do not race while changing shared persistent state. + +QTEE serializes part of the TA session-open path. When simultaneous processes +race that path, one process may receive `TEEC_ERROR_BUSY` (`0xffff000d`). The +runner treats only that exact open-session response as transient and retries +the affected client with a short bounded backoff. Any other command or result +failure is reported immediately. Every client must eventually pass all three +cases and leave no relevant QCOMTEE or RPMB kernel errors. + +Run two clients with defaults: + +```sh +./run.sh +``` + +Run four clients against a selected TEE: + +```sh +./run.sh --clients 4 --tee 1 +``` + +Change the number of retries allowed after a QTEE busy response: + +```sh +./run.sh --busy-retries 3 +``` + +The default is five retries after the initial attempt. Set +`--busy-retries 0` to require every concurrent client to pass on its first +attempt. diff --git a/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/run.sh b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/run.sh new file mode 100755 index 00000000..bb4a4ef4 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Multi_Client_Validation/run.sh @@ -0,0 +1,375 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/lib_pkg_provider.sh" +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/minkipc_pkcs11lib.sh" + +TESTNAME="MinkIPC_PKCS11_Multi_Client_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +CLIENT_PATH="${XTEST_QTEE:-}" +TEE_ID="${XTEST_TEE_ID:-}" +CLIENT_COUNT="${XTEST_CLIENT_COUNT:-2}" +TIMEOUT_SECONDS="${XTEST_TIMEOUT:-300}" +BUSY_RETRIES="${XTEST_BUSY_RETRIES:-5}" +SAFE_CASES="1000 1001 1002" +CLIENT_PIDS="" + +minkipc_pkcs11_install_cleanup_traps + +# Purpose: Print the command-line interface supported by this validation. +# Arguments: +# None. +# Output: +# Writes usage text to standard output. +# Returns: +# 0 after writing usage text to standard output. +usage() { + cat </dev/null; then + kill "$cmc_pid" 2>/dev/null || true + fi + done + + for cmc_pid in ${CLIENT_PIDS:-}; do + wait "$cmc_pid" 2>/dev/null || true + done + + CLIENT_PIDS="" + minkipc_pkcs11_restore_runtime + return 0 +} + +# Purpose: Identify the retryable QTEE serialization response in client output. +# Arguments: +# $1 - xtest_qtee output log to inspect. +# Output: +# None. +# Returns: +# 0 only when a TEEC open-session operation reports the TEEC_ERROR_BUSY +# result code 0xffff000d. Otherwise 1. +multi_client_log_has_qtee_busy() { + mclhqb_log="$1" + + [ -r "$mclhqb_log" ] || return 1 + grep -qiE \ + 'TEEC[ _-]*open[ _-]*session.*(0x)?ffff000d' \ + "$mclhqb_log" +} + +# Purpose: Run one concurrent PKCS#11 client with bounded QTEE busy recovery. +# Arguments: +# $1 - One-based client index used in log names and retry staggering. +# $2 - Stable final log-file path for this client. +# $3 - File where the number of attempts is recorded. +# $4... - xtest_qtee command and arguments. +# Expected globals: +# SCRIPT_DIR, TIMEOUT_SECONDS, BUSY_RETRIES, and SAFE_CASES. +# Side effects: +# Writes one log per attempt, updates the stable final log, and sleeps for a +# short staggered backoff after a retryable QTEE busy response. +# Returns: +# 0 when an attempt exits successfully and passes manifest validation. +# 1 immediately for a non-busy failure or after busy retries are exhausted. +run_multi_client_worker() { + rmcw_index="$1" + rmcw_final_log="$2" + rmcw_attempt_file="$3" + shift 3 + + rmcw_attempt=1 + rmcw_max_attempts=$((BUSY_RETRIES + 1)) + + while [ "$rmcw_attempt" -le "$rmcw_max_attempts" ]; do + rmcw_attempt_log="$SCRIPT_DIR/xtest_qtee_multiclient_${rmcw_index}_attempt_${rmcw_attempt}.log" + printf '%s\n' "$rmcw_attempt" > "$rmcw_attempt_file" + + run_with_timeout_log "$TIMEOUT_SECONDS" "$rmcw_attempt_log" "$@" + rmcw_rc=$? + cp "$rmcw_attempt_log" "$rmcw_final_log" + + if [ "$rmcw_rc" -eq 0 ] && \ + minkipc_pkcs11_validate_log "$rmcw_attempt_log" "$SAFE_CASES"; then + return 0 + fi + + if ! multi_client_log_has_qtee_busy "$rmcw_attempt_log"; then + return 1 + fi + + if [ "$rmcw_attempt" -ge "$rmcw_max_attempts" ]; then + return 1 + fi + + rmcw_delay=$((1 + ((rmcw_index + rmcw_attempt) % 2))) + sleep "$rmcw_delay" + rmcw_attempt=$((rmcw_attempt + 1)) + done + + return 1 +} + +trap 'cleanup_multi_clients' EXIT + +while [ "$#" -gt 0 ]; do + case "$1" in + --client) + [ "$#" -ge 2 ] || write_early_failure "--client requires a value" + CLIENT_PATH="$2" + shift 2 + ;; + --client=*) + CLIENT_PATH=${1#--client=} + shift + ;; + -d|--tee) + [ "$#" -ge 2 ] || write_early_failure "$1 requires a value" + TEE_ID="$2" + shift 2 + ;; + --tee=*) + TEE_ID=${1#--tee=} + shift + ;; + --clients) + [ "$#" -ge 2 ] || write_early_failure "--clients requires a value" + CLIENT_COUNT="$2" + shift 2 + ;; + --clients=*) + CLIENT_COUNT=${1#--clients=} + shift + ;; + --timeout) + [ "$#" -ge 2 ] || write_early_failure "--timeout requires a value" + TIMEOUT_SECONDS="$2" + shift 2 + ;; + --timeout=*) + TIMEOUT_SECONDS=${1#--timeout=} + shift + ;; + --busy-retries) + [ "$#" -ge 2 ] || write_early_failure "--busy-retries requires a value" + BUSY_RETRIES="$2" + shift 2 + ;; + --busy-retries=*) + BUSY_RETRIES=${1#--busy-retries=} + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + write_early_failure "unknown option: $1" + ;; + esac +done + +case "$CLIENT_COUNT" in + ''|*[!0-9]*) + write_early_failure "invalid client count: $CLIENT_COUNT" + ;; +esac +if [ "$CLIENT_COUNT" -lt 2 ] || [ "$CLIENT_COUNT" -gt 8 ]; then + write_early_failure "client count must be between 2 and 8: $CLIENT_COUNT" +fi + +case "$TIMEOUT_SECONDS" in + ''|*[!0-9]*|0) + write_early_failure "invalid timeout: $TIMEOUT_SECONDS" + ;; +esac + +case "$BUSY_RETRIES" in + ''|*[!0-9]*) + write_early_failure "invalid busy retry count: $BUSY_RETRIES" + ;; +esac +if [ "$BUSY_RETRIES" -gt 10 ]; then + write_early_failure "busy retry count must be between 0 and 10: $BUSY_RETRIES" +fi + +if [ -n "$TEE_ID" ]; then + case "$TEE_ID" in + *[!A-Za-z0-9_.:-]*) + write_early_failure "invalid TEE identifier: $TEE_ID" + ;; + esac +fi + +rm -f "$RES_FILE" + +OS_ID=$(pkg_detect_os_id 2>/dev/null || printf '%s\n' unknown) +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "OS=$OS_ID arch=$(uname -m 2>/dev/null || printf '%s\n' unknown) clients=$CLIENT_COUNT timeout=${TIMEOUT_SECONDS}s busy_retries=$BUSY_RETRIES" + +if ! minkipc_pkcs11_prepare_runtime "$CLIENT_PATH" "$OS_ID"; then + case "$MINKIPC_PKCS11_PREP_RESULT" in + SKIP) + finish_test SKIP "$MINKIPC_PKCS11_PREP_MESSAGE" 0 + ;; + *) + finish_test FAIL "$MINKIPC_PKCS11_PREP_MESSAGE" 1 + ;; + esac +fi +CLIENT_PATH="$MINKIPC_PKCS11_CLIENT_PATH" + +log_info "Launching $CLIENT_COUNT concurrent clients with non-mutating cases: $SAFE_CASES" +log_info "A client is retried only when QTEE reports TEEC_ERROR_BUSY during session open" + +STALE_INDEX=1 +while [ "$STALE_INDEX" -le 8 ]; do + rm -f "$SCRIPT_DIR/xtest_qtee_multiclient_${STALE_INDEX}.log" + rm -f "$SCRIPT_DIR/xtest_qtee_multiclient_${STALE_INDEX}.attempts" + STALE_ATTEMPT=1 + while [ "$STALE_ATTEMPT" -le 11 ]; do + rm -f "$SCRIPT_DIR/xtest_qtee_multiclient_${STALE_INDEX}_attempt_${STALE_ATTEMPT}.log" + STALE_ATTEMPT=$((STALE_ATTEMPT + 1)) + done + STALE_INDEX=$((STALE_INDEX + 1)) +done + +CLIENT_INDEX=1 +while [ "$CLIENT_INDEX" -le "$CLIENT_COUNT" ]; do + CLIENT_LOG="$SCRIPT_DIR/xtest_qtee_multiclient_${CLIENT_INDEX}.log" + CLIENT_ATTEMPTS="$SCRIPT_DIR/xtest_qtee_multiclient_${CLIENT_INDEX}.attempts" + rm -f "$CLIENT_LOG" + + set -- "$CLIENT_PATH" -t pkcs11 + if [ -n "$TEE_ID" ]; then + set -- "$@" -d "$TEE_ID" + fi + for safe_case in $SAFE_CASES; do + set -- "$@" "$safe_case" + done + + run_multi_client_worker \ + "$CLIENT_INDEX" \ + "$CLIENT_LOG" \ + "$CLIENT_ATTEMPTS" \ + "$@" & + CLIENT_PIDS="$CLIENT_PIDS $!" + CLIENT_INDEX=$((CLIENT_INDEX + 1)) +done + +FAILED_CLIENTS="" +CLIENT_INDEX=1 +for client_pid in $CLIENT_PIDS; do + if wait "$client_pid"; then + CLIENT_RC=0 + else + CLIENT_RC=$? + fi + + CLIENT_LOG="$SCRIPT_DIR/xtest_qtee_multiclient_${CLIENT_INDEX}.log" + CLIENT_ATTEMPTS_FILE="$SCRIPT_DIR/xtest_qtee_multiclient_${CLIENT_INDEX}.attempts" + CLIENT_ATTEMPTS=$(sed -n '1p' "$CLIENT_ATTEMPTS_FILE" 2>/dev/null) + case "$CLIENT_ATTEMPTS" in + ''|*[!0-9]*) + CLIENT_ATTEMPTS=1 + ;; + esac + + CLIENT_ATTEMPT=1 + while [ "$CLIENT_ATTEMPT" -le "$CLIENT_ATTEMPTS" ]; do + CLIENT_ATTEMPT_LOG="$SCRIPT_DIR/xtest_qtee_multiclient_${CLIENT_INDEX}_attempt_${CLIENT_ATTEMPT}.log" + log_file_with_label "client-$CLIENT_INDEX-attempt-$CLIENT_ATTEMPT" "$CLIENT_ATTEMPT_LOG" + CLIENT_ATTEMPT=$((CLIENT_ATTEMPT + 1)) + done + + if [ "$CLIENT_RC" -ne 0 ]; then + if multi_client_log_has_qtee_busy "$CLIENT_LOG"; then + log_fail "Concurrent client $CLIENT_INDEX exhausted QTEE busy recovery after $CLIENT_ATTEMPTS attempts" + elif minkipc_pkcs11_validate_log "$CLIENT_LOG" "$SAFE_CASES"; then + log_fail "Concurrent client $CLIENT_INDEX returned $CLIENT_RC" + else + log_fail "Concurrent client $CLIENT_INDEX $MINKIPC_PKCS11_VALIDATION_MESSAGE" + fi + FAILED_CLIENTS="$FAILED_CLIENTS $CLIENT_INDEX" + elif ! minkipc_pkcs11_validate_log "$CLIENT_LOG" "$SAFE_CASES"; then + log_fail "Concurrent client $CLIENT_INDEX $MINKIPC_PKCS11_VALIDATION_MESSAGE" + FAILED_CLIENTS="$FAILED_CLIENTS $CLIENT_INDEX" + else + log_pass "Concurrent client $CLIENT_INDEX passed after attempts=$CLIENT_ATTEMPTS with cases=$MINKIPC_PKCS11_CASE_TOTAL subtests=$MINKIPC_PKCS11_SUBTEST_TOTAL" + fi + + CLIENT_INDEX=$((CLIENT_INDEX + 1)) +done + +CLIENT_PIDS="" + +if [ -n "$FAILED_CLIENTS" ]; then + FAILED_CLIENTS=$(printf '%s\n' "$FAILED_CLIENTS" | sed 's/^[[:space:]]*//') + finish_test FAIL "concurrent clients failed: $FAILED_CLIENTS" 1 +fi + +if minkipc_pkcs11_scan_dmesg "$SCRIPT_DIR/pkcs11_multiclient_dmesg"; then + finish_test FAIL "relevant QCOMTEE or RPMB errors were found in the kernel log" 1 +fi + +finish_test PASS "clients=$CLIENT_COUNT cases_per_client=3 busy_retries=$BUSY_RETRIES" 0 diff --git a/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/MinkIPC_PKCS11_Validation.yaml b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/MinkIPC_PKCS11_Validation.yaml new file mode 100644 index 00000000..0b756a94 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/MinkIPC_PKCS11_Validation.yaml @@ -0,0 +1,27 @@ +metadata: + name: MinkIPC_PKCS11_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate the Qualcomm QTEE PKCS#11 Trusted Application through MinkIPC using xtest_qtee" + maintainer: + - "Qualcomm Linux Testkit" + os: + - linux + scope: + - functional + +params: + CLIENT: "" + TEE_ID: "" + LEVEL: "0" + TIMEOUT: "1800" + ITERATIONS: "1" + CLEAR_STORAGE: "1" + TEST_IDS: "" + EXCLUDE_IDS: "" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation + - XTEST_QTEE="${CLIENT}" XTEST_TEE_ID="${TEE_ID}" XTEST_LEVEL="${LEVEL}" XTEST_TIMEOUT="${TIMEOUT}" XTEST_ITERATIONS="${ITERATIONS}" XTEST_CLEAR_STORAGE="${CLEAR_STORAGE}" XTEST_INCLUDE="${TEST_IDS}" XTEST_EXCLUDE="${EXCLUDE_IDS}" ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh MinkIPC_PKCS11_Validation.res diff --git a/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/README.md b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/README.md new file mode 100644 index 00000000..5079bd72 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/README.md @@ -0,0 +1,104 @@ +# MinkIPC QTEE PKCS#11 Validation + +This suite validates the Qualcomm QTEE PKCS#11 Trusted Application through +MinkIPC with `xtest_qtee`. The suite is pinned to `-t pkcs11`; regression, +benchmark, performance, statistics, and internal applets are outside its scope. + +## Prerequisites + +- A Qualcomm QCOMTEE or SMCInvoke device accessible to the test user. +- A running `qtee_supplicant` with TA autoload, filesystem, and RPMB listeners. +- Successful secure-filesystem initialization and writable persistent storage. +- RPMB provisioned for QTEE secure storage. +- The PKCS#11 test TA: + + `FD02C9DA-306C-48C7-A49C-BBD827AE86EE.mbn` + +The TA autoloader first checks `/data`, then derives +`/lib/qtee-tas//` from every runtime device-tree compatible string +after removing the vendor prefix. The runner follows the same lookup rule and +does not hardcode a machine list. + +Debian and Ubuntu recover `minkipc-qteesupplicant` and `xtest-qtee` from the +Qualcomm `qli-staging` trixie suite. Yocto, CentOS, and other distributions use +their image-provided components. + +## Usage + +Run all QTEE-applicable PKCS#11 cases at the baseline level: + +```sh +./run.sh +``` + +Run selected test IDs or exclude a test: + +```sh +./run.sh 1000 1003 1022 +./run.sh -x 1026 +``` + +Run optional higher-level coverage: + +```sh +./run.sh --level 15 +``` + +Select a TEE and override the timeout: + +```sh +./run.sh --tee 1 --timeout 2400 +``` + +Run repeatability coverage without cleanup between iterations: + +```sh +./run.sh --iterations 2 +``` + +Storage cleanup is evaluated only before the first iteration. Every iteration +must independently pass the complete result and expected-case validation. + +Use `./run.sh --help` for the complete wrapper interface. + +## Storage cleanup + +The runner checks for both upstream storage test TAs before attempting +`xtest_qtee --clear-storage`. Source inspection shows that this applet stops at +the first TA it cannot open and clears only the REE and RPMB object namespaces +owned by those storage test TAs. It does not clear the PKCS#11 TA namespace. + +Qualcomm QTEE images may provision only the PKCS#11 TA and omit the upstream +storage test TAs. In that configuration the runner skips the inapplicable +applet instead of invoking it and reporting an expected open-session error. +PKCS#11 case 1003 reinitializes its test token, and the object tests remove +their own persistent test objects. Use `--no-clear-storage` when the pre-run +applet must be suppressed explicitly even if both storage test TAs are present. + +## Result policy + +The run passes only when: + +- `xtest_qtee` exits with status zero. +- The summary reports zero failed test cases and zero failed subtests. +- At least one case runs for a filtered invocation. +- Every expected QTEE PKCS#11 case reports `OK` in an unfiltered invocation. +- The shared kernel-log scanner finds no relevant QCOMTEE or RPMB errors. + +The Qualcomm build exposes 21 PKCS#11 cases. The meta-qcom QTEE guard omits +upstream cases 1016, 1018, 1019, 1021, 1025, 1026, 1027, and 1028. The guard +starts before the shared RSA-AES support used by 1026 and ends after 1027, so +1026 is also absent from the QTEE binary. + +Newer upstream optee_test revisions add cases 1029 for object checksums and +1030 for AES-GCM. They are not part of the optee_test 4.0.0 source integrated +by meta-qcom and should be evaluated when that dependency is upgraded. + +## Related validation + +- `MinkIPC_PKCS11_Multi_Client_Validation` runs safe discovery and session + cases from multiple clients and validates recovery from QTEE TA contention. + +Persistent-object validation across a `qtee_supplicant` restart remains future +work because it requires a helper that can intentionally leave a uniquely +labelled token object and reopen it in a later process. diff --git a/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/run.sh b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/run.sh new file mode 100755 index 00000000..8640698a --- /dev/null +++ b/Runner/suites/Kernel/Baseport/MinkIPC_PKCS11_Validation/run.sh @@ -0,0 +1,405 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/lib_pkg_provider.sh" +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/minkipc_pkcs11lib.sh" + +TESTNAME="MinkIPC_PKCS11_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +CLIENT_PATH="${XTEST_QTEE:-}" +TEE_ID="${XTEST_TEE_ID:-}" +TEST_LEVEL="${XTEST_LEVEL:-0}" +TIMEOUT_SECONDS="${XTEST_TIMEOUT:-1800}" +ITERATIONS="${XTEST_ITERATIONS:-1}" +CLEAR_STORAGE="${XTEST_CLEAR_STORAGE:-1}" +INCLUDE_TESTS="${XTEST_INCLUDE:-}" +EXCLUDE_TESTS="${XTEST_EXCLUDE:-}" +FILTERED_RUN=0 + +minkipc_pkcs11_install_cleanup_traps + +# Purpose: Print the command-line interface supported by this validation. +# Arguments: +# None. +# Output: +# Writes usage text to standard output. +# Returns: +# 0 after writing usage text to standard output. +usage() { + cat </dev/null || printf '%s\n' unknown) + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "OS=$OS_ID arch=$(uname -m 2>/dev/null || printf '%s\n' unknown) level=$TEST_LEVEL iterations=$ITERATIONS timeout=${TIMEOUT_SECONDS}s" + +if ! minkipc_pkcs11_prepare_runtime "$CLIENT_PATH" "$OS_ID"; then + case "$MINKIPC_PKCS11_PREP_RESULT" in + SKIP) + finish_test SKIP "$MINKIPC_PKCS11_PREP_MESSAGE" 0 + ;; + *) + finish_test FAIL "$MINKIPC_PKCS11_PREP_MESSAGE" 1 + ;; + esac +fi +CLIENT_PATH="$MINKIPC_PKCS11_CLIENT_PATH" + +CLEAR_LOG="$SCRIPT_DIR/xtest_qtee_clear_storage.log" +rm -f "$CLEAR_LOG" "$SCRIPT_DIR/xtest_qtee_pkcs11.log" +STALE_ITERATION=1 +while [ "$STALE_ITERATION" -le 10 ]; do + rm -f "$SCRIPT_DIR/xtest_qtee_pkcs11_iteration_${STALE_ITERATION}.log" + STALE_ITERATION=$((STALE_ITERATION + 1)) +done + +if [ "$CLEAR_STORAGE" -eq 1 ]; then + MISSING_STORAGE_TAS=$(minkipc_pkcs11_missing_clear_storage_tas) + if [ -n "$MISSING_STORAGE_TAS" ]; then + MISSING_STORAGE_TAS=$(printf '%s\n' "$MISSING_STORAGE_TAS" | tr '\n' ' ' | sed 's/[[:space:]]*$//') + log_info "Skipping xtest_qtee --clear-storage because required upstream storage-test TAs are not provisioned: $MISSING_STORAGE_TAS" + log_info "The PKCS#11 suite initializes its test token and removes its own test objects" + else + log_info "Running documented pre-test cleanup: $CLIENT_PATH --clear-storage" + run_with_timeout_log 300 "$CLEAR_LOG" "$CLIENT_PATH" --clear-storage + CLEAR_RC=$? + log_file_with_label clear-storage "$CLEAR_LOG" + + if [ "$CLEAR_RC" -eq 0 ]; then + log_pass "xtest_qtee pre-test storage cleanup completed" + else + log_warn "xtest_qtee --clear-storage returned $CLEAR_RC despite both storage-test TAs being present" + log_info "Continuing because this applet does not clear the PKCS#11 TA namespace" + fi + fi +else + log_info "Pre-test storage cleanup was disabled by request" +fi + +set -- "$CLIENT_PATH" -t pkcs11 -l "$TEST_LEVEL" +if [ -n "$TEE_ID" ]; then + set -- "$@" -d "$TEE_ID" +fi +for excluded_test in $EXCLUDE_TESTS; do + set -- "$@" -x "$excluded_test" +done +for included_test in $INCLUDE_TESTS; do + set -- "$@" "$included_test" +done + +log_info "Running QTEE PKCS#11 validation with level=$TEST_LEVEL" +if [ -n "$TEE_ID" ]; then + log_info "Using TEE identifier: $TEE_ID" +fi +if [ -n "$INCLUDE_TESTS" ]; then + log_info "Included test filters: $INCLUDE_TESTS" +fi +if [ -n "$EXCLUDE_TESTS" ]; then + log_info "Excluded test filters: $EXCLUDE_TESTS" +fi + +EXPECTED_CASES="$MINKIPC_PKCS11_EXPECTED_CASES" +if [ "$FILTERED_RUN" -eq 1 ]; then + EXPECTED_CASES="-" +fi + +TOTAL_CASES=0 +TOTAL_SUBTESTS=0 +ITERATION=1 +while [ "$ITERATION" -le "$ITERATIONS" ]; do + if [ "$ITERATIONS" -eq 1 ]; then + RUN_LOG="$SCRIPT_DIR/xtest_qtee_pkcs11.log" + RUN_LABEL="xtest_qtee" + else + RUN_LOG="$SCRIPT_DIR/xtest_qtee_pkcs11_iteration_${ITERATION}.log" + RUN_LABEL="xtest_qtee-$ITERATION" + fi + + log_info "Running QTEE PKCS#11 validation iteration $ITERATION of $ITERATIONS" + run_with_timeout_log "$TIMEOUT_SECONDS" "$RUN_LOG" "$@" + RUN_RC=$? + log_file_with_label "$RUN_LABEL" "$RUN_LOG" + + if [ "$RUN_RC" -ne 0 ]; then + if grep -qiE 'rpmb|storage|token.*not|TEE_ERROR|TEEC_' "$RUN_LOG"; then + log_info "Failure may indicate missing RPMB provisioning, persistent storage, or PKCS#11 TA access" + fi + finish_test FAIL "iteration $ITERATION returned $RUN_RC, see $RUN_LOG" 1 + fi + + if ! minkipc_pkcs11_validate_log "$RUN_LOG" "$EXPECTED_CASES"; then + finish_test FAIL "iteration $ITERATION $MINKIPC_PKCS11_VALIDATION_MESSAGE, see $RUN_LOG" 1 + fi + + log_pass "PKCS#11 iteration $ITERATION passed with cases=$MINKIPC_PKCS11_CASE_TOTAL subtests=$MINKIPC_PKCS11_SUBTEST_TOTAL" + TOTAL_CASES=$((TOTAL_CASES + MINKIPC_PKCS11_CASE_TOTAL)) + TOTAL_SUBTESTS=$((TOTAL_SUBTESTS + MINKIPC_PKCS11_SUBTEST_TOTAL)) + ITERATION=$((ITERATION + 1)) +done + +if minkipc_pkcs11_scan_dmesg "$SCRIPT_DIR/pkcs11_dmesg"; then + finish_test FAIL "relevant QCOMTEE or RPMB errors were found in the kernel log" 1 +fi + +finish_test PASS "iterations=$ITERATIONS cases=$TOTAL_CASES subtests=$TOTAL_SUBTESTS level=$TEST_LEVEL" 0 diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index d113169b..741f1551 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -2484,6 +2484,44 @@ run_with_timeout() { return $status } +# Purpose: Run a command with a timeout and capture stdout and stderr. +# Arguments: +# $1 - Timeout in seconds. +# $2 - Destination log-file path. +# $3... - Command and arguments to execute. +# Side effects: +# Creates or replaces the destination log with command stdout and stderr. +# Returns: +# The command status returned by run_with_timeout(), including a nonzero +# status when the command fails or is terminated after the timeout. +run_with_timeout_log() { + rwtl_timeout="$1" + rwtl_log_file="$2" + shift 2 + + run_with_timeout "$rwtl_timeout" "$@" > "$rwtl_log_file" 2>&1 +} + +# Purpose: Replay every line from a file through the common information logger. +# Arguments: +# $1 - Label prepended to each logged line. +# $2 - Log-file path to replay. +# Output: +# Sends each readable input line through log_info(). +# Returns: +# 0 after replaying the file. An unreadable file is treated as empty. +log_file_with_label() { + lfwl_label="$1" + lfwl_file="$2" + + [ -r "$lfwl_file" ] || return 0 + while IFS= read -r lfwl_line || [ -n "$lfwl_line" ]; do + log_info "[$lfwl_label] $lfwl_line" + done < "$lfwl_file" + + return 0 +} + # Only apply a timeout if TIMEOUT is set; prefer `timeout`; avoid functestlib here runWithTimeoutIfSet() { # Normalize TIMEOUT: treat empty or non-numeric as 0 diff --git a/Runner/utils/minkipc_pkcs11lib.sh b/Runner/utils/minkipc_pkcs11lib.sh new file mode 100755 index 00000000..4477aacc --- /dev/null +++ b/Runner/utils/minkipc_pkcs11lib.sh @@ -0,0 +1,562 @@ +#!/bin/sh +# shellcheck disable=SC2034 + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# Shared preparation and result helpers for MinkIPC QTEE PKCS#11 tests. +# functestlib.sh and lib_pkg_provider.sh must be sourced before this file. + +MINKIPC_PKCS11_TA_UUID="FD02C9DA-306C-48C7-A49C-BBD827AE86EE" +MINKIPC_PKCS11_STORAGE_TA_UUIDS="B689F2A7-8ADF-477A-9F99-32E90C0AD0A2 731E279E-AAFB-4575-A771-38CAA6F0CCA6" +MINKIPC_PKCS11_EXPECTED_CASES="1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1017 1020 1022 1023 1024" + +MINKIPC_PKCS11_CLIENT_PATH="" +MINKIPC_PKCS11_QTEE_DEVICE="" +MINKIPC_PKCS11_TA_PATH="" +MINKIPC_PKCS11_SFS_UNIT="" +MINKIPC_PKCS11_SUPPLICANT_UNIT="" +MINKIPC_PKCS11_SFS_STARTED_BY_TEST=0 +MINKIPC_PKCS11_SUPPLICANT_STARTED_BY_TEST=0 +MINKIPC_PKCS11_PREP_RESULT="FAIL" +MINKIPC_PKCS11_PREP_MESSAGE="runtime preparation was not completed" + +MINKIPC_PKCS11_CASE_TOTAL="" +MINKIPC_PKCS11_CASE_FAILED="" +MINKIPC_PKCS11_SUBTEST_TOTAL="" +MINKIPC_PKCS11_SUBTEST_FAILED="" +MINKIPC_PKCS11_VALIDATION_MESSAGE="" + +# Purpose: Locate a QTEE TA using the qtee_supplicant autoload search order. +# Arguments: +# $1 - TA UUID without a filename extension. +# Output: +# Prints the first matching .mbn or .b00 path. +# Returns: +# 0 when a TA file is found, otherwise 1. +find_qtee_ta() { + fqta_uuid="$1" + fqta_upper=$(printf '%s\n' "$fqta_uuid" | tr '[:lower:]' '[:upper:]') + fqta_lower=$(printf '%s\n' "$fqta_uuid" | tr '[:upper:]' '[:lower:]') + + for fqta_name in "$fqta_uuid" "$fqta_upper" "$fqta_lower"; do + for fqta_extension in mbn b00; do + fqta_candidate="/data/$fqta_name.$fqta_extension" + if [ -s "$fqta_candidate" ]; then + printf '%s\n' "$fqta_candidate" + return 0 + fi + done + done + + fqta_compatible_file="" + for fqta_path in \ + /sys/firmware/devicetree/base/compatible \ + /proc/device-tree/compatible; do + if [ -r "$fqta_path" ]; then + fqta_compatible_file="$fqta_path" + break + fi + done + + [ -n "$fqta_compatible_file" ] || return 1 + + fqta_compatibles=$(tr '\000' '\n' < "$fqta_compatible_file" 2>/dev/null) + for fqta_compatible in $fqta_compatibles; do + fqta_platform=${fqta_compatible#*,} + [ -n "$fqta_platform" ] || continue + + for fqta_name in "$fqta_uuid" "$fqta_upper" "$fqta_lower"; do + for fqta_extension in mbn b00; do + fqta_candidate="/lib/qtee-tas/$fqta_platform/$fqta_name.$fqta_extension" + if [ -s "$fqta_candidate" ]; then + printf '%s\n' "$fqta_candidate" + return 0 + fi + done + done + done + + return 1 +} + +# Purpose: Prepare packages required by the QTEE PKCS#11 tests. +# Arguments: +# $1 - Normalized operating-system identifier. +# Side effects: +# Debian and Ubuntu may install the minkipc-pkcs11 package set. +# Returns: +# 0 when packages are ready or image-provided components are expected. +# 1 on package preparation failure and sets MINKIPC_PKCS11_PREP_MESSAGE. +minkipc_pkcs11_prepare_packages() { + mpp_os_id="$1" + + case "$mpp_os_id" in + debian|ubuntu) + for mpp_helper in \ + pkg_provider_init \ + pkg_lookup_package_set \ + pkg_ensure_optional_package_set_present; do + if ! command -v "$mpp_helper" >/dev/null 2>&1; then + MINKIPC_PKCS11_PREP_MESSAGE="required package helper is unavailable: $mpp_helper" + return 1 + fi + done + + if ! pkg_lookup_package_set minkipc-pkcs11 >/dev/null 2>&1; then + MINKIPC_PKCS11_PREP_MESSAGE="minkipc-pkcs11 package mapping is unavailable for os=$mpp_os_id" + return 1 + fi + + if ! pkg_provider_init; then + MINKIPC_PKCS11_PREP_MESSAGE="package provider initialization failed" + return 1 + fi + + mpp_old_upgrade="${PKG_PACKAGE_SET_UPGRADE-__unset__}" + PKG_PACKAGE_SET_UPGRADE=0 + export PKG_PACKAGE_SET_UPGRADE + + if ! pkg_ensure_optional_package_set_present \ + minkipc-pkcs11 \ + qli-staging \ + trixie \ + --overlay; then + case "$mpp_old_upgrade" in + __unset__) + unset PKG_PACKAGE_SET_UPGRADE + ;; + *) + PKG_PACKAGE_SET_UPGRADE="$mpp_old_upgrade" + export PKG_PACKAGE_SET_UPGRADE + ;; + esac + MINKIPC_PKCS11_PREP_MESSAGE="failed to prepare the QTEE PKCS#11 package set" + return 1 + fi + + case "$mpp_old_upgrade" in + __unset__) + unset PKG_PACKAGE_SET_UPGRADE + ;; + *) + PKG_PACKAGE_SET_UPGRADE="$mpp_old_upgrade" + export PKG_PACKAGE_SET_UPGRADE + ;; + esac + log_pass "QTEE PKCS#11 package set is ready" + ;; + *) + log_info "Package installation is not enabled for os=$mpp_os_id, using image-provided QTEE components" + ;; + esac + + return 0 +} + +# Purpose: Validate and prepare the complete runtime shared by PKCS#11 tests. +# Arguments: +# $1 - Requested xtest_qtee path or command name, or an empty string. +# $2 - Normalized operating-system identifier. +# Side effects: +# May install packages and start inactive SFS or qtee_supplicant services. +# Outputs: +# Sets MINKIPC_PKCS11_CLIENT_PATH and related runtime-state variables. +# Returns: +# 0 when the runtime is ready. Returns 1 and sets +# MINKIPC_PKCS11_PREP_RESULT and MINKIPC_PKCS11_PREP_MESSAGE otherwise. +minkipc_pkcs11_prepare_runtime() { + mpr_requested_client="$1" + mpr_os_id="$2" + + MINKIPC_PKCS11_CLIENT_PATH="" + MINKIPC_PKCS11_QTEE_DEVICE="" + MINKIPC_PKCS11_TA_PATH="" + MINKIPC_PKCS11_SFS_UNIT="" + MINKIPC_PKCS11_SUPPLICANT_UNIT="" + MINKIPC_PKCS11_SFS_STARTED_BY_TEST=0 + MINKIPC_PKCS11_SUPPLICANT_STARTED_BY_TEST=0 + MINKIPC_PKCS11_PREP_RESULT="FAIL" + MINKIPC_PKCS11_PREP_MESSAGE="runtime preparation was not completed" + + minkipc_pkcs11_prepare_packages "$mpr_os_id" || return 1 + + if [ -n "$mpr_requested_client" ]; then + case "$mpr_requested_client" in + */*) + MINKIPC_PKCS11_CLIENT_PATH="$mpr_requested_client" + ;; + *) + MINKIPC_PKCS11_CLIENT_PATH=$(command -v "$mpr_requested_client" 2>/dev/null || true) + ;; + esac + else + MINKIPC_PKCS11_CLIENT_PATH=$(command -v xtest_qtee 2>/dev/null || true) + fi + + if [ -z "$MINKIPC_PKCS11_CLIENT_PATH" ] || [ ! -x "$MINKIPC_PKCS11_CLIENT_PATH" ]; then + case "$mpr_os_id" in + debian|ubuntu) + MINKIPC_PKCS11_PREP_RESULT="FAIL" + MINKIPC_PKCS11_PREP_MESSAGE="xtest_qtee is missing after package preparation" + ;; + *) + MINKIPC_PKCS11_PREP_RESULT="SKIP" + MINKIPC_PKCS11_PREP_MESSAGE="xtest_qtee is not provisioned on this image" + ;; + esac + return 1 + fi + log_pass "xtest_qtee binary found: $MINKIPC_PKCS11_CLIENT_PATH" + + for mpr_device in /dev/tee0 /dev/qcomtee /dev/qcomtee0 /dev/smcinvoke; do + if [ -c "$mpr_device" ]; then + MINKIPC_PKCS11_QTEE_DEVICE="$mpr_device" + break + fi + done + + if [ -z "$MINKIPC_PKCS11_QTEE_DEVICE" ]; then + MINKIPC_PKCS11_PREP_RESULT="SKIP" + MINKIPC_PKCS11_PREP_MESSAGE="no QCOMTEE or SMCInvoke character device was found" + return 1 + fi + if [ ! -r "$MINKIPC_PKCS11_QTEE_DEVICE" ] || [ ! -w "$MINKIPC_PKCS11_QTEE_DEVICE" ]; then + MINKIPC_PKCS11_PREP_MESSAGE="current user cannot read and write $MINKIPC_PKCS11_QTEE_DEVICE" + return 1 + fi + log_pass "QTEE device is accessible: $MINKIPC_PKCS11_QTEE_DEVICE" + + MINKIPC_PKCS11_TA_PATH=$(find_qtee_ta "$MINKIPC_PKCS11_TA_UUID" 2>/dev/null || true) + if [ -z "$MINKIPC_PKCS11_TA_PATH" ]; then + MINKIPC_PKCS11_PREP_RESULT="SKIP" + MINKIPC_PKCS11_PREP_MESSAGE="PKCS#11 TA $MINKIPC_PKCS11_TA_UUID is not provisioned under /data or the runtime-compatible /lib/qtee-tas directory" + return 1 + fi + log_pass "PKCS#11 Trusted Application found: $MINKIPC_PKCS11_TA_PATH" + + for mpr_sfs in sfsconfig minkipc-sfsconfig sfs-config; do + if systemd_service_exists "$mpr_sfs"; then + MINKIPC_PKCS11_SFS_UNIT="$mpr_sfs" + break + fi + done + + if [ -n "$MINKIPC_PKCS11_SFS_UNIT" ]; then + if systemd_service_is_active "$MINKIPC_PKCS11_SFS_UNIT"; then + log_pass "Secure-filesystem initialization is active: $MINKIPC_PKCS11_SFS_UNIT" + elif [ "$(id -u)" -eq 0 ] && systemd_service_start_safe "$MINKIPC_PKCS11_SFS_UNIT"; then + MINKIPC_PKCS11_SFS_STARTED_BY_TEST=1 + log_pass "Started secure-filesystem initialization for this test: $MINKIPC_PKCS11_SFS_UNIT" + else + MINKIPC_PKCS11_PREP_MESSAGE="secure-filesystem initialization is not successful: $MINKIPC_PKCS11_SFS_UNIT" + return 1 + fi + else + log_warn "No SFS systemd unit was found, validating secure-storage directories directly" + fi + + for mpr_secure_dir in \ + /var/lib/tee/qtee_supplicant \ + /var/lib/qtee_supplicant/vendor/tzstorage; do + if [ ! -d "$mpr_secure_dir" ]; then + MINKIPC_PKCS11_PREP_MESSAGE="required secure-storage directory is missing: $mpr_secure_dir" + return 1 + fi + log_pass "Secure-storage directory exists: $mpr_secure_dir" + done + + if command -v findmnt >/dev/null 2>&1 && findmnt -n /var/lib/tee >/dev/null 2>&1; then + log_pass "Persistent TEE storage is mounted at /var/lib/tee" + else + log_info "/var/lib/tee is not a separate mount, continuing with the available secure-storage path" + fi + + for mpr_supplicant in qteesupplicant qtee-supplicant minkipc-qteesupplicant; do + if systemd_service_exists "$mpr_supplicant"; then + MINKIPC_PKCS11_SUPPLICANT_UNIT="$mpr_supplicant" + break + fi + done + + mpr_supplicant_running=0 + for mpr_process in qtee_supplicant qteesupplicant; do + if get_pid "$mpr_process" >/dev/null 2>&1; then + mpr_supplicant_running=1 + break + fi + done + + if [ -n "$MINKIPC_PKCS11_SUPPLICANT_UNIT" ]; then + if systemd_service_is_active "$MINKIPC_PKCS11_SUPPLICANT_UNIT" || \ + [ "$mpr_supplicant_running" -eq 1 ]; then + log_pass "qtee_supplicant is active: $MINKIPC_PKCS11_SUPPLICANT_UNIT" + elif [ "$(id -u)" -eq 0 ] && systemd_service_start_safe "$MINKIPC_PKCS11_SUPPLICANT_UNIT"; then + MINKIPC_PKCS11_SUPPLICANT_STARTED_BY_TEST=1 + log_pass "Started qtee_supplicant for this test: $MINKIPC_PKCS11_SUPPLICANT_UNIT" + else + MINKIPC_PKCS11_PREP_MESSAGE="qtee_supplicant could not be started: $MINKIPC_PKCS11_SUPPLICANT_UNIT" + return 1 + fi + elif [ "$mpr_supplicant_running" -eq 1 ]; then + log_pass "qtee_supplicant process is running without a systemd unit" + else + MINKIPC_PKCS11_PREP_MESSAGE="qtee_supplicant is not running and no service unit was found" + return 1 + fi + + MINKIPC_PKCS11_PREP_RESULT="PASS" + MINKIPC_PKCS11_PREP_MESSAGE="runtime preparation completed" + return 0 +} + +# Purpose: Restore services started by minkipc_pkcs11_prepare_runtime(). +# Arguments: +# None. Uses the MINKIPC_PKCS11_*_STARTED_BY_TEST state variables. +# Side effects: +# Stops only services that the current test invocation started. +# Returns: +# 0 after attempting restoration. Restoration failures are logged. +minkipc_pkcs11_restore_runtime() { + if [ "$MINKIPC_PKCS11_SUPPLICANT_STARTED_BY_TEST" -eq 1 ] && \ + [ -n "$MINKIPC_PKCS11_SUPPLICANT_UNIT" ]; then + if systemd_service_stop_safe "$MINKIPC_PKCS11_SUPPLICANT_UNIT"; then + log_info "Restored qtee_supplicant service to its original inactive state" + else + log_warn "Could not restore qtee_supplicant service state: $MINKIPC_PKCS11_SUPPLICANT_UNIT" + fi + MINKIPC_PKCS11_SUPPLICANT_STARTED_BY_TEST=0 + fi + + if [ "$MINKIPC_PKCS11_SFS_STARTED_BY_TEST" -eq 1 ] && \ + [ -n "$MINKIPC_PKCS11_SFS_UNIT" ]; then + if systemd_service_stop_safe "$MINKIPC_PKCS11_SFS_UNIT"; then + log_info "Restored SFS service to its original inactive state" + else + log_warn "Could not restore SFS service state: $MINKIPC_PKCS11_SFS_UNIT" + fi + MINKIPC_PKCS11_SFS_STARTED_BY_TEST=0 + fi + + return 0 +} + +# Purpose: Install signal and exit traps that restore PKCS#11 runtime state. +# Arguments: +# None. +# Side effects: +# Replaces the shell EXIT, HUP, INT, and TERM traps. Callers that need extra +# cleanup may replace only the EXIT trap after calling this function. +# Returns: +# 0 after installing the traps. +minkipc_pkcs11_install_cleanup_traps() { + trap 'minkipc_pkcs11_restore_runtime' EXIT + trap 'exit 129' HUP + trap 'exit 130' INT + trap 'exit 143' TERM + + return 0 +} + +# Purpose: Identify storage-test TAs required by xtest_qtee --clear-storage. +# Arguments: +# None. Uses MINKIPC_PKCS11_STORAGE_TA_UUIDS. +# Output: +# Prints one missing TA UUID per line and prints nothing when all are found. +# Returns: +# 0 after checking every required TA. +minkipc_pkcs11_missing_clear_storage_tas() { + for mmcst_uuid in $MINKIPC_PKCS11_STORAGE_TA_UUIDS; do + if ! find_qtee_ta "$mmcst_uuid" >/dev/null 2>&1; then + printf '%s\n' "$mmcst_uuid" + fi + done + + return 0 +} + +# Purpose: Parse the final case and subtest summaries from an xtest_qtee log. +# Arguments: +# $1 - Readable xtest_qtee output log. +# Outputs: +# Sets MINKIPC_PKCS11_CASE_TOTAL, MINKIPC_PKCS11_CASE_FAILED, +# MINKIPC_PKCS11_SUBTEST_TOTAL, and MINKIPC_PKCS11_SUBTEST_FAILED. +# Returns: +# 0 after parsing. Missing summaries are represented by empty output values. +minkipc_pkcs11_parse_summary() { + mps_log="$1" + + mps_case_values=$(awk ' + /test case(s)? of which [0-9]+ failed/ { + for (i = 1; i <= NF; i++) { + if ($i == "test" && $(i + 1) ~ /^case/) { + total = $(i - 1) + failed = $(i + 4) + } + } + } + END { + if (total != "") + print total, failed + } + ' "$mps_log") + + mps_subtest_values=$(awk ' + /subtest(s)? of which [0-9]+ failed/ { + for (i = 1; i <= NF; i++) { + if ($i ~ /^subtests?$/) { + total = $(i - 1) + failed = $(i + 3) + } + } + } + END { + if (total != "") + print total, failed + } + ' "$mps_log") + + MINKIPC_PKCS11_CASE_TOTAL=$(printf '%s\n' "$mps_case_values" | awk '{print $1}') + MINKIPC_PKCS11_CASE_FAILED=$(printf '%s\n' "$mps_case_values" | awk '{print $2}') + MINKIPC_PKCS11_SUBTEST_TOTAL=$(printf '%s\n' "$mps_subtest_values" | awk '{print $1}') + MINKIPC_PKCS11_SUBTEST_FAILED=$(printf '%s\n' "$mps_subtest_values" | awk '{print $2}') + + return 0 +} + +# Purpose: Report expected top-level PKCS#11 cases that did not report OK. +# Arguments: +# $1 - Readable xtest_qtee output log. +# $2 - Optional space-separated case ID manifest. The QTEE manifest is used +# when this argument is omitted or empty. +# Output: +# Prints one missing case ID per line. +# Returns: +# 0 after checking the complete manifest. +minkipc_pkcs11_missing_expected_cases() { + mmec_log="$1" + mmec_expected="${2:-$MINKIPC_PKCS11_EXPECTED_CASES}" + + # Intentional splitting of the validated space-separated case manifest. + # shellcheck disable=SC2086 + for mmec_case in $mmec_expected; do + if ! grep -Eq "^[[:space:]]*pkcs11_${mmec_case}[[:space:]]+OK[[:space:]]*$" "$mmec_log"; then + printf '%s\n' "$mmec_case" + fi + done + + return 0 +} + +# Purpose: Validate an xtest_qtee PKCS#11 summary and expected case manifest. +# Arguments: +# $1 - Readable xtest_qtee output log. +# $2 - Optional space-separated case manifest. Pass "-" to validate only +# the summary without enforcing specific case IDs. +# Outputs: +# Sets parsed summary variables and MINKIPC_PKCS11_VALIDATION_MESSAGE. +# Returns: +# 0 when the run is valid, otherwise 1 with the failure reason in +# MINKIPC_PKCS11_VALIDATION_MESSAGE. +minkipc_pkcs11_validate_log() { + mvl_log="$1" + mvl_expected="${2:-$MINKIPC_PKCS11_EXPECTED_CASES}" + MINKIPC_PKCS11_VALIDATION_MESSAGE="" + + minkipc_pkcs11_parse_summary "$mvl_log" + + if [ -z "$MINKIPC_PKCS11_CASE_TOTAL" ] || \ + [ -z "$MINKIPC_PKCS11_CASE_FAILED" ] || \ + [ -z "$MINKIPC_PKCS11_SUBTEST_TOTAL" ] || \ + [ -z "$MINKIPC_PKCS11_SUBTEST_FAILED" ]; then + MINKIPC_PKCS11_VALIDATION_MESSAGE="xtest_qtee did not emit a complete test summary" + return 1 + fi + + if [ "$MINKIPC_PKCS11_CASE_FAILED" -ne 0 ] || \ + [ "$MINKIPC_PKCS11_SUBTEST_FAILED" -ne 0 ]; then + MINKIPC_PKCS11_VALIDATION_MESSAGE="summary reports cases=$MINKIPC_PKCS11_CASE_TOTAL failed_cases=$MINKIPC_PKCS11_CASE_FAILED subtests=$MINKIPC_PKCS11_SUBTEST_TOTAL failed_subtests=$MINKIPC_PKCS11_SUBTEST_FAILED" + return 1 + fi + + if [ "$MINKIPC_PKCS11_CASE_TOTAL" -eq 0 ]; then + MINKIPC_PKCS11_VALIDATION_MESSAGE="the supplied filters selected no QTEE PKCS#11 test cases" + return 1 + fi + + if [ "$mvl_expected" != "-" ]; then + mvl_missing=$(minkipc_pkcs11_missing_expected_cases "$mvl_log" "$mvl_expected") + if [ -n "$mvl_missing" ]; then + mvl_missing=$(printf '%s\n' "$mvl_missing" | tr '\n' ' ' | sed 's/[[:space:]]*$//') + MINKIPC_PKCS11_VALIDATION_MESSAGE="expected QTEE PKCS#11 cases did not pass: $mvl_missing" + return 1 + fi + fi + + MINKIPC_PKCS11_VALIDATION_MESSAGE="cases=$MINKIPC_PKCS11_CASE_TOTAL subtests=$MINKIPC_PKCS11_SUBTEST_TOTAL" + return 0 +} + +# Purpose: Scan the kernel log for QCOMTEE, SMCInvoke, and RPMB errors. +# Arguments: +# $1 - Directory where dmesg scan artifacts will be stored. +# Side effects: +# Writes artifacts using the shared scan_dmesg_errors() helper. +# Returns: +# 0 when relevant errors are found, otherwise 1. This matches the existing +# scan_dmesg_errors() return convention. +minkipc_pkcs11_scan_dmesg() { + scan_dmesg_errors \ + "$1" \ + 'qcomtee|qcom_tee|qcom-tee|qtee|smcinvoke|rpmb' \ + 'optional|not supported|deferred probe|B689F2A7-8ADF-477A-9F99-32E90C0AD0A2|731E279E-AAFB-4575-A771-38CAA6F0CCA6' +} + +# Purpose: Record, log, and exit with the final result of a PKCS#11 test. +# Arguments: +# $1 - PASS, SKIP, or FAIL result. +# $2 - Human-readable result message. +# $3 - Process exit status. +# Expected globals: +# TESTNAME - Test name written to logs and the result file. +# RES_FILE - Result-file path. +# Side effects: +# Writes the result file and restores services started by the test. +# Returns: +# Does not return. Exits with the status supplied in $3. +finish_test() { + ft_result="$1" + ft_message="$2" + ft_rc="$3" + + case "$ft_result" in + PASS) + log_pass "$TESTNAME PASS: $ft_message" + ;; + SKIP) + log_skip "$TESTNAME SKIP: $ft_message" + ;; + *) + ft_result="FAIL" + log_fail "$TESTNAME FAIL: $ft_message" + ;; + esac + + echo "$TESTNAME $ft_result" > "$RES_FILE" + minkipc_pkcs11_restore_runtime + exit "$ft_rc" +} + +# Purpose: Record an argument or configuration failure before test execution. +# Arguments: +# $1 - Human-readable failure message. +# Expected globals: +# TESTNAME - Test name written to logs and the result file. +# RES_FILE - Result-file path. +# Side effects: +# Delegates result logging, result-file creation, and exit to finish_test(). +# Returns: +# Does not return. Records FAIL and exits with status 1. +write_early_failure() { + finish_test FAIL "$1" 1 +}