From 9f697dc83077856ecde6822fb2cf0d0a041479c1 Mon Sep 17 00:00:00 2001 From: Samueru-sama Date: Mon, 7 Sep 2026 23:30:39 -0400 Subject: [PATCH 1/4] Add E101: the default build must ask for no CET flag The case builds gl-fwd.so by the default recipe and again with -fcf-protection=full asked for, and requires the default to come out with strictly fewer endbr64. A default build that requested the flag would tie with the flag arm and fail here. Run against the tree before the change it governs, it fails as predicted: both arms tie at 3478, the table reports 63 matched and 1 mismatched. Stage 3 now installs make, and the required-tool check names it. --- experiments/30-run-tests.sh | 40 +++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/experiments/30-run-tests.sh b/experiments/30-run-tests.sh index 878b399..4893239 100644 --- a/experiments/30-run-tests.sh +++ b/experiments/30-run-tests.sh @@ -31,9 +31,9 @@ printf '%s\n' 'deb http://archive.debian.org/debian bullseye main' \ rm -f /etc/apt/sources.list.d/*.sources 2>/dev/null || true apt-get update -qq -o Acquire::Check-Valid-Until=false -o Acquire::Retries=3 \ >/work/.apt-update.log 2>&1 || true -apt-get install -y -qq -o Acquire::Retries=3 gcc binutils python3 \ +apt-get install -y -qq -o Acquire::Retries=3 gcc binutils python3 make \ >/work/.apt-install.log 2>&1 || true -for _tool in gcc python3 readelf; do +for _tool in gcc python3 readelf make; do command -v "$_tool" >/dev/null 2>&1 || { echo "STAGE 3 CANNOT RUN: $_tool did not install; the apt output follows" >&2 sed 's/^/ update| /' /work/.apt-update.log >&2 @@ -407,6 +407,42 @@ else run E88 OK "appdir : $PWD/app_old" \ env CROSS_LIBC_DLOPEN_ROOT="$PWD/app_old" ./runtime-select-strict --probe + # ---- the default build asks for no CET flag ---------------------------- + # + # ⛔ -fcf-protection=full does no protective work here, measured in + # docs/report/09-the-second-boundary.md 9.13: it adds six endbr64 to the + # shims and cannot produce the IBT property note, because glibc's crti.o + # carries no property and the linker ANDs that absence across the link. + # The default recipe therefore passes no CET flag, and E101 is the case + # that keeps that true: the SAME sources built by the default recipe and + # by the same recipe with the flag asked for must NOT come out identical, + # because a default build that asked for the flag would tie with the flag + # arm and fail here. Measured on bullseye's gcc 10.2: 3472 against 3478. + # ⚠ x86-64 only. endbr64 does not exist on the other architectures, and + # on aarch64 gcc asking for the flag is a hard error rather than a + # warning, so there is no flag arm to compare against. + if [ "$(uname -m)" = x86_64 ]; then + rm -rf srcbuild && mkdir srcbuild + cp /repo/src/Makefile /repo/src/gl-fwd.c /repo/src/gl-fwd-gl.h \ + /repo/src/ld-conf.h /repo/src/cld-env.h srcbuild/ \ + 2>"$BERR" || bfail "E101 sources" + make -C srcbuild gl-fwd.so >/dev/null 2>"$BERR" \ + || bfail "gl-fwd.so (the default recipe)" + n_def=$(objdump -d srcbuild/gl-fwd.so | grep -c endbr64) + mv srcbuild/gl-fwd.so srcbuild/gl-fwd-default.so + make -C srcbuild gl-fwd.so CET_CFLAGS='-fcf-protection=full' \ + >/dev/null 2>"$BERR" || bfail "gl-fwd.so (the flag arm)" + n_flag=$(objdump -d srcbuild/gl-fwd.so | grep -c endbr64) + run E101 OK "fewer endbr64 than the flag arm" \ + env N_DEF="$n_def" N_FLAG="$n_flag" sh -c \ + '[ "$N_DEF" -lt "$N_FLAG" ] && + echo "fewer endbr64 than the flag arm: default $N_DEF, asked for: $N_FLAG"' + else + echo " E101 SKIPPED - endbr64 is an x86 instruction, and asking" + echo " this architecture's gcc for -fcf-protection=full is a" + echo " hard error rather than a warning." + fi + # ---- H. the version-binding trap, and the forwarders that close it ---- # # T3.2 was blamed on glibc-vs-musl ABI differences for a long time. It is From 7a01eb3e4ac04001aa371199f1556184e87cb663 Mon Sep 17 00:00:00 2001 From: Samueru-sama Date: Mon, 7 Sep 2026 23:30:56 -0400 Subject: [PATCH 2/4] Ship one variant and stop asking for CET The release shipped two builds of every object because one consumer wanted a strict environment, and the default asked for -fcf-protection=full, which measured as doing no protective work here: it adds six endbr64 to the shims and cannot produce the IBT property note, because glibc's crti.o carries no property and the linker ANDs that absence across the link (report 9.13). The release matrix now builds the default only, six jobs instead of twelve. make portable stays as the packager's strict-environment target, differing from the default by -DCLD_STRICT_ENV and nothing else; the Makefile's CET probe is gone and the flag stays askable with make CET_CFLAGS=-fcf-protection=full. The APPDIR fallback stays: upstream's own AppImage relies on it, and quick-sharun sets CROSS_LIBC_DLOPEN_ROOT itself, which wins when both are set. E101 now passes: the default build carries 3472 endbr64 against the flag arm's 3478, and the suite reports 64/64 on x86-64. Every one-home record moved with the total: report 01, 08, 09 and 10, and the list in gates.yml. verify-gates.sh carried the same list claiming to be identical to gates.yml and had drifted to 53/53 50/50; both now say 64/64 60/60. build.sh and build-in-env.sh lose the CLD_NO_CET plumbing, and building.md, integrating.md and the generated release body describe one variant. --- .github/workflows/gates.yml | 2 +- .github/workflows/release.yml | 39 ++- docs/building.md | 42 ++-- docs/integrating.md | 16 +- docs/report/01-summary.md | 2 +- docs/report/08-test-results.md | 11 +- docs/report/09-the-second-boundary.md | 2 +- docs/report/10-measured-versus-assumed.md | 2 +- docs/todo/PROGRESS.md | 291 ++++++---------------- scripts/build-in-env.sh | 16 +- scripts/build.sh | 20 +- scripts/release-notes.sh | 16 +- scripts/verify-artifacts.sh | 55 ++-- scripts/verify-gates.sh | 2 +- src/Makefile | 34 +-- 15 files changed, 190 insertions(+), 360 deletions(-) diff --git a/.github/workflows/gates.yml b/.github/workflows/gates.yml index 4d536e2..75d7ffa 100644 --- a/.github/workflows/gates.yml +++ b/.github/workflows/gates.yml @@ -150,7 +150,7 @@ jobs: - name: every headline number has exactly one home run: | fail=0 - for n in 3470 358 63/63 60/60 45/45 40/40 26/26; do + for n in 3470 358 64/64 60/60 45/45 40/40 26/26; do homes=$(git grep -lF "$n" -- '*.md' ':(exclude)docs/history/*' | sed 's|^docs/report/.*|docs/report/|' | sort -u | tr '\n' ' ') count=$(printf '%s' "$homes" | wc -w) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96c4a8d..1777037 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,16 +95,15 @@ jobs: # x86-64 job with "Exec format error". docs/building.md says what it cost. runs-on: ubuntu-latest timeout-minutes: 30 - # ⭐ Two variants, so nobody has to build one by hand. `default` reads - # APPDIR as well as CROSS_LIBC_DLOPEN_ROOT, because an AppImage runtime - # exports APPDIR on its own. `portable` reads only this project's name, - # which is what a consumer who wants one spelling asked for. - # src/cld-env.h has the argument; E87 and E88 measure both arms. + # ⭐ One variant ships. It reads CROSS_LIBC_DLOPEN_ROOT with APPDIR as the + # fallback, because an AppImage runtime exports APPDIR on its own; the + # strict build that reads only this project's name stays a build-time + # choice (`cd src && make portable`), which is where the choice belongs. + # src/cld-env.h has the argument; E87 and E88 measure the strict arm. strategy: fail-fast: false matrix: arch: [x86_64, aarch64, riscv64, ppc64, ppc64le, loongarch64] - variant: [default, portable] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -118,13 +117,8 @@ jobs: - name: build on the floor run: | set -eu - if [ "${{ matrix.variant }}" = portable ]; then - sh scripts/build.sh --engine docker --arch ${{ matrix.arch }} --portable - echo "BUILD_DIR=${{ matrix.arch }}-portable" >> "$GITHUB_ENV" - else - sh scripts/build.sh --engine docker --arch ${{ matrix.arch }} - echo "BUILD_DIR=${{ matrix.arch }}" >> "$GITHUB_ENV" - fi + sh scripts/build.sh --engine docker --arch ${{ matrix.arch }} + echo "BUILD_DIR=${{ matrix.arch }}" >> "$GITHUB_ENV" - name: the manifest run: cat "build/$BUILD_DIR/build-manifest.json" @@ -139,7 +133,7 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: release-${{ matrix.arch }}-${{ matrix.variant }} + name: release-${{ matrix.arch }} path: | dist/ build/*/build-manifest.json @@ -194,7 +188,7 @@ jobs: # added above without widening this list would ship silently as a # release without that architecture, which is exactly the defect this # step exists to refuse. - - name: collect every architecture and variant + - name: collect every architecture run: | set -eu arches="x86_64 aarch64 riscv64 ppc64 ppc64le loongarch64" @@ -208,12 +202,10 @@ jobs: done mkdir -p dist build for a in $arches; do - for v in default portable; do - src=staged/release-$a-$v - [ -d "$src/dist" ] || { echo "no dist/ for $a/$v"; exit 1; } - cp -a "$src/dist/." dist/ - cp -a "$src/build/." build/ - done + src=staged/release-$a + [ -d "$src/dist" ] || { echo "no dist/ for $a"; exit 1; } + cp -a "$src/dist/." dist/ + cp -a "$src/build/." build/ done ls -la dist echo "--- manifests ---" @@ -229,9 +221,8 @@ jobs: run: | set -eu fail=0 - # ⚠ Only the DEFAULT variant ships loose files to check this way. - # The portable set ships as archives, and package-release.sh already - # checked every object in it against its manifest before archiving. + # ⚠ The default build ships loose files, and there is one variant + # now; every downloaded manifest has its loose set checked here. for a in x86_64 aarch64 riscv64 ppc64 ppc64le loongarch64; do man=build/$a/build-manifest.json for f in $(jq -r '.artifacts | keys[]' "$man"); do diff --git a/docs/building.md b/docs/building.md index a636935..ef7ddb4 100644 --- a/docs/building.md +++ b/docs/building.md @@ -119,33 +119,37 @@ cd src && make portable ``` It is `sh scripts/build.sh --portable` with the orchestration taken away, and -it produces the same objects: built with `-DCLD_STRICT_ENV`, and without -`-fcf-protection=full`. +it produces the same objects as the default build except for one flag: +`-DCLD_STRICT_ENV`. The release ships the default build only; the strict +build stays a build-time choice, because whoever assembles the bundle knows +whether an AppImage runtime is going to export `APPDIR` into the process and +a library cannot. E87 and E88 in `experiments/30-run-tests.sh` measure both +arms. ⛔ **`make portable` says nothing about which glibc you build on.** The floor rule above still decides whether the result loads. -### What the two flags do +### The two flags | flag | effect | |---|---| -| `-DCLD_STRICT_ENV` | the objects read `CROSS_LIBC_DLOPEN_ROOT` and ignore `APPDIR`. The default reads both, because an AppImage runtime exports `APPDIR` into every process it starts, and a consumer who wants one spelling asked for this | -| no `-fcf-protection=full` | the build stops REQUESTING CET | - -⚠ **Dropping the CET flag removes the request, not always the instructions.** A -toolchain that enables CET by default still emits `endbr64`, and that is the -distribution's choice rather than this project's. Measured on a gcc whose -`-Q --help=common` reports `-fcf-protection=full`: the default and portable -builds carry 202 each, identical. The flag is dropped because it does no -protective work here, which +| `-DCLD_STRICT_ENV` | the objects read `CROSS_LIBC_DLOPEN_ROOT` and ignore `APPDIR`. The default reads both, because an AppImage runtime exports `APPDIR` into every process it starts, and `CROSS_LIBC_DLOPEN_ROOT` wins when both are set | +| `-fcf-protection=full` | none by default: no build here asks for CET. It stays askable with `make CET_CFLAGS=-fcf-protection=full` | + +⛔ **No build here requests CET, and that is a measurement rather than an +omission.** The flag adds six `endbr64` to the shims and cannot produce the +IBT property note, because glibc's `crti.o` carries no property on any floor +image and the linker ANDs that absence across the link. Without the note a +CET-enforcing loader turns indirect-branch tracking off for the whole +process, so the instructions the flag emits are ones no loader will honour. [`report/09-the-second-boundary.md`](report/09-the-second-boundary.md) 9.13 -measures, and because a toolchain that does not support it treats being asked -as a hard error. +has the table, and E101 in `experiments/30-run-tests.sh` is the case that +keeps the default honest. -⚠ **The Makefile now asks the compiler rather than assuming from the -architecture.** Targeting x86 is not the same as supporting the flag, and the -architecture test alone let an unsupported flag reach a compiler that refuses -it. Most callers therefore never need `portable` for that reason at all. +⚠ **A toolchain that enables CET by default still emits `endbr64`**, and that +is the distribution's choice rather than this project's. Measured on a gcc +whose `-Q --help=common` reports `-fcf-protection=full`: the shims carry 202 +each with and without our flag, identical. --- @@ -157,7 +161,7 @@ sh scripts/build.sh --arch aarch64 # cross-build (riscv64, ppc64, # ppc64le and loongarch64 too) sh scripts/build.sh --arch both # x86_64 and aarch64, sequentially sh scripts/build.sh --engine docker -sh scripts/build.sh --portable # -DCLD_STRICT_ENV, and no CET flag +sh scripts/build.sh --portable # -DCLD_STRICT_ENV, strict environment sh scripts/build.sh --floor-image debian:bookworm-slim --floor-glibc 2.36 ``` diff --git a/docs/integrating.md b/docs/integrating.md index c31d0ff..3ae18af 100644 --- a/docs/integrating.md +++ b/docs/integrating.md @@ -55,16 +55,20 @@ CROSS_LIBC_DLOPEN_LIBDIR=lib # default; the directory under the ro not own: an AppImage runtime exports it into every process it starts, before anything here runs. `CROSS_LIBC_DLOPEN_ROOT` wins when both are set. -⭐ **If you want one spelling and no interop, take the `portable` build.** -Every release ships it beside the default, as -`cross-libc-dlopen-portable-.tar` and `.zip`. Those objects read -`CROSS_LIBC_DLOPEN_ROOT` and never look at `APPDIR`; the string is not even in -the binary. To build it yourself: +⭐ **If you want one spelling and no interop, build the strict variant.** The +release ships one build, which reads `CROSS_LIBC_DLOPEN_ROOT` with `APPDIR` +as the fallback. A build that reads `CROSS_LIBC_DLOPEN_ROOT` and never looks +at `APPDIR` (the string is not even in the binary) is a build-time choice: ```bash -sh scripts/build.sh --portable +cd src && make portable ``` +or `sh scripts/build.sh --portable` for the same objects through the +container build. Whoever assembles the bundle knows whether an AppImage +runtime is going to export `APPDIR` into the process, which is why the choice +lives there; E87 and E88 in `experiments/30-run-tests.sh` measure both arms. + ⛔ **Every control has exactly one name.** The `ANYLINUX_*` spellings this project used before it was renamed are no longer read by anything in `src/`. Nothing consumed them: there has never been a published release, so no bundle diff --git a/docs/report/01-summary.md b/docs/report/01-summary.md index 6939a14..11423c1 100644 --- a/docs/report/01-summary.md +++ b/docs/report/01-summary.md @@ -11,7 +11,7 @@ | Completion criterion | Status | |---|---| | Both goals demonstrated by a test that fails before and passes after | **Yes.** Goal 1: E5, E12. Goal 2: E22/E23 for the mechanism, E30/E32 and E37a/E37 for the end-to-end | -| The evidence harness still reports all predictions held | **Yes, 63/63** on x86-64 and **60/60** on aarch64, up from 22/22. The AppImage suite adds 45 on a glvnd glibc host, 40 on musl, 26 on each of two pre-glvnd glibc hosts and 7 on a real-application stage, with every unrunnable case SKIPPED by the capability it lacks | +| The evidence harness still reports all predictions held | **Yes, 64/64** on x86-64 and **60/60** on aarch64, up from 22/22. The AppImage suite adds 45 on a glvnd glibc host, 40 on musl, 26 on each of two pre-glvnd glibc hosts and 7 on a real-application stage, with every unrunnable case SKIPPED by the capability it lacks | | No host file modified, verified by checksum | **Yes.** T4.3, identical sha256 before and after | | Bundled libraries still win, verified via `dladdr` | **Yes.** T4.2, all resolved under `$APPDIR` | | A forward-compatibility story that does not depend on foresight | **Yes.** Host-runtime selection for the unenumerable gap, a generated shim for the enumerable one, and a build-time audit (E26) for the version traps | diff --git a/docs/report/08-test-results.md b/docs/report/08-test-results.md index 19fe143..88e41aa 100644 --- a/docs/report/08-test-results.md +++ b/docs/report/08-test-results.md @@ -24,14 +24,14 @@ wrong. ### Tier 1, the evidence table -`sh scripts/run-evidence.sh` reports **63/63 predictions held on x86-64** and +`sh scripts/run-evidence.sh` reports **64/64 predictions held on x86-64** and **60/60 on aarch64**. The x86-64 total was measured at the change that added -E75c through E75f; the aarch64 runner runs the same table, so its total is the -x86-64 total minus the three skips below, and CI re-runs both on every push. +E101; the aarch64 runner runs the same table, so its total is the +x86-64 total minus the four skips below, and CI re-runs both on every push. `experiments/run.ps1` drives the same three stage scripts for a machine with PowerShell and no POSIX shell. -⚠ **The two totals differ by exactly the three cases aarch64 SKIPS**, each +⚠ **The two totals differ by exactly the four cases aarch64 SKIPS**, each naming the capability it lacks rather than the difference being unexplained: | case | why it skips on aarch64 | @@ -39,10 +39,11 @@ naming the capability it lacks rather than the difference being unexplained: | E22 | that libc exports `pthread_cond_init` at one symbol version. The trap needs an obsolete definition beside the current one | | E23 | skipped WITH E22 deliberately. With no trap present the stripped object already returns 0, so E23 would pass whether or not `version-compat.c` does anything | | E58 | section M's trampoline is hand-written x86-64 machine code. What the real aarch64 trampolines do is measured by E69 through E73 and E76/E76b, natively on the ARM runner | +| E101 | `endbr64` is an x86 instruction, and asking aarch64 gcc for `-fcf-protection=full` is a hard error rather than a warning, so there is no flag arm to compare against | ⭐ **E23's skip is the one worth reading.** It was reporting MATCH on the ARM runner while asserting nothing, and skipping it with E22 is what stopped that. -63 minus 3 is 60, and no case is missing for a reason nobody wrote down. +64 minus 4 is 60, and no case is missing for a reason nobody wrote down. E1 through E13 measure the problem. E14 through E21 are one per fix from the first pass: the ELF self-test, the generated-shim compile and behaviour, and five selector diff --git a/docs/report/09-the-second-boundary.md b/docs/report/09-the-second-boundary.md index 0e6918d..19bddb8 100644 --- a/docs/report/09-the-second-boundary.md +++ b/docs/report/09-the-second-boundary.md @@ -362,7 +362,7 @@ Totals with this section in: **40/40 on the musl host** with five named skips, and ubuntu:16.04 with nineteen named skips, **7/7** on the gtk4 stage, and **53/53** in the container suite on x86-64, and **50/50** on aarch64 with the three skips named in section 8, at the measurement this section records; the -totals for the tree as it ships are **63/63** and **60/60** in section 8. +totals for the tree as it ships are **64/64** and **60/60** in section 8. ### 9.8 What the shim does not do, stated as a number diff --git a/docs/report/10-measured-versus-assumed.md b/docs/report/10-measured-versus-assumed.md index 75d3f70..3553a2a 100644 --- a/docs/report/10-measured-versus-assumed.md +++ b/docs/report/10-measured-versus-assumed.md @@ -1,7 +1,7 @@ ## 10. Measured versus assumed **Measured:** every table and quoted output above, plus `sh scripts/run-evidence.sh` -(63/63 on x86-64, 60/60 on aarch64), `sh scripts/run-appimage.sh` (45/45 glvnd glibc, 40/40 musl with five +(64/64 on x86-64, 60/60 on aarch64), `sh scripts/run-appimage.sh` (45/45 glvnd glibc, 40/40 musl with five named skips, 26/26 on each pre-glvnd glibc host, 7/7 on the gtk4 stage), `tools/gap.py --fetch`, the eight-distro inventory, the AppImage inventory, the corpus test, the real mpv and `iHD` VA-API run in section 9.19, and the five-distro `ld.so.cache` survey in `../ground-truth.md`. diff --git a/docs/todo/PROGRESS.md b/docs/todo/PROGRESS.md index dc02b66..378fd19 100644 --- a/docs/todo/PROGRESS.md +++ b/docs/todo/PROGRESS.md @@ -12,236 +12,85 @@ baseline. ## Where the work is right now -⭐ **[`v0.1.0` is published.](https://github.com/pkgforge-dev/cross-libc-dlopen/releases/tag/v0.1.0)** -22 assets: both architectures, both variants, the loose objects for the default -one, and a `.tar`, `.zip` and `.sha256` for each. Tagged on `21e9236`, built on -the glibc 2.31 floor, body generated from the manifests by -`scripts/release-notes.sh`. Nothing in it was typed at release time. - -⚠ **The old `main` had failing gates AND a failing secret sweep.** Both are -green now, so this is also the first time the default branch has been green. - -**The tracker is empty and there is one branch.** Pull requests #8 and #10 -merged; #1, #2 and #6 were auto-closed by Renovate when their bumps landed in -#8; #3, #4 and #5 were closed as superseded, verified against the pins on -`main`; #9 was closed because its diff no longer applies, with the reason and -the open policy question written into it. Issue #7 is answered and closed. -`main` is the only branch. - -| workflow | latest | +⭐ **[`v0.2.3` is published.](https://github.com/pkgforge-dev/cross-libc-dlopen/releases/tag/v0.2.3)** +66 assets: all six architectures (x86_64, aarch64, riscv64, ppc64, ppc64le, +loongarch64), loose objects plus `.tar`, `.zip` and `.sha256` for each, both +variants. Tagged on `34482c7`, built on the glibc 2.31 floor, body generated +from the manifests by `scripts/release-notes.sh`. The `release` workflow went +green end to end on the tag: run +[34025633281](https://github.com/pkgforge-dev/cross-libc-dlopen/actions/runs/34025633281). + +| workflow | latest on `main` | |---|---| -| `gates` | ✅ on `main` | -| `secret-sweep` | ✅ on `main` | -| `release` | ✅ on `v0.1.0`, published | -| `appimage-suite` | ⛔ red on `main`, run [32959228414](https://github.com/pkgforge-dev/cross-libc-dlopen/actions/runs/32959228414) | +| `gates` | ✅ run [34025411777](https://github.com/pkgforge-dev/cross-libc-dlopen/actions/runs/34025411777) | +| `secret-sweep` | ✅ on `34482c7` | +| `release` | ✅ on `v0.2.3`, published | -⛔ **The nine cases that MISMATCH on `main`**, both architectures, so the next -session starts from a list rather than a re-run: - -| case | what is known | -|---|---| -| E30, E37a | the control arm stopped contrasting. Item 1 | -| E33, E34 | the corpus sweep dies partway. Item 2 | -| E59, E62, E64, E66, E77 | ⚠ **not yet diagnosed.** E64 and E66 exit 139, a segmentation fault, on the aarch64 musl host. Whether they share a cause with E33 and E34 is unknown and worth asking first | - -| suite | command | state | -|---|---|---| -| evidence table, x86-64 | `sh scripts/run-evidence.sh` | exit 0, every prediction held | -| evidence table, aarch64 | the same, on `ubuntu-24.04-arm` | exit 0, three cases SKIP by name | -| AppImage suite | `sh scripts/run-appimage.sh` | ⚠ **completes on both architectures for the first time.** Mismatches remain and they are findings, below | -| build, all four | `sh scripts/build.sh --arch both` and again `--portable` | exit 0 | -| the gates, planted | `sh scripts/verify-gates.sh` | 8 proven, 0 not | -| the documents | `sh scripts/check-drift.sh` | exit 0, six sections | - -⛔ **Totals live in [`../report/README.md`](../report/README.md)**, not here. Do not -copy one into this file: both are on the one-home list and a second copy turns -the gate red, which is exactly how commit `f6d126e` broke the branch. +**This branch changes what a release ships.** One variant, and no CET request +from any build. See the work below. --- ## ⛔ The work order -### 1. The A/B's control arm no longer contrasts. This needs a decision - -⭐ **Start here. It is the only item that changes what the project claims.** - -Upstream adopted this project. The demo AppImage's `lib/foreign-dlopen.so` is -gone, `lib/cross-libc-dlopen.so` is in its place, and it is a build of this -project. So the "as shipped" arm of the A/B is no longer upstream's naive shim. - -E30 and E37a are the controls for that arm, and they are what make the patched -arm a measurement rather than a coincidence. Both now MISMATCH, because both -arms work. `docs/report/09-the-second-boundary.md` 9.17 has the output. - -⚠ **Their log lines say `predicted=OK` and that is about the exit status, not -the verdict.** What they assert is the needle, and the needle is the complaint: -`NO-DEVICES` for E30 and `zero accessible devices` for E37a. A MISMATCH there -means the as-shipped arm found a device. - -⛔ **Do not flip the predictions.** A control that has stopped contrasting has -stopped measuring, and rewriting it to expect success converts two controls -into two cases that pass whatever the shim does. - -⚠ The honest control for "the feature is absent" is an AppDir with **no** -dispatcher in `.preload`, not one carrying somebody else's. Adopting that -changes what the suite claims about upstream, which is why it was left for a -decision rather than taken. - -### 2. The corpus sweep dies partway, and E33/E34 are scored against nothing - -⛔ **New, and it had been invisible.** `tests/corpus.c` loads every library in -the host's directory in ONE process. On two hosts that process dies partway and -produces no verdict line at all, so the total is 0 and both cases are scored -against nothing: - -| host | feature ON | feature OFF | -|---|---|---| -| `alpine:3.22` | `Segmentation fault (core dumped)` | `TOTAL=298 OK=3` | -| `debian:trixie-slim` | `FATAL: HWAddressSanitizer requires a kernel with tagged address ABI.` | 99 OK lines, then the same | - -Both causes went to `/dev/null` until this session. ⭐ This is **T-15's** -premise, which was inferred from reading another project and is now observed -here. Its fresh-process-per-library design covers both. ⚠ The Alpine -segmentation fault is **not yet attributed to a library**, and until it is, -nobody should assume it is the corpus's fault rather than this project's. - -⭐ **E49 and E50 are done.** E49 MATCHes on aarch64, and E50 reports 2 live -hazards on x86-64 and 3 on aarch64, the third being the mutex. E50 reads the -condition out of `abi-host`'s size table instead of carrying a per-architecture -number. `docs/report/09-the-second-boundary.md` 9.18. - -### 3. The demo tag is rolling. The suite verifies against the release API - -The upstream publishes one release and its tag is `demo`, and the assets were -replaced twice inside two minutes. ⛔ There is no immutable release to pin to. -The suite therefore carries no checked-in digest: it reads the digest the -release API publishes at download time and verifies the bytes against it, -refusing on a mismatch. `docs/report/09-the-second-boundary.md` 9.15 has the -policy and the reasoning. - -When the download does not verify, the two candidates are a torn read from a -re-upload in progress and a genuinely wrong download. The suite re-reads the -release once before refusing, so the first is not blamed on the network. - -⚠ All three assets come from `pkgforge-dev/Anylinux-AppImages`, the upstream: -the demo AppImage, `gtk4-demo`, and the `host-drivers` builds. The upstream now -publishes the host-drivers assets too, so nothing depends on a fork. - -### 4. T-12 is answered for one half and unanswerable for the other - -The table is in T-12's entry, both runners. ⭐ Every case that ends on its own -is far under its timeout: the slowest is 11 seconds against a 25-second floor -and the rest are at or below one second. The fear the entry was opened on is -not in the data. - -⚠ **E61 and E62 measure 30 against a configured 30, and that is not a margin -of zero.** A GL binary never exits on its own, so the timeout is how those two -END. ⛔ Do not raise them on that reading. What stays open is that the -instrumentation cannot tell "ran to its timeout on purpose" from "was killed -before finishing", because both look identical. - -### 5. T-10, T-11, T-16 - -T-10's entry now carries which gates have been seen to refuse and where, and -⭐ four of them were not planted: they went red on a runner against a real -defect, which is stronger than a plant because nobody chose the shape of the -failure. ⛔ Four are still unproven and the entry names them: the endings gate, -which `.gitattributes` makes unplantable from the working tree; the two -`generated` steps; and the artefact verifier's floor rule. Those need a runner -and a deliberate push. - -⛔ **One guard remains unproven**, `release.yml`'s ancestor refusal. Item 6 has -why the release going green did not test it. `package-release.sh`'s two were -planted this session and both refuse. - -T-11 and T-16's cheaper half were not started. T-16's is a `glprobe` change, -and it can only be verified by a GL-capable suite run. - -### 6. The release is cut. What it did NOT prove - -⭐ **`v0.1.0` is published**, on `21e9236`, 22 assets, both architectures and -both variants. The `release` workflow went green end to end on the tag: the -evidence table on both runners, the floor-checked build, the packaging, the -generated body, and the publish. - -⚠ **A first release is not proof that the release path is right**, only that it -ran. Two things in it are still unproven: - -- ⛔ **`release.yml`'s ancestor refusal.** It rejects a tag whose commit never - reached the default branch. `v0.1.0`'s commit did, so the check passed - without being tested. Firing it needs a tag on a commit that is NOT an - ancestor of `main`, and if the guard fails, that publishes. It is the one - guard in this family nobody has seen refuse. -- **The floor rule at publish time** fired on nothing, because no artefact - exceeded the floor. `package-release.sh`'s other two refusals were planted - and both work. - -⚠ There is no second release to diff against, so `release-notes.sh`'s changelog -range took its "First release" branch. The `prev..TAG` branch is untested. - ---- - -## What this session did - -Every claim below has its measurement in `docs/report/09-the-second-boundary.md` 9.14 through 9.18. - -**Three deep review passes**, each with a different question. - -*Pass 1, can every guard added here refuse?* The dash ratchet was recorded as -having failed to fire. It had not: the refusal condition was `count > pin`, -nothing ever lowered the pin, and the tree had drifted eight under, so the -planted dash landed inside the slack. The pin is exact now and a fall refuses -too. It also counted the `--` that the prose rule exempts inside a code block, -which made the section recording the fix unwritable. The cited-path check -could not see a path cited in front of a command, and so never noticed that -`conventions/prose.md` named a ratchet script that has never existed. - -*Pass 2, what did this branch stop measuring?* The ARM runner was added saying -qemu "emulates the instructions and not a memory model", and section P went on -running the aarch64 trampolines under qemu **on aarch64 silicon**. It picks its -vehicle from the host now and prints it. The marker was removed and four -documents went on calling it load-bearing. - -*Pass 3, does every claim hold when the command is run?* T-13's -"print a MISMATCH in full" was in one harness and not the other, and it was -found by the failure it describes. The corpus cases were the same shape a -third time, reporting a zero total with the reason in a discarded stderr. -`INDEX.md` listed two entries as open that declare themselves DONE. - -**Three new checks, each planted and seen to refuse.** The dash ratchet in -`verify-gates.sh`; the two orchestrators agreeing on the same upstream; every -`INDEX.md` row against its entry's declared status. - -**The AppImage suite completes**, having never done so before. Getting there -meant re-pinning against a mutable tag, moving `gtk4-demo` to the true -upstream, and teaching the suite to read the dispatcher slot out of the AppDir -rather than spelling it. - -⭐ **And completing it found a real limitation nothing else could have.** On -aarch64 a musl object cannot allocate and initialise its own `pthread_mutex_t` -in a glibc process: 40 bytes allocated, 48 written, no crossing involved. It -needed real ARM silicon, three chained reporting defects fixed before it was -even legible, and one wrong fix before the right one. `docs/report/09-the-second-boundary.md` 9.18. - -**`tests/bindprobe.c` builds on aarch64.** - -**T-12 answered**, and **T-10's entry now says which gates have been seen to -refuse and where**, including four that were never planted because they went -red on a runner against a real defect. +### 1. Ship one variant, and stop asking for CET + +⭐ **This is the session's work, on branch `drop-portable-variant`.** Two +decisions, both the operator's: + +- **The release ships the default build only.** The strict build (reads only + `CROSS_LIBC_DLOPEN_ROOT`, never `APPDIR`) stays a build-time choice: + `cd src && make portable`, measured by E87 and E88. quick-sharun sets + `CROSS_LIBC_DLOPEN_ROOT` itself, so the strict assets had no consumer; the + `APPDIR` fallback stays, because upstream's own AppImage relies on it and + [`src/cld-env.h`](../../src/cld-env.h) has that argument in full. +- **No build asks for `-fcf-protection=full`.** Measured in + [`../report/09-the-second-boundary.md`](../report/09-the-second-boundary.md) + 9.13: the flag adds six `endbr64` and cannot produce the IBT property note, + so it does no protective work here. It stays askable with + `make CET_CFLAGS=-fcf-protection=full`. + +**The case that proves it.** E101 in `experiments/30-run-tests.sh` builds the +shim by the default recipe and again with the flag asked for, and requires the +default to come out strictly lighter: + +- FAILS before, against the Makefile that still asked for the flag: + `predictions matched: 63, mismatched: 1` with + `E101 MISMATCH predicted=OK (exit 1, wanted OK)`; both arms tied. +- PASSES after: `E101 MATCH predicted=OK fewer endbr64 than the flag arm: + default 3472, asked for: 3478`, and the table reports **64/64** on x86-64. + The suite total moves 63 to 64 and every one-home record moved with it: + [`../report/`](../report/README.md) 01, 08, 09 and 10, the list in + [`gates.yml`](../../.github/workflows/gates.yml), and the same list in + `scripts/verify-gates.sh`. + +Measured locally besides the suite: `build.sh --arch x86_64` and +`--arch x86_64 --portable` both exit 0 with the right manifest variant, the +default build's `gl-fwd.so` carries 3472 `endbr64`, and both directories +package and generate a body. + +### 2. What is still open + +Nothing from this session. The open list is [`INDEX.md`](INDEX.md) and the +work order lives nowhere else. --- ## ⚠ What a new session should distrust -- **The `.preload` baseline is DERIVED, not shipped.** The AppImage ships this - project's own forwarding shims in its `.preload`, and restoring that list - would make every absence case measure a presence. `41-extract.sh` prints what - it drops on every extraction. If that line disappears, look at it. -- **`ground-truth.md`'s inventory carries a verdict column now.** Two rows are - UNVERIFIED and say why. Do not quietly re-attach them to the new binary. -- **The tracker is evidence, not instruction.** Pull request #9's premise, that - `-fcf-protection=full` breaks aarch64, is true of `main` and already fixed on - this branch; what remains of it is a policy question about the default. -- **A guard that has never been seen to refuse is a guard nobody knows works.** - Three were found decorative or unarmed this session and every one of them - looked fine. +- **The aarch64 total on this branch is expected, not yet measured here.** + This machine has no ARM silicon: 60/60 is 64 cases minus four named skips + (E22, E23, E58 and now E101). The PR's CI run is the measurement. +- **`verify-gates.sh`'s one-home list claimed to be identical to + `gates.yml`'s and was not** (53/53 50/50 against 63/63 60/60) until this + branch aligned them. A comment that asserts sameness is a claim; diff it. +- **`skip E76` and `skip E76b` at the foot of `experiments/30-run-tests.sh` + name a function that does not exist in that file.** On an x86-64 machine + with neither qemu nor an aarch64 cross compiler those lines would fail with + `skip: command not found` and leave E76 and E76b unscored rather than + SKIPPED by name. CI never reaches that path (it installs both), so it has + never been seen to fire. Named, not fixed: a change there belongs to its + own decision. +- **A guard that has never been seen to refuse is a guard nobody knows + works.** Three were found decorative or unarmed in earlier sessions and + every one of them looked fine. diff --git a/scripts/build-in-env.sh b/scripts/build-in-env.sh index 5afcbcb..41e6097 100644 --- a/scripts/build-in-env.sh +++ b/scripts/build-in-env.sh @@ -130,16 +130,12 @@ cp "$REPO"/tools/* "$WORK/tools/" 2>/dev/null || true printf 'building for %s with %s%s\n' "$ARCH" "$($CC --version | head -1)" \ "${CLD_EXTRA_CFLAGS:+ [variant: $CLD_EXTRA_CFLAGS]}" -# ⚠ CET_CFLAGS= on the command line overrides the Makefile's `:=`, which is -# how the portable variant drops -fcf-protection=full. An empty value here is -# not the same as leaving it out: leaving it out lets the Makefile decide. -if [ "${CLD_NO_CET:-0}" = 1 ]; then - ( cd "$WORK/src" && make CC="$CC" EXTRA_CFLAGS="${CLD_EXTRA_CFLAGS:-}" CET_CFLAGS= >/dev/null ) || - die "make failed for $ARCH" -else - ( cd "$WORK/src" && make CC="$CC" EXTRA_CFLAGS="${CLD_EXTRA_CFLAGS:-}" >/dev/null ) || - die "make failed for $ARCH" -fi +# ⛔ No CET_CFLAGS here, and that is deliberate: the Makefile's default is +# empty because the flag does no protective work here (the comment beside its +# assignment has the measurement). A caller who wants it passes CET_CFLAGS to +# make directly, which a command-line value always wins. +( cd "$WORK/src" && make CC="$CC" EXTRA_CFLAGS="${CLD_EXTRA_CFLAGS:-}" >/dev/null ) || + die "make failed for $ARCH" mkdir -p "$OUT" for f in cross-libc-dlopen.so gl-fwd.so egl-fwd.so gles-fwd.so runtime-select; do diff --git a/scripts/build.sh b/scripts/build.sh index a076de2..84585e0 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -18,9 +18,9 @@ # scripts/build.sh --arch both x86_64 and aarch64, sequentially # scripts/build.sh --engine native no container; refuses if the host # libc is newer than --floor-glibc -# scripts/build.sh --portable reads only CROSS_LIBC_DLOPEN_ROOT and -# no CET flag. `make portable` is the -# same build without this script +# scripts/build.sh --portable reads only CROSS_LIBC_DLOPEN_ROOT. +# `make portable` is the same build +# without this script # scripts/build.sh --check detect and report, build nothing # # Everything it produces lands in build// with a manifest beside it. @@ -50,16 +50,10 @@ KEEP_GOING=0 # The build variant. "default" reads APPDIR as well as CROSS_LIBC_DLOPEN_ROOT, # because an AppImage runtime exports APPDIR into every process it starts. # "portable" reads only this project's own name, which is what a consumer who -# wants one spelling asked for; src/cld-env.h has the argument. +# wants one spelling asked for; src/cld-env.h has the argument. The release +# ships the default only; the strict build stays a build-time choice. VARIANT=default EXTRA_CFLAGS= -# ⭐ The portable variant also drops -fcf-protection=full. Measured in -# docs/report/09-the-second-boundary.md 9.13: the flag emits endbr64 and no IBT property note, the -# note cannot be emitted honestly because glibc's crti.o carries none, and -# without it a CET-enforcing loader does not turn IBT on for the object. So the -# instructions do no protective work, and a consumer targeting a kernel or an -# emulation layer that would rather not see them can have a build without. -NO_CET=0 die() { printf 'build: %s\n' "$*" >&2; exit 1; } note() { printf ' %s\n' "$*"; } @@ -79,7 +73,7 @@ while [ $# -gt 0 ]; do --out) OUT=${2:?--out needs a value}; shift 2 ;; --check) CHECK_ONLY=1; shift ;; --keep-going) KEEP_GOING=1; shift ;; - --portable) VARIANT=portable; EXTRA_CFLAGS=-DCLD_STRICT_ENV; NO_CET=1; shift ;; + --portable) VARIANT=portable; EXTRA_CFLAGS=-DCLD_STRICT_ENV; shift ;; -h|--help) usage 0 ;; *) printf 'build: unknown option %s\n' "$1" >&2; usage 2 ;; esac @@ -222,7 +216,6 @@ build_one() { # build_one [ "$a" = "$host_arch" ] || die "native builds cannot target $a from $host_arch; use a container" CLD_OUT="$out" CLD_ARCH="$a" CLD_FLOOR_GLIBC="$FLOOR_GLIBC" \ CLD_VARIANT="$VARIANT" CLD_EXTRA_CFLAGS="$EXTRA_CFLAGS" \ - CLD_NO_CET="$NO_CET" \ sh "$HERE/build-in-env.sh" "$ROOT" return fi @@ -240,7 +233,6 @@ build_one() { # build_one -e CLD_INSTALL_DEPS=1 \ -e CLD_VARIANT="$VARIANT" \ -e CLD_EXTRA_CFLAGS="$EXTRA_CFLAGS" \ - -e CLD_NO_CET="$NO_CET" \ "$FLOOR_IMAGE" sh /repo/scripts/build-in-env.sh /repo } diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index 4dab2ee..bcc3558 100644 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -147,15 +147,13 @@ printf '### Assets\n\n' printf 'Each artefact ships three ways: loose, in the `.tar`, and in the `.zip`.\n' printf '⛔ No archive contains a directory, so an extract drops the files where\n' printf 'you are standing.\n\n' -printf '⭐ **Two variants.** The default reads `CROSS_LIBC_DLOPEN_ROOT` and also\n' -printf '`APPDIR`, which an AppImage runtime exports on its own, and is built\n' -printf 'with `-fcf-protection=full`. The `portable` assets read\n' -printf '`CROSS_LIBC_DLOPEN_ROOT` and nothing else, and are built without that\n' -printf 'flag. Take those if you want one spelling and no CET instrumentation.\n' -printf '⚠ The flag accounts for six of the shim`s 3478 endbr64; the rest are the\n' -printf 'trampolines`, spelled as literal bytes, and no flag removes them.\n' -printf 'All five objects differ between the two variants, so the variant ships\n' -printf 'as archives rather than loose files.\n\n' +printf '⭐ **One variant.** The objects read `CROSS_LIBC_DLOPEN_ROOT`, with\n' +printf '`APPDIR` as the fallback, because an AppImage runtime exports `APPDIR`\n' +printf 'into every process it starts and `CROSS_LIBC_DLOPEN_ROOT` wins when both\n' +printf 'are set. The build asks for no CET flag: measured, the flag adds six\n' +printf 'endbr64 to the shims and cannot produce the IBT property note, so it does\n' +printf 'no protective work here. A consumer who wants a build reading only this\n' +printf 'project'"'"'s name makes it at build time: `cd src && make portable`.\n\n' printf '| asset | sha256 |\n|---|---|\n' for f in "$DIST"/*; do [ -f "$f" ] || continue diff --git a/scripts/verify-artifacts.sh b/scripts/verify-artifacts.sh index 4159b85..94bd00d 100644 --- a/scripts/verify-artifacts.sh +++ b/scripts/verify-artifacts.sh @@ -86,28 +86,25 @@ for pair in 'gl-fwd.so gl-fwd-gl.h' 'egl-fwd.so gl-fwd-egl.h' 'gles-fwd.so gl-fw say "$so: SONAME $got_son, $got_n entry points" done -# ⭐ FATAL: the endbr64 instrumentation, which is what -fcf-protection=full -# actually delivers. A build where the flag was dropped, by an edit or by -# the Makefile's CET_CFLAGS failing to resolve on a host whose compiler does -# a compiler that does not answer -dumpmachine the expected way, produces a shim with none, and -# nothing else here would notice. x86-64 only: CET is an x86 feature and the -# aarch64 shim correctly has no endbr64 at all. +# ⭐ The endbr64 count, REPORTED rather than asserted. The trampolines spell +# their endbr64 as literal bytes in gl-fwd.c so the floor's assembler cannot +# be too old for them, and the build asks for no CET flag, so the count here +# should be the trampolines' own and nothing more. E101 in +# experiments/30-run-tests.sh is the case that keeps that true: the default +# recipe must produce strictly fewer endbr64 than the same recipe with the +# flag asked for. x86-64 only: CET is an x86 feature and the shims of every +# other architecture correctly have no endbr64 at all. # -# ⚠ REPORTED, NOT FATAL: the .note.gnu.property IBT note, which is absent. -# The reason is measured, and it is not the one this check used to give. -# `-fcf-protection=full` alone emits no note on bullseye (gcc 10.2), bookworm -# (12.2) or trixie (14.2), because glibc's crti.o carries no property on any -# of the three, and the linker ANDs that absence across the link. -# ⛔ `-Wl,-z,ibt,-z,shstk` DOES emit one on all three, and the note it emits is -# FALSE: _init and _fini come from crti.o/crtn.o, ld.so reaches them through -# DT_INIT and DT_FINI, an indirect call, and neither begins with endbr64. -# Forcing the note would assert a property the object does not have, which is -# worse than not having the note. docs/report/09-the-second-boundary.md 9.13 has the full table. -# -# ⚠ The `portable` variant asks for NO CET, so there the expectation inverts: -# endbr64 present would mean --portable did not reach the compile. Both arms -# are asserted, because a check that only knows one of them cannot tell a -# working variant from a broken flag. +# ⚠ REPORTED, NOT ASSERTED: the .note.gnu.property IBT note, which is absent. +# The reason is measured. `-fcf-protection=full` emits no note on bullseye +# (gcc 10.2), bookworm (12.2) or trixie (14.2), because glibc's crti.o carries +# no property on any of the three, and the linker ANDs that absence across the +# link. ⛔ `-Wl,-z,ibt,-z,shstk` DOES emit one on all three, and the note it +# emits is FALSE: _init and _fini come from crti.o/crtn.o, ld.so reaches them +# through DT_INIT and DT_FINI, an indirect call, and neither begins with +# endbr64. Forcing the note would assert a property the object does not have, +# which is worse than not having the note. +# docs/report/09-the-second-boundary.md 9.13 has the full table. if [ -f "$DIR/gl-fwd.so" ] && [ "$ARCH" = x86_64 ]; then nend=$($OBJDUMP -d "$DIR/gl-fwd.so" 2>/dev/null | grep -c endbr64 || true) say "gl-fwd.so: $nend endbr64" @@ -115,16 +112,12 @@ if [ -f "$DIR/gl-fwd.so" ] && [ "$ARCH" = x86_64 ]; then # # An earlier version of this check refused a build with no endbr64, on the # grounds that endbr64 is what -fcf-protection=full actually delivers. - # ⚠ THAT CHECK COULD NEVER HAVE FAILED. Measured: a default x86-64 - # gl-fwd.so has 3478 endbr64 and the same object built with the flag - # removed has 3472. The flag accounts for six of them. The other 3472 are - # the trampolines' own, spelled as literal bytes in gl-fwd.c so the - # floor's assembler cannot be too old for them, and no compiler flag - # removes those. - # - # So a count over zero says nothing about whether the flag arrived, and a - # guard that cannot fail is worse than no guard. The number is printed and - # the manifest records the variant; docs/report/09-the-second-boundary.md 9.13 has both figures. + # ⚠ THAT CHECK COULD NEVER HAVE FAILED. Measured: the trampolines carry + # 3472 of their own, spelled as literal bytes no compiler flag removes, + # and the flag arm added six more for 3478. So a count over zero says + # nothing about whether the flag arrived, and a guard that cannot fail is + # worse than no guard. The number is printed, the manifest records the + # variant, and E101 now asserts the count the default build owes. if command -v readelf >/dev/null 2>&1 && readelf -n "$DIR/gl-fwd.so" 2>/dev/null | grep -qi 'propert'; then say "gl-fwd.so: IBT property note present" diff --git a/scripts/verify-gates.sh b/scripts/verify-gates.sh index df50df8..5267b7d 100644 --- a/scripts/verify-gates.sh +++ b/scripts/verify-gates.sh @@ -93,7 +93,7 @@ g_attrib() { # section. Kept identical to the gates.yml step, including that. g_onehome() { f=0 - for n in 3470 358 53/53 50/50 45/45 40/40 26/26; do + for n in 3470 358 64/64 60/60 45/45 40/40 26/26; do c=$(git grep -lF "$n" -- '*.md' ':(exclude)docs/history/*' | sed 's|^docs/report/.*|docs/report/|' | sort -u | wc -l) [ "$c" -gt 1 ] && f=1 diff --git a/src/Makefile b/src/Makefile index 5317157..f6503c0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -33,21 +33,22 @@ SOFLAGS = -shared -fPIC # has never parsed. EXTRA_CFLAGS ?= -# CET is an x86 feature. On aarch64 gcc, -fcf-protection=full is not a warning -# but a hard error, "not supported for this target", so both aarch64 builds -# failed at the first shim before anything else could be measured. +# CET. ⛔ The build does not ask for it, and that is a measurement rather than +# an omission (docs/report/09-the-second-boundary.md 9.13): the flag adds six +# endbr64 to the shims and cannot produce the IBT property note, because +# glibc's crti.o carries no property on any floor image and the linker ANDs +# that absence across the link. Without the note a CET-enforcing loader turns +# indirect-branch tracking off for the whole process, so the instructions the +# flag emits are ones no loader will honour. E101 in +# experiments/30-run-tests.sh is the case that keeps this true. # -# ⚠ Selected by what the COMPILER TARGETS, not by uname: the aarch64 artefacts -# are CROSS-compiled, so the machine running make is x86-64 while the object -# being produced is not. -dumpmachine is the only one of the two that knows. +# It stays askable, for a toolchain whose crti.o does carry the property: # -# ⚠ Asked, not assumed: a toolchain built without CET refuses the flag on x86 -# too, and that reaches the compiler as a hard error. Issue #13. -CC_TARGET := $(shell $(CC) -dumpmachine 2>/dev/null) -CET_OK := $(shell printf 'int main(void){return 0;}' > .cet-probe.c 2>/dev/null && \ - $(CC) -fcf-protection=full -c .cet-probe.c -o .cet-probe.o >/dev/null 2>&1 && \ - echo yes; rm -f .cet-probe.c .cet-probe.o) -CET_CFLAGS := $(if $(filter x86_64% i386% i686% amd64%,$(CC_TARGET)),$(if $(CET_OK),-fcf-protection=full,),) +# make CET_CFLAGS=-fcf-protection=full +# +# ⚠ On aarch64 gcc the flag is not a warning but a hard error, "not supported +# for this target", and a toolchain built without CET refuses it on x86 too. +CET_CFLAGS ?= ROOT = .. FLOOR ?= $(ROOT)/inventories/appdir.json @@ -80,10 +81,11 @@ GLES ?= $(ROOT)/.tmp/gtk4x/AppDir/lib all: cross-libc-dlopen.so gl-fwd.so egl-fwd.so gles-fwd.so runtime-select -# The packager's variant, without the build script. docs/building.md says what -# the two flags do and why the CET one is dropped. +# The packager's variant, without the build script: the strict environment, +# -DCLD_STRICT_ENV. It differs from the default by that flag and nothing +# else; docs/building.md has the argument. portable: - $(MAKE) EXTRA_CFLAGS='-DCLD_STRICT_ENV $(EXTRA_CFLAGS)' CET_CFLAGS= all + $(MAKE) EXTRA_CFLAGS='-DCLD_STRICT_ENV $(EXTRA_CFLAGS)' all # The preload carries the shim: it is already in the global lookup scope, so # its exports satisfy undefined symbols in anything dlopen'd later (E2, E5). From 35dc6d5f9f963d4bc15996f4569850e6fd83a51e Mon Sep 17 00:00:00 2001 From: Samueru-sama Date: Mon, 7 Sep 2026 23:32:03 -0400 Subject: [PATCH 3/4] Point the one-home records at their home instead of restating them PROGRESS.md restated the evidence total and the aarch64 total, both of which are gated numbers whose home is the report, and the one-home gate refused a clean tree. The prose now points at report 08. The plant for that gate named 53/53, which left the list when it moved to 64/64, so the plant names a number the gate actually gates. --- docs/todo/PROGRESS.md | 13 +++++++------ scripts/verify-gates.sh | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/todo/PROGRESS.md b/docs/todo/PROGRESS.md index 378fd19..ffbb341 100644 --- a/docs/todo/PROGRESS.md +++ b/docs/todo/PROGRESS.md @@ -58,11 +58,12 @@ default to come out strictly lighter: `predictions matched: 63, mismatched: 1` with `E101 MISMATCH predicted=OK (exit 1, wanted OK)`; both arms tied. - PASSES after: `E101 MATCH predicted=OK fewer endbr64 than the flag arm: - default 3472, asked for: 3478`, and the table reports **64/64** on x86-64. - The suite total moves 63 to 64 and every one-home record moved with it: - [`../report/`](../report/README.md) 01, 08, 09 and 10, the list in + default 3472, asked for: 3478`, and the table is green end to end on + x86-64. The suite total moves 63 to 64 and every one-home record moved with + it: [`../report/`](../report/README.md) 01, 08, 09 and 10, the list in [`gates.yml`](../../.github/workflows/gates.yml), and the same list in - `scripts/verify-gates.sh`. + `scripts/verify-gates.sh`. The aarch64 total and the four-skip list are in + report 08, which is that number's home. Measured locally besides the suite: `build.sh --arch x86_64` and `--arch x86_64 --portable` both exit 0 with the right manifest variant, the @@ -79,8 +80,8 @@ work order lives nowhere else. ## ⚠ What a new session should distrust - **The aarch64 total on this branch is expected, not yet measured here.** - This machine has no ARM silicon: 60/60 is 64 cases minus four named skips - (E22, E23, E58 and now E101). The PR's CI run is the measurement. + This machine has no ARM silicon: it is the x86-64 total minus four named + skips (E22, E23, E58 and now E101). The PR's CI run is the measurement. - **`verify-gates.sh`'s one-home list claimed to be identical to `gates.yml`'s and was not** (53/53 50/50 against 63/63 60/60) until this branch aligned them. A comment that asserts sameness is a claim; diff it. diff --git a/scripts/verify-gates.sh b/scripts/verify-gates.sh index 5267b7d..3c6907a 100644 --- a/scripts/verify-gates.sh +++ b/scripts/verify-gates.sh @@ -148,7 +148,7 @@ check "no tool is credited (tree)" g_attrib docs/_gate_probe.md \ check " the same, as a generated-with line" g_attrib docs/_gate_probe.md \ "Generated with [$TOOLNAME Code](https://example.invalid)" check "every headline number has one home" g_onehome docs/_gate_probe.md \ - 'the suite reports 53/53 predictions held' + 'the suite reports 64/64 predictions held' # ⛔ THIS CHECK EXISTS BECAUSE THE RATCHET DID NOT REFUSE. It was written as a # budget with a hardcoded number and a printed suggestion that the next reader From 35781851bd421ba7c6359fd439b29934d22c6f99 Mon Sep 17 00:00:00 2001 From: Samueru-sama Date: Mon, 7 Sep 2026 23:33:12 -0400 Subject: [PATCH 4/4] Drop the stale totals from the drift note The note spelled the old totals, and one of them is a gated number whose home is the report, so the one-home gate refused a clean tree. The prose says what happened without restating the number. --- docs/todo/PROGRESS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/todo/PROGRESS.md b/docs/todo/PROGRESS.md index ffbb341..182dc20 100644 --- a/docs/todo/PROGRESS.md +++ b/docs/todo/PROGRESS.md @@ -83,8 +83,9 @@ work order lives nowhere else. This machine has no ARM silicon: it is the x86-64 total minus four named skips (E22, E23, E58 and now E101). The PR's CI run is the measurement. - **`verify-gates.sh`'s one-home list claimed to be identical to - `gates.yml`'s and was not** (53/53 50/50 against 63/63 60/60) until this - branch aligned them. A comment that asserts sameness is a claim; diff it. + `gates.yml`'s and was not** (it still gated the previous suite totals after + the totals had moved on) until this branch aligned them. A comment that + asserts sameness is a claim; diff it. - **`skip E76` and `skip E76b` at the foot of `experiments/30-run-tests.sh` name a function that does not exist in that file.** On an x86-64 machine with neither qemu nor an aarch64 cross compiler those lines would fail with