Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
39 changes: 15 additions & 24 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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 ---"
Expand All @@ -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
Expand Down
42 changes: 23 additions & 19 deletions docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand All @@ -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
```

Expand Down
16 changes: 10 additions & 6 deletions docs/integrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<arch>.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
Expand Down
2 changes: 1 addition & 1 deletion docs/report/01-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
11 changes: 6 additions & 5 deletions docs/report/08-test-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,26 @@ 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 |
|---|---|
| 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
Expand Down
2 changes: 1 addition & 1 deletion docs/report/09-the-second-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/report/10-measured-versus-assumed.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
Loading