Skip to content

Every leg at its line's patched OTP floor; FIPS on 28.5.0.8 on x86_64 and aarch64; the box leg - #90

Merged
HackTuah merged 2 commits into
mainfrom
slice/046-otp-floor-and-arm64
Oct 20, 2026
Merged

HackTuah merged 2 commits into
mainfrom
slice/046-otp-floor-and-arm64

Conversation

@HackTuah

Copy link
Copy Markdown
Member

This change is CI and pinning only. Nothing a consumer of the package can hit changes, and mix.exs's supported floor stays OTP 27.

CVE-2026-55737 (heap corruption in binary_to_term/1) is fixed in OTP 27.3.4.15, 28.5.0.4 and 29.0.4. The gate's legs already resolve releases above those floors, but nothing asserted it. The FIPS leg built 28.1.1, which is below its floor.

What changes

  • Every leg is held to its line's patched floor. tools/otp-floors records the floors. tools/otp_floor.sh reads the running release's OTP_VERSION and refuses a runtime below its line's floor. It is the gate's second step, so the gate now has seventeen steps, and the FIPS job runs it after its build.
  • The FIPS leg moves to OTP 28.5.0.8 with the same OpenSSL 3.5.8 and the same 3.1.2 provider (CMVP #4985). It runs on x86_64 and on aarch64 (ubuntu-24.04-arm).
  • A fourth gate leg, box: OTP 28.5.0.8 with Elixir 1.20.4, exact versions, on ubuntu-24.04-arm. These are the versions and the architecture a fielded edge box runs. Minutes for a public repository are free on both architectures.
  • .tool-versions pins erlang 28.5.0.8.
  • A fix: after a body edit, the summary job's check (tools/ci_legs_verdict.sh) found legs only by whole-number OTP versions. It never saw the box leg (OTP 28.5.0.8), so a red box leg would have passed. The pattern now accepts a dotted version, and the check requires four legs.

Evidence

  • tools/otp_floor.sh: OTP 28.1.1 is refused; 28.5.0.8 and 29.1 pass.
  • tools/probe_ci_legs_verdict.sh: the new case S9 (only the box leg is red) failed before the pattern fix and passes after it; S1 to S8 still pass.
  • The local gate on OTP 28.5.0.8 with Elixir 1.18.4 prints Gate OK.

Not yet measured

  • The box leg and both aarch64 jobs. This run is their first measurement, and the resolved toolchain lines will be quoted from its logs.

…rchitectures; the box leg

CVE-2026-55737 (heap corruption in binary_to_term/1 decoding
LARGE_TUPLE_EXT) is fixed in OTP 27.3.4.15, 28.5.0.4 and 29.0.4. Gate run
37704440253 on 336ce2b resolved 27.3.4.18, 28.5.0.7 and 29.1.1, all above
those floors, but nothing asserted it, so a later resolution below a floor
would have passed. The FIPS leg built 28.1.1, which is below its floor.

- tools/otp-floors records each line's lowest patched release.
  tools/otp_floor.sh reads the running release's OTP_VERSION file
  (otp_release gives only the major, so 28.1.1 and 28.5.0.8 both read
  "28"). It refuses a runtime below its line's floor and a line older
  than every recorded one. The gate runs it as its second step, which
  makes seventeen steps. The FIPS job runs no gate, so it runs the script
  after its build.
- The FIPS leg builds OTP 28.5.0.8 from source (sha256 from the release's
  SHA256.txt), with the same OpenSSL 3.5.8 and 3.1.2 provider as before.
  It runs on ubuntu-24.04 and on ubuntu-24.04-arm, and each architecture
  has its own cache.
- A fourth gate leg, "box", runs OTP 28.5.0.8 with Elixir 1.20.4
  (version-type: strict) on ubuntu-24.04-arm: the versions and the
  architecture a fielded edge box runs. Minutes for a public repository
  are free on both architectures. The Dialyzer cache key now includes the
  architecture.
- .tool-versions: erlang 28.5.0.8.

The summary job's check after a body edit (tools/ci_legs_verdict.sh)
found legs by the pattern "OTP [0-9]+", which does not match the box
leg's "OTP 28.5.0.8". A red box leg was therefore left out and the check
passed. The pattern now accepts a dotted version, and LEGS_MIN is 4. The
probe's new case S9, where only the box leg is red, failed before the fix:

    S9 the box leg alone is red   exit 0 (want 1); last: pass: 3 leg(s) completed, all success

and passes after it (exit 1, "a leg's latest verdict ... is not success").

Measured locally:

    28.1.1:   below the patched floor 28.5.0.4 for line 28 (CVE-2026-55737); refused
    28.5.0.8: at or above the patched floor 28.5.0.4 for line 28
    29.1:     at or above the patched floor 29.0.4 for line 29

The gate on OTP 28.5.0.8 with Elixir 1.18.4 printed "Gate OK" with every
step passing. On OTP 28.5.0.8 with Elixir 1.20.4 (x86_64), the run was
stopped after ten steps, all of which had passed. Neither the box leg nor
either FIPS job has run on aarch64 yet; this pull request's first CI run
is that measurement.

README, CONTRIBUTING, docs/governance.md and docs/assurance-case.md now
say seventeen steps and four legs. docs/fips.md names 28.5.0.8 and both
architectures, and labels its 2026-09-23 figures as measured on 28.1.1,
x86_64.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
The box leg of gate run 39324309977 (push, 44d23d2) failed
http_test.exs:1679 on an error line that readme_claims_test.exs logged at
the same moment. ExUnit.CaptureLog hands every capture the events of
every process, and Elixir's own docs warn that an async test may capture
another test's messages. The four `refute log =~ "[error]"` checks in
"an exception the adapter raises goes back to the adapter as itself" were
open to that leak. The same commit's pull_request run passed with a
different seed.

own_log/1 captures with `metadata: [:pid]` and keeps only the entries
logged by the calling process. Every Logger.error in http.ex on these
paths runs in the process that called HTTP.call/2, which here is the
test process, so the refutes still see everything the transport logs.

Measured locally beside a temporary async module that logs an error every
2 ms:

    before (capture_log):  117 tests, 1 failure   (the other module's line)
    after (own_log/1):     117 tests, 0 failures
    mutant (http.ex logs before handing the exception back, own_log/1):
                           117 tests, 3 failures

Signed-off-by: Ayla Croft <aylacroft@proton.me>
@HackTuah
HackTuah merged commit abe79c4 into main Oct 20, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant