Add Phase 9C security hardening
Generate exact-hash SDK source overrides without modifying dependencies. Harden SSH allocation and algorithm policy, tighten web authentication cleanup, and add focused host contract tests and documentation.
This commit is contained in:
@@ -8,12 +8,16 @@ from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HERE = Path(__file__).resolve().parent
|
||||
VENDOR = ROOT / "managed_components/wolfssl__wolfssh"
|
||||
REVIEWED_SHA256 = "81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9"
|
||||
sys.dont_write_bytecode = True
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
from security_overrides import ENTRIES, render_entry
|
||||
|
||||
|
||||
def extract(source, name):
|
||||
@@ -34,10 +38,24 @@ def extract(source, name):
|
||||
return source[start:end] + "\n"
|
||||
|
||||
|
||||
def check_build_profile(database):
|
||||
def check_build_profile(database, override, expected):
|
||||
entries = json.loads(database.read_text())
|
||||
entry = next(e for e in entries if Path(e["file"]).resolve() ==
|
||||
(VENDOR / "src/internal.c").resolve())
|
||||
|
||||
def source_path(entry):
|
||||
path = Path(entry["file"])
|
||||
return (Path(entry["directory"]) / path).resolve()
|
||||
|
||||
original = (VENDOR / "src/internal.c").resolve()
|
||||
if any(source_path(e) == original for e in entries):
|
||||
raise RuntimeError("Production still compiles original internal.c; reconfigure the build")
|
||||
suffix = ("security_overrides", override.name, "internal.c")
|
||||
matches = [e for e in entries if source_path(e).parts[-3:] == suffix]
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError(f"Expected one generated wolfSSH compile entry, found {len(matches)}")
|
||||
entry = matches[0]
|
||||
actual = source_path(entry).read_bytes()
|
||||
if actual != expected:
|
||||
raise RuntimeError("Generated wolfSSH source differs from render_entry; reconfigure the build")
|
||||
args = entry.get("arguments") or shlex.split(entry["command"])
|
||||
# Strip output/dependency-writing flags: this must only preprocess to stdout.
|
||||
clean = []
|
||||
@@ -59,7 +77,8 @@ def check_build_profile(database):
|
||||
"WOLFSSH_NO_ED25519", "NO_FAILURE_ON_REJECTED")
|
||||
if "WOLFSSH_NO_RSA" not in macros or any(m in macros for m in absent):
|
||||
raise RuntimeError("Resolved wolfSSH auth feature profile changed; re-audit")
|
||||
print("PASS: actual compiler preprocessing matches reviewed auth feature profile", flush=True)
|
||||
print("PASS: generated source equals render_entry; actual compiler preprocessing matches reviewed auth feature profile", flush=True)
|
||||
return actual.decode()
|
||||
|
||||
|
||||
def main():
|
||||
@@ -83,12 +102,17 @@ def main():
|
||||
if not re.search(r'^\s*wolfssl/wolfssh:\s*"1\.4\.20"\s*$', manifest, re.M):
|
||||
raise RuntimeError("Application must pin wolfSSH exactly to 1.4.20")
|
||||
|
||||
overrides = [e for e in ENTRIES if e.component == "wolfssl__wolfssh" and
|
||||
e.source == "managed_components/wolfssl__wolfssh/src/internal.c"]
|
||||
if len(overrides) != 1 or overrides[0].root != "project" or overrides[0].sha256 != REVIEWED_SHA256:
|
||||
raise RuntimeError("Expected one independently pinned project wolfSSH override")
|
||||
override = overrides[0]
|
||||
_, expected = render_entry(override, {"project": ROOT})
|
||||
if options.host_only:
|
||||
print("SKIP: production feature verification (--host-only)", flush=True)
|
||||
print("SKIP: production generated-source/feature verification (--host-only); testing render_entry output", flush=True)
|
||||
source = expected.decode()
|
||||
else:
|
||||
check_build_profile(options.compile_commands)
|
||||
|
||||
source = raw.decode()
|
||||
source = check_build_profile(options.compile_commands, override, expected)
|
||||
# Use the installed public callback data layouts, not hand-maintained copies.
|
||||
header = (VENDOR / "wolfssh/ssh.h").read_text()
|
||||
types = header[header.index("typedef struct WS_UserAuthData_Password {"):
|
||||
@@ -101,14 +125,19 @@ def main():
|
||||
"DoUserAuthRequestPassword", "DoUserAuthRequestPublicKey",
|
||||
"SendUserAuthKeyboardRequest", "DoUserAuthRequest", "GetAllowedAuth",
|
||||
"SendChannelData"]
|
||||
extracted = "\n".join(extract(source, name) for name in names)
|
||||
# Exercise the installed nonoptimizable wipe, not a memset replacement.
|
||||
misc = (VENDOR / "src/misc.c").read_text()
|
||||
wipe = extract(misc.replace("STATIC INLINE void ForceZero", "static void ForceZero"), "ForceZero")
|
||||
if "volatile byte*" not in wipe:
|
||||
raise RuntimeError("ForceZero implementation changed; re-audit")
|
||||
extracted = wipe + "\n" + "\n".join(extract(source, name) for name in names)
|
||||
with tempfile.TemporaryDirectory(prefix="wolfssh-auth-contract-") as temp:
|
||||
temp = Path(temp)
|
||||
(temp / "auth_types.h").write_text(types)
|
||||
(temp / "actual.c").write_text(extracted)
|
||||
binary = temp / "contract"
|
||||
cc = shlex.split(os.environ.get("CC", "cc"))
|
||||
subprocess.run(cc + ["-std=c99", "-Wall", "-Wextra", "-Werror",
|
||||
subprocess.run(cc + ["-std=c99", "-O2", "-Wall", "-Wextra", "-Werror",
|
||||
"-Wno-unused-parameter", "-I", str(temp),
|
||||
str(HERE / "contract.c"), "-o", str(binary)],
|
||||
check=True, timeout=30, env={**os.environ, "CCACHE_DISABLE": "1"})
|
||||
|
||||
Reference in New Issue
Block a user