Enforce crash-safe build policy
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.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user