build(tls): bind the reduced-TLS archives to the framework they were built against
The manifest said "rebuild these for every espressif32 platform bump" and nothing enforced it. mbedtls_4k.py verified the staged archives against the manifest's own hashes, which proves the pair agrees with itself and nothing more: bump the platform without rebuilding and every check still passes while the link takes mbedTLS built against a different IDF. That fails at runtime on struct-layout drift, not at the link, which is the failure the mechanism claimed to prevent. Fingerprint the framework's own mbedTLS archives — the ones ours displace — as stock: lines in the manifest and check them before the build. If the framework moves, the staged pair is stale by construction and the build stops with the replacement hashes printed ready to paste. Stronger than comparing a version string: framework-arduinoespressif32 versions independently of the platform, and its archives are what actually has to match. The lib directory is resolved by trying the layouts espressif32 has used rather than hardcoding one, and failing closed if none holds all four archives. The fetch script ignores the new lines; its known-arches hint skips them so they cannot be reported as architectures.
This commit is contained in:
1 parent
2173794966
commit
74a3df3206
3 files changed
+97
-8
No files matched your search
@@ -28,10 +28,13 @@ if [ ! -f "$MANIFEST" ]; then
|
||||
fi
|
||||
|
||||
# Manifest lines: <arch> <sha256> <filename>. Blank lines and # comments ignored.
|
||||
# The "platform" and "stock:<arch>" 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
|
||||
|
||||
|
||||
+82
-7
@@ -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])
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user