ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
check_nsc_veneer_defs.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Gate: every RA8_NSC_VENEER declared in ra8_nsc.h shall have a definition.
5
6An ``RA8_NSC_VENEER`` (a ``cmse_nonsecure_entry`` Secure-Gateway veneer) is the
7ONLY path a Non-Secure caller has into the Secure world. A declaration in the
8public header ``libs/ra8_nsc/inc/ra8_nsc.h`` with no matching definition under
9``libs/ra8_nsc/src/`` advertises an NS->S entry point that does not exist: it
10misleads Non-Secure integrators about the available trust-boundary surface, and
11because a veneer with no caller also has no link error, such a phantom decl can
12sit in the header indefinitely (this is how ``ra8_nsc_key_import`` and
13``ra8_nsc_trng_read`` accumulated -- see security-trustzone-audit F16/A4).
14
15This gate parses every ``RA8_NSC_VENEER`` function declaration out of the header
16and fails if any lacks a definition in the ``ra8_nsc`` sources. There is no
17allowlist: either implement the veneer or delete the declaration.
18
19Run::
20
21 check_nsc_veneer_defs.py
22
23Exit status: 0 if every declared veneer has a definition, 1 otherwise.
24"""
25
26import re
27import sys
28import tempfile
29from pathlib import Path
30
31# Repo root: this file is scripts/checks/check_nsc_veneer_defs.py .
32REPO_ROOT = Path(__file__).resolve().parents[2]
33HEADER = REPO_ROOT / "libs" / "ra8_nsc" / "inc" / "ra8_nsc.h"
34SRC_DIR = REPO_ROOT / "libs" / "ra8_nsc" / "src"
35
36# A declaration: [[nodiscard]] RA8_NSC_VENEER ra8_err_t ra8_nsc_foo(...
37# We only need the function name that follows the RA8_NSC_VENEER return type.
38DECL_RE = re.compile(r"RA8_NSC_VENEER\s+\w[\w\s\*]*?\b(ra8_nsc_\w+)\s*\‍(")
39
40
41def declared_veneers(header: Path) -> list[str]:
42 """Return the veneer function names declared in the header, in order."""
43 text = header.read_text(encoding="utf-8")
44 seen: list[str] = []
45 for name in DECL_RE.findall(text):
46 if name not in seen:
47 seen.append(name)
48 return seen
49
50
51def is_defined(name: str, sources: list[Path]) -> bool:
52 """Whether a definition of ``name`` exists in any ra8_nsc source file.
53
54 A definition in a .c is the function signature followed (eventually) by a
55 body; a bare declaration lives only in the header, so any ``name(`` at
56 statement start in a source file is a definition.
57 """
58 # Match the definition head by its `RA8_NSC_VENEER` prefix -- the same anchor
59 # DECL_RE uses on the header -- so the return type is irrelevant: the
60 # `ra8_err_t` veneers and the `void` ra8_nsc_wdt_refresh are found alike. The
61 # `[\w\s\*]` operand class cannot cross a `(` / `;` / `{`, so the span never
62 # reaches from one veneer's prefix to another's name, and a call site (which
63 # carries no RA8_NSC_VENEER prefix) never matches.
64 def_re = re.compile(r"RA8_NSC_VENEER\s+\w[\w\s\*]*?\b" + re.escape(name) + r"\s*\‍(")
65 return any(def_re.search(src.read_text(encoding="utf-8")) for src in sources)
66
67
68def selftest() -> int:
69 """Prove a phantom veneer fires and a matching definition stays quiet."""
70 with tempfile.TemporaryDirectory(prefix="nsc-veneer-selftest-") as raw:
71 root = Path(raw)
72 header = root / "ra8_nsc.h"
73 source = root / "ra8_nsc.c"
74 header.write_text(
75 "RA8_NSC_VENEER ra8_err_t ra8_nsc_defined(void);\n"
76 "RA8_NSC_VENEER void ra8_nsc_phantom(void);\n",
77 encoding="ascii",
78 )
79 source.write_text(
80 "RA8_NSC_VENEER ra8_err_t ra8_nsc_defined(void) { return 0; }\n"
81 "void caller(void) { ra8_nsc_phantom(); }\n",
82 encoding="ascii",
83 )
84 declared = declared_veneers(header)
85 defined = [name for name in declared if is_defined(name, [source])]
86 missing = [name for name in declared if not is_defined(name, [source])]
87 cases = (
88 (defined == ["ra8_nsc_defined"], "matching veneer definition stays quiet"),
89 (missing == ["ra8_nsc_phantom"], "call-only phantom veneer fires"),
90 )
91 failed = [label for passed, label in cases if not passed]
92 for passed, label in cases:
93 print(f" [{'ok' if passed else 'FAIL'}] {label}")
94 if failed:
95 print(f"check_nsc_veneer_defs.py --selftest: {len(failed)} failure(s)", file=sys.stderr)
96 return 1
97 print("check_nsc_veneer_defs.py --selftest: all cases pass (both directions).")
98 return 0
99
100
101def main() -> int:
102 """Fail when a declared RA8_NSC_VENEER has no definition in the NSC sources.
103
104 The asymmetry is the point: an undefined veneer is a TrustZone entry point
105 the public header advertises to the non-secure world and the secure world
106 cannot service. That is a trust hazard, not a link error to be discovered
107 later, so it is checked at the declaration rather than left to the linker.
108
109 A missing header exits 1 rather than 0 -- with nothing to parse there are
110 zero declarations, and "zero declared, zero missing" would pass while
111 verifying nothing.
112
113 Returns 0 when every declaration has a definition, 1 on a phantom veneer
114 or a missing header.
115 """
116 args = sys.argv[1:]
117 if args == ["--selftest"]:
118 return selftest()
119 if args:
120 print("usage: check_nsc_veneer_defs.py [--selftest]", file=sys.stderr)
121 return 2
122 if not HEADER.is_file():
123 print(f"check_nsc_veneer_defs.py: header not found: {HEADER}", file=sys.stderr)
124 return 1
125 sources = sorted(SRC_DIR.glob("*.c"))
126 veneers = declared_veneers(HEADER)
127 missing = [name for name in veneers if not is_defined(name, sources)]
128
129 hdr = HEADER.relative_to(REPO_ROOT)
130 src = SRC_DIR.relative_to(REPO_ROOT)
131 if missing:
132 print("check_nsc_veneer_defs.py: RA8_NSC_VENEER declared without a definition:")
133 for name in missing:
134 print(f" {name}: declared in {hdr}, no definition in {src}/")
135 print("Fix each at the root -- implement the veneer, or delete the declaration.")
136 print("A phantom NS->S entry point in the public header is a trust hazard.")
137 return 1
138
139 count = len(veneers)
140 print(f"check_nsc_veneer_defs.py: PASS -- all {count} RA8_NSC_VENEER declaration(s) defined.")
141 return 0
142
143
144if __name__ == "__main__":
145 sys.exit(main())
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298