Add compile-time checks for silent reboot, disabled core dumps, and disabled debugger-aware panic handling. Include regression coverage, hardening guidance, and update Phase 8/9 project status.
89 lines
3.5 KiB
Python
89 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile the production crash-policy guard against safe and unsafe configs."""
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / "src/security_build_policy.c"
|
|
REQUIRED = (
|
|
"CONFIG_ESP_COREDUMP_ENABLE_TO_NONE",
|
|
"CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT",
|
|
)
|
|
FORBIDDEN = (
|
|
"CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH",
|
|
"CONFIG_ESP_COREDUMP_ENABLE_TO_UART",
|
|
"CONFIG_ESP_COREDUMP_ENABLE",
|
|
"CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT",
|
|
"CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT",
|
|
"CONFIG_ESP_SYSTEM_PANIC_GDBSTUB",
|
|
"CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME",
|
|
"CONFIG_ESP_DEBUG_OCDAWARE",
|
|
"CONFIG_FREERTOS_DEBUG_OCDAWARE",
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--sdkconfig-header", type=Path,
|
|
help="also check an actual generated sdkconfig.h")
|
|
args = parser.parse_args()
|
|
compiler = shlex.split(os.environ.get("CC", "cc"))
|
|
count = 0
|
|
|
|
with tempfile.TemporaryDirectory(prefix="security-build-policy-") as directory:
|
|
header = Path(directory) / "sdkconfig.h"
|
|
|
|
def check(name, config, accepted):
|
|
nonlocal count
|
|
header.write_text(config)
|
|
result = subprocess.run(
|
|
compiler + ["-std=c11", "-Wall", "-Wextra", "-Werror",
|
|
"-fsyntax-only", "-I", directory, str(SOURCE)],
|
|
capture_output=True, text=True, timeout=20,
|
|
)
|
|
if (result.returncode == 0) != accepted:
|
|
raise AssertionError(f"{name}: unexpected compiler result\n{result.stderr}")
|
|
if not accepted and "Security policy:" not in result.stderr:
|
|
raise AssertionError(f"{name}: failed for an unrelated reason\n{result.stderr}")
|
|
count += 1
|
|
|
|
def defines(values):
|
|
return "".join(f"#define {name} {value}\n" for name, value in values.items())
|
|
|
|
baseline = dict.fromkeys(REQUIRED, 1)
|
|
check("supported baseline", defines(baseline), True)
|
|
check("explicit disabled options", defines(baseline | dict.fromkeys(FORBIDDEN, 0)), True)
|
|
check("missing config", "", False)
|
|
for name in REQUIRED:
|
|
missing = baseline.copy()
|
|
del missing[name]
|
|
check(f"missing {name}", defines(missing), False)
|
|
check(f"disabled {name}", defines(baseline | {name: 0}), False)
|
|
for name in FORBIDDEN:
|
|
check(f"enabled {name}", defines(baseline | {name: 1}), False)
|
|
|
|
# Verify tracked defaults select the policy, rather than just accepting
|
|
# a synthetic fixture. Disabled Kconfig booleans are absent from headers.
|
|
defaults = (ROOT / "sdkconfig.defaults").read_text()
|
|
values = {}
|
|
for line in defaults.splitlines():
|
|
if line.startswith("CONFIG_") and "=" in line:
|
|
name, value = line.split("=", 1)
|
|
if name in REQUIRED + FORBIDDEN:
|
|
values[name] = 1 if value == "y" else 0
|
|
check("tracked defaults", defines(values), True)
|
|
if '"security_build_policy.c"' not in (ROOT / "src/CMakeLists.txt").read_text():
|
|
raise AssertionError("production build does not register policy source")
|
|
if args.sdkconfig_header:
|
|
check("resolved SDK configuration", args.sdkconfig_header.read_text(), True)
|
|
|
|
print(f"PASS: {count} compile-policy cases; production source registered")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|