Files
AXP-Resurection/docs/009-fortify-family-bisect-20260927.md
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

6.6 KiB

Status: historical. Do not use as execution order. Resume: 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.