From 9a48837cda38c57bf1b9132ba61c7bae93f09858 Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 20 Jul 2026 10:50:55 +0100 Subject: [PATCH] python3-improv: report secure_boot from U-Boot sec_boot flag (imx93-jaguar-eink) sec.secure_boot previously always returned "unknown". This kernel exposes no userspace ELE lifecycle path (no /dev/ele_mu, no lifecycle nvmem cell; the ELE "get info" lifecycle sits behind OP-TEE/secure world), so the authoritative OEM_OPEN/OEM_CLOSED value still needs a dedicated OP-TEE TA (tracked follow-up, roadmap P0-1/P2-1). As the best available userspace signal, read U-Boot's `sec_boot` env flag (via fw_printenv), which the NXP boot script uses to gate authenticated boot (auth_os): yes => "closed", no => "open", else "unknown". This reflects the bootloader configuration, not an ELE-attested lifecycle, and remains self-reported/untrusted until attestation (P2-1). A fuse/OCOTP byte heuristic is deliberately avoided (the ELE-OCOTP shadow has non-zero UID/config words). Verified on eink-0167: fw_printenv -n sec_boot => "no" => secure_boot "open". Co-authored-by: Cursor --- .../imx93-jaguar-eink/onboarding-server.py | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/recipes-devtools/python/python3-improv/imx93-jaguar-eink/onboarding-server.py b/recipes-devtools/python/python3-improv/imx93-jaguar-eink/onboarding-server.py index a0573262..a63c251e 100644 --- a/recipes-devtools/python/python3-improv/imx93-jaguar-eink/onboarding-server.py +++ b/recipes-devtools/python/python3-improv/imx93-jaguar-eink/onboarding-server.py @@ -384,18 +384,46 @@ def compute_time_status(): def _read_secure_boot(): - """i.MX93 secure-boot / lifecycle state. - - Returns 'open' | 'closed' | 'unknown'. Correctly determining this requires - the ELE "get info" lifecycle field (OEM_OPEN vs OEM_CLOSED / FIELD_RETURN) - or interpreting specific SRK-hash / SEC_CONFIG fuses via the validated - i.MX93 OCOTP map — the raw ELE-OCOTP0 shadow cannot be interpreted safely - (every SoC has non-zero fuses: UID, boot config, ...), so a byte-level - heuristic would misreport a fused board. Until that ELE query lands - (tracked follow-up; see roadmap P0-1/P2-1 and BLE-BOARD-PROFILE.md §10) we - report 'unknown' rather than a value that could be wrong. This field is - self-reported and untrusted until backed by ELE attestation (P2-1). + """i.MX93 secure-boot posture. Returns 'open' | 'closed' | 'unknown'. + + The authoritative source is the EdgeLock Enclave (ELE) "get info" + lifecycle field (OEM_OPEN vs OEM_CLOSED / FIELD_RETURN). This kernel, + however, exposes no ELE MU char device (`/dev/ele_mu` absent) and no + lifecycle nvmem cell — the lifecycle lives behind OP-TEE/secure world — + so it cannot be read from Linux userspace without a dedicated OP-TEE TA. + That ELE-attested query remains a tracked follow-up (roadmap P0-1/P2-1, + BLE-BOARD-PROFILE.md §10) and needs secure-world plumbing. + + As the best available userspace signal we read U-Boot's `sec_boot` + environment flag, which the NXP boot script uses to gate authenticated + boot (`auth_os`): 'yes' => authenticated boot enforced ('closed'), + 'no' => not enforced ('open'). This reflects the *bootloader + configuration* rather than an ELE-attested hardware lifecycle, and — + like the whole `sec` block — is self-reported and untrusted until backed + by attestation (P2-1). A fuse/OCOTP byte heuristic is deliberately + avoided: the ELE-OCOTP0 shadow has non-zero UID/config words, so + byte-level guessing would misreport a fused board. """ + val = "" + try: + # `-n` prints just the value; falls back to parsing `k=v` for older + # u-boot-fw-utils that lack `-n`. + out = subprocess.run(["fw_printenv", "-n", "sec_boot"], + capture_output=True, timeout=4, text=True) + if out.returncode == 0: + val = out.stdout.strip().lower() + else: + out = subprocess.run(["fw_printenv", "sec_boot"], + capture_output=True, timeout=4, text=True) + if out.returncode == 0 and "=" in out.stdout: + val = out.stdout.strip().split("=", 1)[1].strip().lower() + except Exception as e: + logger.debug(f"secure_boot read failed: {e}") + return "unknown" + if val in ("yes", "1", "true"): + return "closed" + if val in ("no", "0", "false"): + return "open" return "unknown"