4"""Verify the NSC Secure-Gateway veneer slot offsets in a linked ELF.
6The tz_nsc_cgc_usb Non-Secure image reaches each NSC CGC veneer by NAME, not by
7address: ``ns_main.c`` declares the bare ``ra8_nsc_cgc_*`` prototypes and calls
8them, and the CMSE import library the Secure link emits (``--cmse-implib
9--out-implib``, on this image's link line) binds those names to the
10Secure-Gateway stub addresses. That import library is derived FROM the Secure
11ELF, so the byte offset of each veneer inside the ``.gnu.sgstubs`` region IS the
12ABI contract between the two worlds: reorder the stubs and every bound
13NS->Secure call lands on a different entry point, with no diagnostic.
15This post-build check reads the FINAL Secure symbol table (where ``nm`` reports
16the real SG-stub addresses) and fails the build if any veneer has drifted from
17its pinned offset in ``EXPECTED_OFFSETS`` below. A drift means the Secure ELF no
18longer matches the offsets this table records -- and therefore no longer matches
19the import library the NS image was bound against; re-derive the offsets from
20the link and update the table. (There is no ``k_sg_off_*`` enum to keep in step
21any more: the NS side gave up hard-coded offsets for the import library.)
23The veneer set is REQUIRED, not optional, once the ELF has an NSC region. Both
24callers -- the ``tz_nsc_cgc_usb.elf`` POST_BUILD command and the ``sg-offsets``
25gate -- pick that ELF precisely BECAUSE it binds all three ``ra8_nsc_cgc_*``
26veneers (its CMakeLists passes ``NSC_SRCS ra8_nsc_cgc.c``, whose three
27``RA8_NSC_VENEER`` definitions are the whole point of the app). So an absent
28veneer there is a broken secure gateway, not a build configuration, and the
29old ``any(...)`` guard turned exactly that defect into ``skipped.`` + exit 0.
30Only an ELF with no ``g_ra8_ls_sgstubs_start`` at all -- a link whose script
31never placed ``.gnu.sgstubs`` -- is still skipped.
34 check_sg_offsets.py <elf> [--nm <nm-binary>]
37 0 -- offsets match, or the ELF has no NSC region at all
38 1 -- a veneer slot drifted, or a required veneer is missing from the link
39 2 -- usage / tool error, or a symbol table too small to trust (SYMBOL_FLOOR)
51 "ra8_nsc_cgc_get_clock_hz": 0,
52 "ra8_nsc_cgc_usbfs_clock_enable": 8,
53 "ra8_nsc_cgc_pll2_enable": 16,
55BASE_SYMBOL =
"g_ra8_ls_sgstubs_start"
56THUMB_MASK = 0xFFFFFFFE
70def read_symbols(elf: str, nm: str) -> dict[str, int]:
71 """Return {symbol: address} for every defined symbol in ``elf``."""
73 [nm, elf], capture_output=
True, text=
True, check=
True
75 syms: dict[str, int] = {}
76 for line
in out.splitlines():
78 if len(parts) == NM_FIELD_COUNT
and parts[1]
in (
"T",
"t",
"R",
"r",
"D",
"d",
"B",
"b"):
79 with contextlib.suppress(ValueError):
80 syms[parts[2]] = int(parts[0], 16)
84def offset_drift(syms: dict[str, int]) -> list[str]:
85 """Return every missing or displaced required veneer from a symbol table."""
86 base = syms[BASE_SYMBOL]
88 for sym, want
in EXPECTED_OFFSETS.items():
90 drift.append(f
" {sym}: MISSING from the link")
92 got = (syms[sym] & THUMB_MASK) - base
94 drift.append(f
" {sym}: at sgstubs+{got} (expected sgstubs+{want})")
99 """Prove exact Thumb offsets pass while one drift and one absence fire."""
103 **{name: base + offset + 1
for name, offset
in EXPECTED_OFFSETS.items()},
106 missing = next(iter(EXPECTED_OFFSETS))
108 shifted = next(name
for name
in EXPECTED_OFFSETS
if name != missing)
110 good_findings = offset_drift(good)
111 bad_findings = offset_drift(bad)
112 expected_bad_findings = 2
114 (
not good_findings,
"exact Thumb-normalized veneer offsets stay quiet"),
116 len(bad_findings) == expected_bad_findings
117 and any(
"MISSING" in item
for item
in bad_findings)
118 and any(
"expected" in item
for item
in bad_findings),
119 "a missing veneer and a displaced veneer both fire",
122 failed = [label
for passed, label
in cases
if not passed]
123 for passed, label
in cases:
124 print(f
" [{'ok' if passed else 'FAIL'}] {label}")
126 print(f
"check_sg_offsets.py --selftest: {len(failed)} failure(s)", file=sys.stderr)
128 print(
"check_sg_offsets.py --selftest: all cases pass (both directions).")
133 """Verify the secure-gateway veneers sit at their pinned offsets in an ELF.
135 The offsets are ABI: non-secure code reaches the secure world by branching
136 into the SG region at a fixed distance from its base, so a veneer moving
137 silently redirects a call to a different entry point. Only the offset from
138 BASE_SYMBOL is compared, never absolute addresses, since the region as a
139 whole is free to relocate between builds.
141 Symbol values are masked with THUMB_MASK before subtracting -- every Thumb
142 function symbol carries bit 0 set, and comparing raw values would make
143 every offset off by one.
145 Only an ELF with no ``g_ra8_ls_sgstubs_start`` at all is skipped -- a link
146 whose script never placed ``.gnu.sgstubs``, where there is no NSC region to
147 have drifted. Once that base symbol exists, EVERY veneer in
148 EXPECTED_OFFSETS is required: the callers hand this the one ELF that binds
149 all three by construction, so a missing one is a broken secure gateway. The
150 previous ``any(...)`` guard reported that defect as ``skipped.`` and
153 SYMBOL_FLOOR guards the layer beneath both: a parse that yields almost no
154 symbols makes every lookup miss, which the skip branch would then read as
155 "no veneers present".
157 Returns 0 when every offset matches or the ELF has no NSC region, 1 on
158 drift or a missing veneer, 2 when ``nm`` could not be run at all or its
159 output fell below SYMBOL_FLOOR.
161 ap = argparse.ArgumentParser()
162 ap.add_argument(
"elf", nargs=
"?")
163 ap.add_argument(
"--nm", default=
"arm-none-eabi-nm")
164 ap.add_argument(
"--selftest", action=
"store_true")
165 args = ap.parse_args()
168 if args.elf
is not None or args.nm !=
"arm-none-eabi-nm":
169 ap.error(
"--selftest does not accept an ELF or --nm")
172 ap.error(
"the following arguments are required: elf")
174 nm = shutil.which(args.nm)
or args.nm
176 syms = read_symbols(args.elf, nm)
177 except (OSError, subprocess.CalledProcessError)
as exc:
178 print(f
"check_sg_offsets: cannot run nm on {args.elf}: {exc}", file=sys.stderr)
181 if len(syms) < SYMBOL_FLOOR:
183 f
"check_sg_offsets: FATAL -- only {len(syms)} defined symbol(s) read from "
184 f
"{args.elf}, floor is {SYMBOL_FLOOR}. A collapsed symbol table reports "
185 "'no SG veneers present' because every lookup missed.",
190 if BASE_SYMBOL
not in syms:
194 print(
"check_sg_offsets: no NSC region in this ELF -- skipped.")
197 drift = offset_drift(syms)
200 print(
"check_sg_offsets: FATAL -- NSC SG-veneer slot drift detected.", file=sys.stderr)
201 print(
"\n".join(drift), file=sys.stderr)
203 "Re-derive the offsets from the Secure link and update EXPECTED_OFFSETS here.",
208 print(
"check_sg_offsets: NSC SG-veneer slot offsets OK (0/8/16).")
212if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.