Ship script gates wave 4 behind AXP_DEBLOB_WAVE4, documents secrets cert path, and adds deblob appliers plus tests; docs capture Swissquote fix and parity. Co-authored-by: Cursor <cursoragent@cursor.com>
262 lines
13 KiB
Markdown
262 lines
13 KiB
Markdown
# 009 — Userspace hardening / signing / AVB checklist (Gate G)
|
||
|
||
**Task:** T040.
|
||
**Companion:** kernel half = `docs/009-gate-g-kernel-attribution.md`.
|
||
**Policy (2026-09-29):** project **release keys yes**; **bootloader relock no**
|
||
(AXP/LOS unlocked norm; IOdes-class lock = non-goal). See
|
||
`specs/009-axp-los23-port/research.md`.
|
||
|
||
Secrets never enter Git. Host material lives under `/srv/rom/secrets/` on
|
||
`rom-dev01` only.
|
||
|
||
---
|
||
|
||
## Measured baseline (2026-09-29)
|
||
|
||
| Prop / fact | GOLDEN (AXP Pro) | LAB (Stage1 pack) | Target |
|
||
|-------------|------------------|-------------------|--------|
|
||
| `ro.build.type` | `user` | `userdebug` | `user` for ship; `userdebug` OK while developing |
|
||
| `ro.build.tags` | `release-keys` | `test-keys` | `release-keys` |
|
||
| `ro.build.flavor` | `lineage_sunfish-user` | `lineage_sunfish-userdebug` | `…-user` at ship |
|
||
| `ro.debuggable` | `0` | `0` on LAB measure wait — typically `1` on userdebug | `0` on ship |
|
||
| `ro.secure` | `1` | (LOS default) | `1` |
|
||
| `ro.boot.flash.locked` | `0` | `0` | `0` (unlocked — intentional) |
|
||
| `ro.boot.verifiedbootstate` | `orange` | `orange` | `orange` while unlocked |
|
||
| `ro.boot.vbmeta.device_state` | `unlocked` | (same class) | unlocked |
|
||
| `ro.boot.veritymode` | `enforcing` | expect enforcing | keep |
|
||
| SELinux | Enforcing (expect) | **Enforcing** | keep |
|
||
| OTA zip cert | AXP releasekey | **AOSP `testkey.x509.pem`** (byte-match on stage1 zip) | project releasekey |
|
||
| AVB signing key in tree | AXP custom | `external/avb/test/data/testkey_rsa2048.pem` | project AVB key |
|
||
| `BOARD_AVB_MAKE_VBMETA_IMAGE_ARGS --flags 3` | AXP sets real AVB when enabled | LOS sunfish adds `--flags 3` unless `WITH_AVB=true` | keep flags 3 (signing infra only; not runtime harden) |
|
||
|
||
LAB adb sample (device `11231JEC207477`, Stage1 sideload):
|
||
`test-keys` / `userdebug` / `orange` / `flash.locked=0` / `getenforce=Enforcing`.
|
||
|
||
GOLDEN source: `/srv/rom/references/axp/golden/2026-09-13/getprop.txt`.
|
||
|
||
---
|
||
|
||
## What “done” means for Gate G userspace
|
||
|
||
1. Ship images/OTAs verify as **`release-keys`** (not AOSP test-keys).
|
||
2. Phonesky / microG keep **their** project JKS identities under
|
||
`/srv/rom/secrets/{phonesky,microg}-signing/` (already exist).
|
||
3. AVB *build* uses project keys where LOS23/sunfish allows; device stays
|
||
**unlocked** (`orange`). Relock is **out of scope**.
|
||
4. Userspace hardening stages are attributable (patch dir + map) or explicitly
|
||
deferred/skipped with reason.
|
||
5. Gaps written down — no silent omit.
|
||
|
||
---
|
||
|
||
## Stages (do in order)
|
||
|
||
### US-S0 — Inventory (this doc) — **done 2026-09-29**
|
||
|
||
- [x] Compare GOLDEN vs LAB signing/AVB props.
|
||
- [x] Confirm stage1 zip OTA cert == AOSP testkey.
|
||
- [x] Confirm no `~/.android-certs` / platform release keys on host yet
|
||
(only Phonesky + microG JKS).
|
||
- [x] Record bootloader-lock = wont.
|
||
|
||
### US-S1 — Generate project Android signing keys — **done 2026-09-29 (host)**
|
||
|
||
Host-only. Never commit `.pk8` / private PEM.
|
||
|
||
| Key | Role |
|
||
|-----|------|
|
||
| `releasekey` | Default APK/OTA (`PRODUCT_DEFAULT_DEV_CERTIFICATE`) |
|
||
| `platform` | `platform`-signed priv-apps |
|
||
| `shared` / `media` / `networkstack` / `sdk_sandbox` / `bluetooth` / `nfc` | AOSP partition signing set |
|
||
|
||
Procedure: `scripts/009-generate-release-keys.sh` on `rom-dev01`
|
||
→ prefers `/srv/rom/secrets/android-certs/` if writable, else
|
||
`$HOME/.android-certs` (Lineage default; used 2026-09-29 because secrets/
|
||
is root-owned 750 without group write).
|
||
|
||
**Mandatory before first signed ship bacon:** encrypted/offline backup of that
|
||
directory (constitution). Public `*.x509.pem` may later be copied into Git for
|
||
docs; private material must not.
|
||
|
||
Acceptance:
|
||
|
||
- [x] `$HOME/.android-certs/{releasekey,platform,…}.{pk8,x509.pem}` + `avb.pem`
|
||
exist on `rom-dev01` (2026-09-29T16:37Z)
|
||
- [x] Host tar backup: `/home/android-build/backups/android-certs-20260929T1637Z.tar.gz`
|
||
(mode 600; SHA256 in `.sha256`) — **still copy offline/encrypted**
|
||
- [x] `releasekey` DN = `O=AXP-Resurection, OU=sunfish-a16, CN=release`
|
||
(not AOSP testkey)
|
||
- [ ] Root later: `install -d` `/srv/rom/secrets/android-certs` and move keys there
|
||
- [ ] Owner offline encrypted copy confirmed
|
||
|
||
### US-S2 — Wire release keys into bacon — **in progress**
|
||
|
||
Lineage convention: `PRODUCT_DEFAULT_DEV_CERTIFICATE` → path without suffix
|
||
(e.g. `…/releasekey`). On LOS 23 / soong, **env export alone is ignored** —
|
||
must be a product makefile `PRODUCT_` (see `vendor/sunfish-a16/signing.mk`,
|
||
inherited via `magisk.mk`). Verified `get_build_var` → project releasekey.
|
||
|
||
Keys **present** on rom-dev01 (`$HOME/.android-certs/releasekey.pk8`, 2026-09-29).
|
||
First artifact `20261001T221102Z-…-cve-hi-releasekeys` was **still AOSP testkey**
|
||
(env-only wire). Rebacon after `signing.mk` (2026-10-01T22:12Z).
|
||
CVE LAB bacons keep test-keys. First real signed bacon stays **`userdebug`**.
|
||
|
||
**LAB 2026-10-02:** first signed sideload booted Wi‑Fi OK but Bluetooth
|
||
crash-looped — zygote `selinux_android_setcontext` failed with
|
||
`seinfo=default` for `com.android.bluetooth`. Cause: APK signed with project
|
||
`certs/bluetooth.pk8` while `MAINLINE_BLUETOOTH_SEPOLICY_DEV_CERTIFICATES`
|
||
stayed on AOSP `build/make/target/product/security/` (non-`com.google.android.bt`
|
||
default in `config.mk`). Fix: `PRODUCT_MAINLINE_BLUETOOTH_SEPOLICY_DEV_CERTIFICATES`
|
||
in `signing.mk` (rebacon required; boot-only flash insufficient).
|
||
|
||
Acceptance:
|
||
|
||
- [ ] Zip `META-INF/com/android/otacert` ≠ AOSP testkey
|
||
- [ ] `ro.build.tags=release-keys` on LAB after **sideload** (boot-only flash insufficient)
|
||
(note: first RK sideload still showed `dev-keys` on userdebug — recheck after BT fix)
|
||
- [x] Magisk preroot + microG + Wi‑Fi **and Bluetooth** OK on Stage1+combo4+CVE-hi ship pack
|
||
(`20261002T085915Z-…-cve-hi-releasekeys`, LAB 2026-10-02: BT ON, crashed 0)
|
||
- [x] `plat_mac_permissions.xml` embeds project `bluetooth.x509.pem` (`seinfo=bluetooth`)
|
||
- [ ] Minor: LOS SetupWizard after Wi‑Fi connect can bounce to language step
|
||
(Wi‑Fi stays connected; non-blocking; note only)
|
||
- [ ] Prior test-keys image retained for bisect
|
||
(`20261001T201836Z-…-cve-hi-batch2` / `#49`, or hi-batch3 when OK)
|
||
|
||
Script: `scripts/009-bacon-axp-stage1-combo4-cve-hi-releasekeys.sh` +
|
||
`vendor/sunfish-a16/signing.mk` → `vendor/sunfish-a16/certs/releasekey`
|
||
(in-tree copy of `$HOME/.android-certs`; **gitignored** — soong rejects paths
|
||
outside `ANDROID_BUILD_TOP`).
|
||
|
||
### US-S3 — Project AVB key (unlocked device)
|
||
|
||
Today sunfish often signs with AOSP `testkey_rsa4096` (or rsa2048) and adds
|
||
`--flags 3` unless `WITH_AVB=true` (`BoardConfigLineage.mk` /
|
||
`BoardConfig-common.mk`). LAB `20261002T085915Z` vbmeta = AOSP testkey_rsa4096
|
||
+ flags 3 (not project `avb.pem`).
|
||
|
||
AXP ships custom AVB keys and documents AVB2; GOLDEN stays **unlocked/orange**.
|
||
Clone that *signing* posture, not IOdes lock.
|
||
|
||
**Bootloader / OEM unlocking (2026-10-02):** relock remains **wont** for this
|
||
Magisk track (orange + preroot). Locking with Magisk-patched boot is a brick
|
||
risk on Pixel unless the full AVB chain is enforced end-to-end — out of scope.
|
||
Keep `flash.locked=0` / `verifiedbootstate=orange`. Prefer leaving **OEM
|
||
unlocking** enabled in Developer options as a *best-effort* escape hatch for
|
||
future unlock attempts — it is **not** a guarantee of recovery, data
|
||
preservation, or rescue from every failure mode (AOSP: unlock ability is
|
||
conditional). Do **not** turn it off casually.
|
||
|
||
Wire: `vendor/sunfish-a16/BoardConfig.mk` overrides `BOARD_AVB_*` →
|
||
`vendor/sunfish-a16/certs/avb.pem` + `SHA256_RSA4096`. Leave `WITH_AVB`
|
||
unset so `--flags 3` stays (Magisk-friendly).
|
||
|
||
**What flags 3 means (libavb):** `HASHTREE_DISABLED (1) | VERIFICATION_DISABLED (2)`.
|
||
Descriptors are not enforced at boot; this is **AVB signing infrastructure**,
|
||
not “AVB-hardened verified boot”. Do not call US-S3 “AVB endurecido” while
|
||
flags stay 3. A later stage may measure `WITH_AVB=true` (flags 0) + Magisk
|
||
resign — separate acceptance.
|
||
|
||
Acceptance:
|
||
|
||
- [x] `BOARD_AVB_KEY_PATH` → project `vendor/sunfish-a16/certs/avb.pem`
|
||
- [x] Artifact gates **fail closed** on **target_files/IMAGES** (not OUT make
|
||
images): pubkey sha1 == `avb_pkmd.bin` (≠ AOSP `2597c218…`); Flags `3`;
|
||
`verify_image --key` vbmeta + `--expected_chain_partition` vbmeta_system;
|
||
Magisk boot hash matches vbmeta; `magiskboot cpio test` ≠ 0
|
||
- [x] Document clearly: runtime AVB enforce = **off** (flags 3) — not “AVB endurecido”
|
||
- [x] Device still boots unlocked; Magisk preroot intact (LAB 2026-10-02 sideload
|
||
+ boot/vbmeta reinforce; `magiskd`, `flash.locked=0`, orange)
|
||
- [x] No bootloader relock; OEM unlocking left enabled (escape hatch, not guarantee)
|
||
|
||
### Branding (no rebacon-only)
|
||
|
||
- [x] Wire `vendor/sunfish-a16/branding.mk` →
|
||
`TARGET_UNOFFICIAL_BUILD_ID=AXP-Resurection-Pro` (full Magisk track, not slim;
|
||
next bacon embeds it in `ro.lineage.version`)
|
||
- [x] Ship script publishes sibling `AXP-Resurection-Pro-*-sunfish.zip`
|
||
(hardlink/copy of Lineage bacon zip). Current artifact renamed the same way
|
||
without rebuild.
|
||
|
||
### US-S4 — Userspace hardening (first attributable pack)
|
||
|
||
See `docs/009-us-s4-userspace-hardening.md`.
|
||
|
||
- [x] Stage 0 LAB measure (Enforcing + FBE)
|
||
- [x] US-S4-1: strip Openssh/rsync/procmem packages from Lineage `common.mk`
|
||
(`apply_009_drop_openssh_packages.py`); keep `init.openssh.rc` (AXP PRO)
|
||
- [x] LAB 2026-10-02: ship `20261002T151944Z` sideloaded + reinforced;
|
||
see `docs/009-lab-acceptance-20261002-uss4.md`
|
||
- [ ] **US-S4-2** (wired, pending bacon): drop Jelly + Gallery2 (AXP PRO
|
||
product oracle — Fossify stays); `apply_009_drop_jelly_gallery2.py` +
|
||
`NoLineageJelly`; see `docs/009-axp-pro-product-apps-20260324.md`
|
||
|
||
| Candidate | Priority | Notes |
|
||
|-----------|----------|-------|
|
||
| Confirm SELinux Enforcing + encryption | **done** | LAB Enforcing + FBE |
|
||
| ptrace/Yama | **done US-S5-1** | LAB `162840Z` `CONFIG_SECURITY_YAMA=y` |
|
||
| Debug/bugreport surface trim | **done US-S4-1** | AXP PRO product oracle |
|
||
| Jelly + Gallery2 debloat | **US-S4-2 wired** | AXP PRO product; Fossify kept |
|
||
| mount flags / noexec where stock allows | early | don't break HW |
|
||
| hardened_malloc | later | Graphene/AXP; portability cost |
|
||
| Broad Graphene framework (scopes, etc.) | later / deferred | high UX cost; not Magisk-clone MVP |
|
||
|
||
Ship patches under `vendor/sunfish-a16/patches/009-axp-los23-port/hardening/` with README
|
||
attribution. One stage per bacon when risk is unclear.
|
||
|
||
### US-S5 — Kernel CVE mega / more Graphene (on boot-OK pack)
|
||
|
||
**Do not** flash old `20260918*-axp-kh-cve` / `kh-full` (logo hang).
|
||
|
||
- [x] CVE clean-offset 429 + hi-batch1–3 on Magisk ship (`151944Z` LAB OK)
|
||
- [x] **US-S5-1** LAB OK (`162840Z`): `CONFIG_SECURITY_YAMA=y` +
|
||
`CONFIG_SLAB_FREELIST_RANDOM=y` — see
|
||
`docs/009-lab-acceptance-20261002-uss5.md`
|
||
- [ ] **Next cycle after the user image is accepted** (owner 2026-10-06):
|
||
close kernel CVE + Graphene/AXP hardening + deblob on that baseline.
|
||
Boot-check each batch (full `cve-strict` ~503 and `CVE-2022-49980`
|
||
alone hang at the Google logo). Inventory: remaining high-offset KEEP,
|
||
`49980` lock-scope port, Stage1 probes that failed
|
||
(allocsize/0007, ro/0007+0034, slab/0010+0018), `hardened_malloc`,
|
||
AXP deblob that still leaves radio, GPS and Widevine working.
|
||
Still out: location lockdown, bootloader relock, KernelSU, IPA removal.
|
||
- [x] **Next-bacon recipe guard (2026-10-07):** Stage1 uses
|
||
`patch --forward`, omits already-present `misc/0021`, and fails unless
|
||
`KSYM_NAME_LEN=192`; full feature-008 call-recording apply is restored.
|
||
Quarantine the earlier `bacon00949980` build and do not flash it.
|
||
|
||
Map updates: `docs/009-kernel-hardening-map.md` + Gate G attribution.
|
||
|
||
### US-S6 — Optional later
|
||
|
||
- [ ] Confirm `lineage_sunfish-user` after flash
|
||
(bacon started 2026-10-06, tmux `009-user-bacon`, log
|
||
`/srv/rom/artifacts/detach-009-user-bacon-20261006T203157Z.log`.
|
||
PRO stays branding. Check `ro.build.type=user` and `release-keys` on LAB)
|
||
- [ ] GPG-sign published SHA sums (AXP does; nice-to-have)
|
||
- [ ] LOS23 nightly rebase (vanilla + re-apply Magisk/stage1/keys)
|
||
- [ ] Bootloader lock — **wont**
|
||
|
||
---
|
||
|
||
## Host paths
|
||
|
||
| What | Path |
|
||
|------|------|
|
||
| Platform / OTA / AVB secrets | `$HOME/.android-certs/` on rom-dev01 (2026-09-29); prefer `/srv/rom/secrets/android-certs/` after root relocate |
|
||
| Certs tar backup | `/home/android-build/backups/android-certs-20260929T1637Z.tar.gz` |
|
||
| Phonesky JKS | `/srv/rom/secrets/phonesky-signing/` |
|
||
| microG JKS | `/srv/rom/secrets/microg-signing/` |
|
||
| Keygen script (repo) | `scripts/009-generate-release-keys.sh` |
|
||
| Signing README (vendor) | `vendor/sunfish-a16/patches/009-axp-los23-port/signing/README.md` |
|
||
| GOLDEN props | `/srv/rom/references/axp/golden/2026-09-13/` |
|
||
| AXP docs (mirror) | `…/axpos.org/docs/knowledge/{signatures,verifiedboot}/` |
|
||
|
||
---
|
||
|
||
## Explicit non-goals
|
||
|
||
- Bootloader relock / green verified-boot for end users.
|
||
- Replacing Phonesky/microG keys with platform keys.
|
||
- Committing private keys or passphrases to Git.
|
||
- Treating missing lock as Gate G failure.
|