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
6
An ``RA8_NSC_VENEER`` (a ``cmse_nonsecure_entry`` Secure-Gateway veneer) is the
7
ONLY path a Non-Secure caller has into the Secure world. A declaration in the
8
public 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
10
misleads Non-Secure integrators about the available trust-boundary surface, and
11
because a veneer with no caller also has no link error, such a phantom decl can
12
sit 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
15
This gate parses every ``RA8_NSC_VENEER`` function declaration out of the header
16
and fails if any lacks a definition in the ``ra8_nsc`` sources. There is no
17
allowlist: either implement the veneer or delete the declaration.
18
19
Run::
20
21
check_nsc_veneer_defs.py
22
23
Exit status: 0 if every declared veneer has a definition, 1 otherwise.
24
"""
25
26
import
re
27
import
sys
28
import
tempfile
29
from
pathlib
import
Path
30
31
# Repo root: this file is scripts/checks/check_nsc_veneer_defs.py .
32
REPO_ROOT = Path(__file__).resolve().parents[2]
33
HEADER = REPO_ROOT /
"libs"
/
"ra8_nsc"
/
"inc"
/
"ra8_nsc.h"
34
SRC_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.
38
DECL_RE = re.compile(
r"RA8_NSC_VENEER\s+\w[\w\s\*]*?\b(ra8_nsc_\w+)\s*\("
)
39
40
41
def
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
51
def
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
68
def
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
101
def
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
144
if
__name__ ==
"__main__"
:
145
sys.exit(
main
())
main
void main(void)
The application entry point Reset_Handler hands control to.
Definition
main.c:298
scripts
checks
check_nsc_veneer_defs.py
Generated by
1.16.1