Files
AXP-Resurection/docs/009-fortify-family-bisect-20260927.md
T
sunfish-adminandCursor 2d145c2878 009: Polish bacon fixes, OTA host scripts, and migration docs.
Restore disable_configstore, EuiccDisabler and thermal CDEV guards, branding
and rebuild/sync helpers; document device roles, laptop migration, and Gitea.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 21:06:45 +02:00

132 lines
6.6 KiB
Markdown

<!-- Status: historical — Magisk 009 ship resume: docs/009-handoff-20261008.md -->
> **Status: historical.** Do not use as execution order. Resume: [`009-handoff-20261008.md`](009-handoff-20261008.md).
# FORTIFY family bisection — 2026-09-27
## Why this experiment
The two-phase X-then-C experiment establishes loss of an adb-verified record
somewhere in the panic/reboot sequence. Further recovery-channel engineering
has not identified the original FORTIFY boot failure. This experiment instead
isolates the protection families while leaving panic behavior intact.
This is diagnostic-only: a boot with one family is not a completed FORTIFY port
and must not be promoted into the shipping hardening configuration.
## Independent analysis before build
- Reconstructed symbols from the saved failed bounds kernel #19, rather than
inspecting the live output overwritten by later no-FORTIFY diagnostic builds.
- Found 372 direct branches to fortify_panic, including its CFI jump-table
entry. Their presence alone does not prove execution. Inspected early
start_kernel/setup_arch checks; no demonstrated unconditional failure found.
- Recompiled init/main.c, arch/arm64/kernel/setup.c and lib/string.c in scratch
space with the actual Clang 21 flags and the ported header; this is not a
replacement for whole-kernel LTO or a device boot.
- The selector's full, memory and strings variants compile for init/main.c and
lib/string.c with those flags. No overflow stubs are added.
## Source and build isolation
`scripts/009-fortify-select-family.py` selects complete wrapper functions from
the existing port and verifies the expected inventory of 15 wrappers. Memory
keeps memset, memcpy, memmove, memscan, memcmp, memchr, memchr_inv and kmemdup.
Strings keeps strncpy, strcat, strlen, strnlen, strlcpy, strncat and strcpy.
Disabled wrappers use the existing raw declarations, not a returning panic stub.
`scripts/009-build-fortify-family-isolated.sh` archives kernel commit
28f9290ae067d3e7857126eb6312720dbe9c7c80 to its own source directory. It applies
the existing Clang port and the already-tested audio/WLAN bounds patch, then
selects a family. It never resets Cursor's live source or writes its output.
The input .config is extracted from the saved failing bounds kernel. olddefconfig
must leave it byte-identical or the job aborts. Clang r563880c, LLVM tools and
Android host build flags are reused. Kernel release suffix is preserved for
installed modules. Full module linking is required even though only boot will
be packaged. The raw new Image is injected into GOODMAKE; ramdisk and DTB stay
from GOODMAKE. Local unpack, metadata, payload and module-version checks remain
required before device use.
## First run
Remote root: `/srv/rom/artifacts/009-fortify-family-20260927/`.
Detached session: `009-codex-fortify-memory-20260927`.
Log: `/srv/rom/artifacts/detach-009-codex-fortify-memory-20260927-20260927T193643Z.log`.
Status: `/srv/rom/artifacts/detach-009-codex-fortify-memory-20260927.status`.
The memory-only build started at 19:36:43 UTC. Configuration drift check passed
(empty memory/config.diff). Build finished exit=0 at 19:58:48 UTC
(`memory/boot-make.img`).
**LAB boot (2026-09-27 ~22:00 local):** flashed both slots → `sys.boot_completed=1`
on kernel `#1` `Sun Sep 27 19:45:25 UTC 2026`. Memory-only family **boots**.
**Strings-only build:** session `009-codex-fortify-strings-20260927`, finished
exit=0 at 20:51:38 UTC (`strings/boot-make.img`).
**LAB boot (strings):** flashed both slots → returned to **fastboot** (~100s,
no `boot_completed`). Recovered combo4. Strings-only family **fails to boot**.
**Family result:** memory boots, strings does not → narrow toward string
wrappers (`strncpy`/`strcpy`/`strlen`/…), not the memcpy family. Still
diagnostic-only; not a shippable FORTIFY subset.
**Strings split (LAB):**
- `strings-copy` → **boots**
- `strings-len` (strlen, strnlen) → **boots**
- full `strings` (copy+len together) → **fails**
Implication: neither half alone is sufficient to reproduce; failure is in the
**interaction** of fortified copy + fortified len (or LTO/CFI coupling of
both). Next: smaller cross products (e.g. strcpy+strlen only), same baseline.
**Cross products (LAB):**
- `strings-strcpy-strlen` → **boots**
- `strings-copy-strlen` (5 copy + strlen) → **fails**
- `strings-copy-strnlen` → **boots** (strlen is the critical len partner)
- `strings-strlen-restcopy` (strlen+strncpy/strlcpy/strcat/strncat) → **fails**
- `strings-strlen-ncopy` (strlen+strncpy+strlcpy) → **fails**
- `strings-strlen-cat` (strlen+strcat+strncat) → **boots**
- `strings-strlen-strncpy` → **boots**
- `strings-strlen-strlcpy` → **fails**
- **Smoking gun:** fortified `strlcpy` + fortified `strlen` together.
- **Root cause:** 4.14 `strlcpy` does `ret = strlen(q)`. With Clang
`__pass_object_size`, nested fortified `strlen` panics on known-size
sources; GCC usually saw bos==-1 and skipped. Fix in
`apply_fortify_clang.py`: `strlcpy`/`strcpy` use `__underlying_strlen`.
**Fix verification (LAB 2026-09-28):**
- Rebuilt `strings-strlen-strlcpy` + full `strings` with fixed apply → both
`exit=0` (~18:24 / 18:26 UTC).
- Flashed both slots: `strlen+strlcpy` → `boot_completed=1`; full `strings`
→ `boot_completed=1` (`CONFIG_FORTIFY_SOURCE=y`, kernels `#1` 18:06 / 18:10 UTC).
- Pre-fix pair archived as `strings-strlen-strlcpy-broken/` on the host.
**Full family (LAB 2026-09-28):** rebuilt `full` (15 wrappers) with nested-strlen
fix → `exit=0` 19:31 UTC → flashed both slots → **`boot_completed=1`**
(`CONFIG_FORTIFY_SOURCE=y`, kernel `#1` 19:18 UTC). Port wrappers alone are
no longer the brick.
**combo4+FORTIFY (LAB 2026-09-28):** ksync
`20260928T201655Z-bisect-ksync-combo4-fortify-fix` → flashed both slots →
**`boot_completed=1`** with `FORTIFY` + `INIT_ON_*` + `DMESG` +
`SLAB_FREELIST_HARDENED`. Nested-strlen fix unblocked the previous ship
candidate pack.
## Interpretation
- A memory-only boot would narrow investigation toward the string wrappers or
their interaction with memory wrappers. It would not prove a specific string
operation or driver is faulty.
- A memory-only failure would keep the memory family, common port machinery and
build/runtime interactions in scope; it would not clear the string family.
- Complementary/control builds should follow the observation, changing only the
selector. If behavior is deterministic, continue by splitting the implicated
group, then its caller set. Do not restart from an unrelated build baseline.
- Preserve vmlinux, source, effective config, command, compiler version, hashes
and the exact boot result for each variant.