4"""Ban silent error discards at TrustZone boot boundaries.
6C23 makes an explicit ``(void)`` cast the sanctioned suppression for a
7``[[nodiscard]]`` diagnostic, so ``-Wall -Wextra -Werror`` can never flag
9 (void)ra8_cgc_init(); /* discarded right before BLXNS */
11even though the callee is ``[[nodiscard]] ra8_err_t``. That exact pattern
12shipped a Secure boot that would BLXNS into the Non-secure image on a dead
13clock tree (tracker issue #191, T2-05), and it recurred after being fixed
14once because per-app boot files are copied from siblings. The compiler is
15structurally unable to police the void-cast; this gate is the backstop.
17Rules (first-party C/C++ only):
19 A. The TrustZone world-switch API family is must-handle EVERYWHERE: a
20 ``(void)``-cast discard of any ``ra8_tz_secure_boot_*()`` call is
21 banned in every first-party file. A returned error from that family
22 is a root-of-trust DENIAL or a rejected NS vector table -- ignoring
23 it means treating an unauthenticated NS world as live.
25 B. Inside a boot translation unit -- any ``.c`` that defines
26 ``SystemInit`` or ``ra8_trustzone_init`` -- a ``(void)``-cast discard
27 of ANY ``ra8_*()`` call is banned. Every fallible step in those TUs
28 gates the S->NS world switch; a tolerated failure must be an explicit
29 ``if`` (halt, or a documented S-side fallback) or
30 ``RA8_ERROR_CHECK`` / ``RA8_ERROR_CHECK_NO_ABORT``, never a silent
34 C / C++ sources (.c .h .cpp .hpp) under libs/, src/, tests/, examples/,
35 port/, tools/. Vendored SOUP (libs/third_party/*) and generated font
36 data (libs/ra8_fonts/*) are exempt, as are build trees.
39 * Comment lines / prose (a match whose cast sits inside a // or /*
40 comment does not count).
41 * Function-pointer discards like ``(void)ra8_trustzone_init;`` (no call
42 parentheses) -- those silence -Wunused, they do not discard an error.
43 * Any line carrying `TZ-DISCARD-OK: <reason>` (reason text required).
45Exit 0 when clean, 1 on any discard, 2 when the whole-tree sweep collapses
46below FILE_FLOOR (see the constant).
49from __future__
import annotations
54from pathlib
import Path
56sys.path.insert(0, str(Path(__file__).resolve().parent))
58from lint_targets
import is_build_output_path
60ROOTS = (
"libs",
"tests",
"examples",
"port",
"tools",
"apps")
61EXTS = (
".c",
".h",
".cpp",
".hpp")
62EXEMPT_DIRS = (
"/third_party/",
"/ra8_fonts/")
65FAMILY_RE = re.compile(
r"\(\s*void\s*\)\s*(ra8_tz_secure_boot_[a-z0-9_]+)\s*\(")
67ANY_RA8_RE = re.compile(
r"\(\s*void\s*\)\s*(ra8_[a-z0-9_]+)\s*\(")
70BOOT_TU_RE = re.compile(
71 r"^\s*void\s+(?:SystemInit|ra8_trustzone_init)\s*\(\s*void\s*\)", re.MULTILINE
73WAIVER_RE = re.compile(
r"TZ-DISCARD-OK:\s*\S")
84def _is_comment_pos(line: str, pos: int) -> bool:
85 """True if column `pos` sits inside a // or /* comment on this line."""
86 stripped = line.lstrip()
87 if stripped.startswith((
"*",
"//",
"/*")):
93 return "/*" in before
and "*/" not in before
96def discover() -> list[str]:
97 """Every boot-boundary source file, for the whole-tree sweep."""
101 if not base.is_dir():
104 for f
in base.rglob(f
"*{ext}"):
106 if is_build_output_path(s)
or any(d
in s
for d
in EXEMPT_DIRS):
112def check_file(path: str) -> list[tuple[int, str, str]]:
113 """Return (line, rule, snippet) findings for one file."""
114 findings: list[tuple[int, str, str]] = []
116 text = Path(path).read_text(encoding=
"utf-8")
117 except (OSError, UnicodeDecodeError):
119 if "(void)" not in text.replace(
" ",
""):
121 boot_tu = path.endswith(
".c")
and bool(BOOT_TU_RE.search(text))
122 rules: list[tuple[re.Pattern[str], str]] = [(FAMILY_RE,
"A")]
124 rules.append((ANY_RA8_RE,
"B"))
125 lines = text.splitlines()
126 for i, raw
in enumerate(lines, 1):
129 if re.search(
r"\(\s*void\s*\)\s*$", raw)
and i < len(lines):
130 line = raw + lines[i]
131 if WAIVER_RE.search(line):
133 seen: set[tuple[int, int]] = set()
134 for rule_re, rule
in rules:
135 for m
in rule_re.finditer(line):
136 span = (m.start(), m.end())
137 if span
in seen
or _is_comment_pos(line, m.start()):
140 findings.append((i, rule, raw.strip()[:100]))
144def selftest() -> int:
145 """Prove both boundary discard rules fire and handled/waived calls stay quiet."""
146 with tempfile.TemporaryDirectory(prefix=
"tz-discard-selftest-")
as raw:
148 family_bad = root /
"ordinary.c"
149 boot_bad = root /
"boot.c"
150 good = root /
"good.c"
151 family_bad.write_text(
152 "void f(void) { (void)ra8_tz_secure_boot_verify(); }\n", encoding=
"ascii"
154 boot_bad.write_text(
"void SystemInit(void) { (void)ra8_cgc_init(); }\n", encoding=
"ascii")
156 "void SystemInit(void) { if (ra8_cgc_init() != k_ra8_ok) { halt(); } }\n"
157 "void f(void) { (void)ra8_tz_secure_boot_verify(); } "
158 "/* TZ-DISCARD-OK: synthetic documented fallback */\n",
161 bad_findings = check_file(str(family_bad)) + check_file(str(boot_bad))
162 good_findings = check_file(str(good))
165 {rule
for _line, rule, _snippet
in bad_findings} == {
"A",
"B"},
166 "world-switch and boot-translation-unit discards both fire",
168 (
not good_findings,
"handled results and exact reasoned waiver stay quiet"),
170 failed = [label
for passed, label
in cases
if not passed]
171 for passed, label
in cases:
172 print(f
" [{'ok' if passed else 'FAIL'}] {label}")
174 print(f
"check_tz_boundary_discard.py --selftest: {len(failed)} failure(s)", file=sys.stderr)
176 print(
"check_tz_boundary_discard.py --selftest: all cases pass (both directions).")
181 """Fail when a TrustZone boot-boundary call discards its ra8_err_t.
183 These call sites are the worst place in the tree to drop a status: a
184 failed SAU or MPU configuration returns an error and then, ignored, hands
185 control to the non-secure world with the boundary not actually in force.
186 The build links, the board boots, and the isolation is simply absent.
188 FILE_FLOOR is enforced on the whole-tree sweep only, and exits 2 below it.
189 An explicit argv file list is a deliberately narrowed scope -- not a
190 collapsed one -- so it is exempt; the sweep is where a broken enumeration
191 would silently report the tree clean.
193 Returns 1 listing each discard, 0 when clean, 2 when the whole-tree sweep
194 enumerated too few files to trust.
196 raw_args = sys.argv[1:]
197 if raw_args == [
"--selftest"]:
199 if any(arg.startswith(
"-")
for arg
in raw_args):
200 print(
"usage: check_tz_boundary_discard.py [--selftest] [file ...]", file=sys.stderr)
203 whole_tree =
not args
204 files = args
or discover()
205 if whole_tree
and len(files) < FILE_FLOOR:
207 f
"check_tz_boundary_discard.py: FATAL -- only {len(files)} first-party source "
208 f
"file(s) in scope, floor is {FILE_FLOOR}. A collapsed sweep reports a "
209 "clean tree because it scanned nothing.",
214 for path
in sorted(set(files)):
216 not path.endswith(EXTS)
217 or is_build_output_path(path)
218 or any(d
in path
for d
in EXEMPT_DIRS)
221 for ln, rule, snippet
in check_file(path):
223 "world-switch result discarded" if rule ==
"A" else "boot-TU ra8_* result discarded"
226 f
"{path}:{ln}: [rule {rule}] {what} -- handle the ra8_err_t "
227 f
"(halt or a documented fallback; RA8_ERROR_CHECK[_NO_ABORT]); "
228 f
"never (void)-cast it at a TrustZone boot boundary; {snippet}"
233 f
"\ncheck_tz_boundary_discard: {total} violation(s). A (void)-cast "
234 f
"silences [[nodiscard]] by ISO C23 rule, so -Werror cannot catch "
235 f
"these; check the result and fail safe instead (add "
236 f
"`TZ-DISCARD-OK: <reason>` only for a justified exception)."
240 "check_tz_boundary_discard: clean -- no silent ra8_err_t discards at "
241 "TrustZone boot boundaries."
246if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.