diff --git a/scripts/fetch_mbedtls_4k.sh b/scripts/fetch_mbedtls_4k.sh index f1e43c77..462773e6 100755 --- a/scripts/fetch_mbedtls_4k.sh +++ b/scripts/fetch_mbedtls_4k.sh @@ -28,10 +28,13 @@ if [ ! -f "$MANIFEST" ]; then fi # Manifest lines: . Blank lines and # comments ignored. +# The "platform" and "stock:" lines bind the archives to a framework version; +# they are the build check's business, not ours, and are not architectures. expected="$(awk -v a="$ARCH" '$1 == a && $0 !~ /^#/ {print $2" "$3}' "$MANIFEST")" if [ -z "$expected" ]; then echo "error: no manifest entries for arch '$ARCH'" >&2 - echo "known arches: $(awk '$0 !~ /^#/ && NF {print $1}' "$MANIFEST" | sort -u | tr '\n' ' ')" >&2 + echo "known arches: $(awk '$0 !~ /^#/ && NF && $1 != "platform" && $1 !~ /^stock:/ {print $1}' \ + "$MANIFEST" | sort -u | tr '\n' ' ')" >&2 exit 1 fi diff --git a/scripts/mbedtls_4k.py b/scripts/mbedtls_4k.py index 6718cae4..29cf0e3b 100644 --- a/scripts/mbedtls_4k.py +++ b/scripts/mbedtls_4k.py @@ -10,16 +10,21 @@ The archives lower the mbedTLS outbound record buffer from 16 KiB to 4 KiB, savi buffer stays at 16 KiB, so the contiguous allocation a handshake needs is unchanged — this buys headroom, it does not move that floor. See docs/mbedtls-tls-footprint.md. -Two failure modes this guards against, both of which produce a firmware that looks -fine and silently lacks the change: +Three failure modes this guards against, each of which produces a firmware that looks +fine and is silently wrong: - a -L pointing at a missing or partial directory. The linker ignores an unusable search path and quietly resolves mbedTLS from the framework instead. - archives that do not match the manifest, e.g. left over from an earlier platform version. + - a manifest and archives that agree with each other but not with the installed + framework, which is what a platform bump without a rebuild leaves behind. That + one links cleanly and drifts on struct layout at runtime. -So the opt-in path verifies every archive by sha256 before the build, and after the -link re-reads firmware.map to confirm every libmbed*.a came from our directory. +So the opt-in path verifies every archive by sha256 before the build, checks the +framework's own mbedTLS archives still fingerprint as the ones these were built +against, and after the link re-reads firmware.map to confirm every libmbed*.a came +from our directory. """ Import("env") @@ -54,15 +59,44 @@ def _manifest(project_dir, arch): if not os.path.isfile(path): _fail("missing scripts/mbedtls_4k_manifest.txt") wanted = {} + stock = {} + platform_id = "" with open(path) as fh: for line in fh: line = line.strip() if not line or line.startswith("#"): continue parts = line.split() - if len(parts) == 3 and parts[0] == arch: + if len(parts) == 2 and parts[0] == "platform": + platform_id = parts[1] + elif len(parts) == 3 and parts[0] == "stock:" + arch: + stock[parts[2]] = parts[1] + elif len(parts) == 3 and parts[0] == arch: wanted[parts[2]] = parts[1] - return wanted + return wanted, stock, platform_id + + +# Where each espressif32 generation keeps the archives we displace. First directory +# holding all four wins, so this resolves without knowing which platform is in play. +FRAMEWORK_LIB_DIRS = ( + ("framework-arduinoespressif32", "tools/sdk/%s/lib"), + ("framework-arduinoespressif32-libs", "%s/lib"), + ("framework-arduinoespressif32", "tools/esp32-arduino-libs/%s/lib"), +) + + +def _framework_lib_dir(platform, arch): + for package, layout in FRAMEWORK_LIB_DIRS: + try: + base = platform.get_package_dir(package) + except Exception: + base = None + if not base: + continue + path = os.path.join(base, *(layout % arch).split("/")) + if all(os.path.isfile(os.path.join(path, name)) for name in REQUIRED): + return path + return None if os.environ.get("MESHCORE_REDUCED_TLS", "") not in ("1", "true", "yes"): @@ -77,7 +111,7 @@ staged = os.path.join(project_dir, ".mbedtls-4k", arch) if not os.path.isdir(staged): _fail("no archives for %s at %s" % (arch, staged)) -wanted = _manifest(project_dir, arch) +wanted, stock, manifest_platform = _manifest(project_dir, arch) if not wanted: _fail("manifest has no entries for arch '%s'" % arch) @@ -95,6 +129,47 @@ for name in REQUIRED: % (archive, wanted[name], actual) ) +# Bind the staged archives to the framework they were built against. The hashes above +# only prove the staged files are the ones the manifest names; they say nothing about +# whether the manifest is still current. Bump the platform without rebuilding, and +# every check above still passes while the link takes mbedTLS built against a +# different IDF — a struct-layout drift that corrupts silently at runtime. So +# fingerprint the framework's own archives, the ones being displaced: if those moved, +# the staged pair is stale by construction. +platform = env.PioPlatform() +platform_id = "%s@%s" % (platform.name, platform.version) +stock_dir = _framework_lib_dir(platform, arch) +if stock_dir is None: + _fail("cannot locate the framework's own mbedTLS archives for %s, so the staged " + "ones cannot be tied to a framework version" % arch) +if not stock: + _fail("manifest has no stock:%s fingerprints — it predates the framework binding.\n" + "Add these lines for the framework now installed (%s):\n%s" + % (arch, platform_id, + "\n".join("stock:%s %s %s" % (arch, _sha256(os.path.join(stock_dir, n)), n) + for n in REQUIRED))) + +for name in REQUIRED: + actual = _sha256(os.path.join(stock_dir, name)) + if name not in stock: + _fail("manifest has no stock:%s entry for %s" % (arch, name)) + if actual != stock[name]: + _fail( + "the framework's mbedTLS archives are not the ones these were built against.\n" + " %s\n manifest %s\n installed %s\n" + "Manifest records %s; installed is %s.\n" + "Rebuild the reduced-TLS archives against this framework " + "(docs/mbedtls-tls-footprint.md), then update every hash in the manifest." + % (os.path.join(stock_dir, name), stock[name], actual, + manifest_platform or "no platform", platform_id) + ) + +if manifest_platform and manifest_platform != platform_id: + # Hashes are the check; the version string is orientation. Identical archives + # under a renamed platform are not a compatibility problem. + print("reduced-TLS: manifest says %s, installed is %s — archives match, so this is " + "only a stale label" % (manifest_platform, platform_id)) + # Prepend so these satisfy mbedTLS symbols ahead of the framework's own copies: # the linker takes each archive member from the first archive that resolves it. env.Prepend(LIBPATH=[staged]) diff --git a/scripts/mbedtls_4k_manifest.txt b/scripts/mbedtls_4k_manifest.txt index 06c0f385..5ac608a6 100644 --- a/scripts/mbedtls_4k_manifest.txt +++ b/scripts/mbedtls_4k_manifest.txt @@ -10,3 +10,14 @@ esp32s3 01629f635b33ffa2c1fdfcd8ac52327cd3a92e8d4e7c9a32d92974e0cfdfe398 libmbed esp32s3 07e7a09847589fefc35bc8d2f739d556535d535acd7185a9824b2af8edd5b05a libmbedtls_2.a esp32s3 e12bcc8d76a368e819987f266e73c265178d6b6675f05d0e29014bb543c402af libmbedtls.a esp32s3 c1e10324e19f6d7763737f72d9032cec832bdfce60c7a7d381a5fcfc7dcad573 libmbedx509.a + +# Framework binding, read by scripts/mbedtls_4k.py and ignored by the fetch script. +# "stock:" lines fingerprint the framework's own archives — the ones the entries above +# displace. They are what makes "rebuild on every platform bump" enforceable instead of +# advisory: if the framework's copies move, the entries above are stale by construction +# and the build stops. Regenerate both sets together, never one alone. +platform espressif32@6.11.0 +stock:esp32s3 abdaf759ee17aa697468427b55a86c6c0082ac4cdeb643a63d8b3ac2df324633 libmbedcrypto.a +stock:esp32s3 9a580d2ff7c885e1bf59479a14422e3d10a485ae9083acb5db943df714c0ac35 libmbedtls_2.a +stock:esp32s3 a0f331245feb5cf4fe8d9f99b03d34d64f10e38d8a8d680e35b34ec82aa3cf9e libmbedtls.a +stock:esp32s3 3d56277224b066118b6c48a973ae18fdf8078448bf54b482ac5557699e52f223 libmbedx509.a