009: Closure wave 3 deblob, handoff, and optional wave4/malloc bacons.

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>
This commit is contained in:
sunfish-adminandCursor committed 2026-10-09 17:31:01 +02:00
1 parent deaaf42caa
commit 3f941e8451
64 files changed
+6270 -86

No files matched your search

+3
View File
@@ -7,6 +7,8 @@
.idea/
.vscode/
.cursor/
# Codex (ChatGPT) local project config — review agent; never commit
.codex/
# Spec Kit skills live in .agents/skills/ (tracked); Cursor may mirror under .cursor/
# Spec Kit local state (also ignored under .specify/)
@@ -51,4 +53,5 @@ vendor/sunfish-a16/prebuilt/
# Local AXP OTA extracts (payload.bin ~1GB+)
AXP.OS-*/
AXP.OS-*.zip
*.bin
+15 -1
View File
@@ -11,7 +11,7 @@ This constitution is specific to `AXP_Resurection`. Sibling `sunfish-a16-rom`
is the KernelSU standby track. It does **not** inherit SSYSTEMS, company
GitLab, Redmine, or buildout-saar governance.
**Version**: 0.3.0 | **Ratified**: 2026-08-09 | **Last Amended**: 2026-09-16
**Version**: 0.3.1 | **Ratified**: 2026-08-09 | **Last Amended**: 2026-10-07
## Core Principles
@@ -49,6 +49,13 @@ functionality, except features that are obsolete, incompatible with LineageOS
23.2 / Android 16 / sunfish, or deliberately substituted (for example Magisk →
KernelSU-Next; AXP backup tooling → NeoBackup as an external user tool).
Degoogle as far as a free replacement keeps the function. microG plus the
project-patched Play Store stay. Location uses the LineageOS 23.2 stack so a
fix stays accurate and fast; do not apply AXP's location lockdown or delete
the IZat/XTRA/xtwifi blobs. A settings switch, default off, may stop LPP
(and the LPPE session it carries) plus the `loc_launcher` service that
starts XTRA and IZat.
`docs/axp-pro-parity.md` and `docs/hardening-backlog.md` are **non-normative**
product/backlog trackers. A row becomes a requirement only when promoted into a
feature `spec.md`. For an active feature, this constitution plus that feature's
@@ -578,3 +585,10 @@ Upstream freshness for root stack and similar deps: integrate from newest
compatible stable/tagged upstream (full git history), verify version codes
against the matching manager **before** long builds; pin only after LAB works.
Triggered by stale shallow ReSukiSU mirror yielding `KSU_VERSION=30701`.
### Amendment 0.3.1 — 2026-10-07
Owner: degoogle where a free alternative keeps the function; microG and the
patched Play Store stay. Location stays on the LineageOS 23.2 code (accurate
and fast), not AXP's location cuts. Moving apps such as OpenEUICC track the
latest upstream, not the revision frozen in an AXP zip.
+4
View File
@@ -18,6 +18,10 @@ unless the owner explicitly asks.
## Sources of truth
Resume point for the unfinished ROM (2026-10-08): `docs/009-handoff-20261008.md`.
Read that before the older remaining-work docs; several of those still describe
pre-boot state.
1. **Constitution** (`.specify/memory/constitution.md`) — global project rules
for this Magisk-resurrection track.
2. **Feature `spec.md`** — especially `specs/009-axp-los23-port/spec.md`.
+1 -1
View File
@@ -36,7 +36,7 @@ Host packaging: `vendor/sunfish-a16/{Android.mk,gms.mk,BoardConfig.mk}`.
1. Download latest **default** `com.google.android.gms-NNNNNNN.apk` (no `-hw`) +
`GsfProxy.apk` from microG GitHub releases. Match GOLDEN: `versionName`
must **not** end in `-hw` (Huawei flavor drops NLP Location services /
UI). Example: `v0.3.16.252432` → `com.google.android.gms-252432032.apk`.
UI). Newest stable staged 2026-10-06: `v0.3.17.252432` → `com.google.android.gms-252432034.apk` (versionCode 252432034, default flavor, PRESIGNED).
2. Keep **official signature** (`LOCAL_CERTIFICATE := PRESIGNED`). **Never**
re-sign / ABI-strip GmsCore for size — that breaks F-Droid updates.
3. Do **not** install microG’s `com.android.vending-*.apk` (FakeStore).
+2 -1
View File
@@ -38,7 +38,8 @@ Script (idempotent, marker `SUNFISH_A16_005_SIGSPOOF`):
Changes on the LOS tree:
1. Extend `isMicrogSigned()` to accept **project Phonesky** signer for
`com.android.vending`.
`com.android.vending`. Do **not** keep LOS `isDebuggable()` — AXP/GOLDEN
is `user` (`ro.debuggable=0`) and still spoofs via the toggle + grant.
2. Gate spoofing on `Settings.Secure.allow_signature_fake` (default **0**).
3. Require grant of `android.permission.FAKE_PACKAGE_SIGNATURE`.
4. Port EXTENDROM permission + Settings Developer Options toggle + strings.
+15 -2
View File
@@ -1,5 +1,11 @@
# 009 — Remaining path to AXP / Graphene-class ship (post clean-429)
**Stale as an execution order.** Boot, vendor SPL, lock-scope, release-keys,
and `hardened_malloc` moved on 2026-10-08. Start from
`docs/009-handoff-20261008.md`.
Non-normative roadmap after LAB **BOOT_OK** on Stage1+combo4+FORTIFY+Magisk+
**CVE clean-offset 429** (denylist `CVE-2022-49980`), kernel `#47`
(`20261001T180955Z-axp-stage1-combo4-cve-clean`). Authoritative requirements
@@ -8,6 +14,12 @@ remain `specs/009-axp-los23-port/spec.md` + constitution.
Goal (owner): **almost all** applicable AXP.OS / Graphene-class behaviour on
LOS 23.2 sunfish Magisk track — not a blind dump, not bootloader lock.
**2026-10-07 next-bacon correction:** `misc/0021` is already present in the
LOS kernel base. Stage1 must omit it and use `patch --forward`; otherwise GNU
patch auto-reverses it and shrinks `KSYM_NAME_LEN` 192→128 under Clang CFI.
The first corrected candidate retains 192 and applies only the
`CVE-2022-49980` lock-scope companion as its new kernel delta.
## Already landed (kernel / Magisk core)
| Layer | Status |
@@ -16,7 +28,7 @@ LOS 23.2 sunfish Magisk track — not a blind dump, not bootloader lock.
| Combo4 + Clang FORTIFY | Ship |
| Magisk preroot EXTENDROM-class | Ship |
| CVE clean-offset 429 | Ship (LAB OK) |
| `CVE-2022-49980` | Denylist until lock-scope port |
| `CVE-2022-49980` | Original one-liner denylisted; corrected lock-scope candidate next |
| High-offset CVE (~73) | Quarantine — triage started |
Recover/bisect baseline: `20260929T123401Z-axp-stage1-combo4` (`#32`).
@@ -27,7 +39,8 @@ Recover/bisect baseline: `20260929T123401Z-axp-stage1-combo4` (`#32`).
nf_tables leftovers still skipped. Next kernel depth: **US-S5-1** defconfig
(Yama + `SLAB_FREELIST_RANDOM`), not more bulk CVE.
2. **`CVE-2022-49980` proper port** — keep UAF fix without recursive
`udc_lock` (mainline lock-scope series). Optional; denylist is safe.
`udc_lock` using the reviewed lock-scope companion. Boot and USB gadget
smoke are still required; the original one-liner remains denylisted.
3. Failed Stage1 probes (allocsize/ro/slab) — low priority vs high-offset.
4. Graphene modern techniques / `hardened_malloc` — backlog (`docs/hardening-backlog.md`);
evaluate after CVE depth is stable.
+99
View File
@@ -0,0 +1,99 @@
# Closure parity — AXP (GOLDEN) vs AXPR (LAB) — 2026-10-09
Status after **wave 3** boot on LAB slot `_a`. Swissquote 9.11.0 in Island
**PASS** (owner confirmed). Root cause for the prior `-69` / `onPrivilegedAccess`
was **Lineage tools on system** (`bash`, `htop`, `nano`, `unrar`,
`system/addon.d/50-lineage.sh`), removed in wave 3 — not Magisk PIF/cmdline.
GOLDEN was not on USB for this pass; oracle uses
`/srv/rom/references/axp/golden/2026-09-13/` plus prior live diffs.
## Ship candidate (closure baseline)
| Item | Value |
|------|--------|
| Dir | `20261009T145241Z-axp-stage1-combo4-cve-hi-releasekeys` |
| Zip | `AXP-Resurection-Pro-23.2-20261009-sunfish.zip` |
| SHA256 | `7cd20e4d218bc04f0b4a31838a5bb9040db36148a2b38d90342b3bc9f224a5fd` |
| `boot.img` | Same dir (Magisk preroot, AVB flags 0) |
| deblob | wave1 euicc + wave2 carrier/rcs + wave3 lineage tools |
| Do not re-sideload | Owner booted this zip once |
## Identity / Gate G (LAB measured)
| Check | LAB | GOLDEN ref | Notes |
|-------|-----|------------|-------|
| `ro.build.type` | `user` | `user` | OK |
| `ro.build.tags` | `release-keys` | `release-keys` | OK |
| `ro.debuggable` | `0` | `0` | OK |
| SPL system | `2026-09-01` | `2026-03-03` | LOS23 newer |
| SPL vendor | `2023-09-05` | `2023-08-05` | deliberate AXPR guard |
| SELinux | Enforcing | Enforcing | OK |
| Boot state | orange / unlocked | same | OK |
| Phonesky | `/system/priv-app/Phonesky` | AXP path variant | function OK |
| microG | `/system_ext/app/GmsCore` | often `/data` update on GOLDEN | version may differ |
## System surface (commands)
**Removed in wave 3 (was LAB-only, absent on GOLDEN):** `bash`, `htop`,
`nano`, `unrar`, `addon.d/50-lineage.sh`.
**Still LAB-only vs GOLDEN (deferred wave 4):** Qualcomm `diag_*`, `test_diag`,
`imsrcsd`, `disable_configstore`, AOSP debug bins (`bpftool`, `alloctop`,
`pbtombstone`, `apexd-lsof.sh`). Not required for Swissquote PASS.
**Kept like GOLDEN:** `curl` (`/system/bin`), `vim` (`/system_ext`).
## Recovery / boot
Sunfish: **recovery-as-boot** only (`boot_a` / `boot_b`). No `fastboot flash
recovery`. Scratch install: flash `boot.img` to both slots → recovery → factory
reset → sideload full zip → reboot system.
## Repo vs next bacon
- `apply_009_drop_lab_only_commands.py` exists but is **commented out** in the
ship script (wave 4 deferred).
- Optional later: `scripts/009-bacon-hardened-malloc.sh` (012) — separate bacon;
not in the booted wave 3 image.
- Host keys: `/srv/rom/secrets/android-certs` (2026-10-09); `~/.android-certs`
symlinks there. Backup `~/backups/android-certs-secrets-20261009T1517Z.tar.gz`.
## Physical-SIM call gate
Before any wave beyond carrier/rcs deblob: owner places a **mobile-network**
voice call on LAB (SIM in slot, not Wi‑Fi calling only). Note result and time
in `docs/009-handoff-20261008.md`.
## GOLDEN parity capture
When GOLDEN `11071JEC203839` is on USB (read-only oracle):
```bash
adb -s 11071JEC203839 shell pm list packages -f | sort > /tmp/golden-packages.txt
adb -s 11231JEC207477 shell pm list packages -f | sort > /tmp/lab-packages.txt
diff -u /tmp/golden-packages.txt /tmp/lab-packages.txt
adb -s 11071JEC203839 shell cat /proc/cmdline > /tmp/golden-cmdline.txt
adb -s 11231JEC207477 shell cat /proc/cmdline > /tmp/lab-cmdline.txt
```
Offline fallback: `/srv/rom/references/axp/golden/2026-09-13/` (not a live
cmdline). Refresh this doc when a live GOLDEN dump exists.
## Follow-on bacons (not the booted closure zip)
| Script | Purpose |
|--------|---------|
| `scripts/009-bacon-wave4-deblob.sh` | wave 4 lab-only command drops |
| `scripts/009-bacon-hardened-malloc.sh` | 012 hardened_malloc (2 GiB regions) |
Launch on `rom-dev01` via `rom-detach`; never two ninjas on one `out/`.
## Scratch reinstall
Owner-driven virgin `/data` with the **same** closure zip: flash `boot.img` to
both slots → recovery factory reset → sideload → reboot system from menu.
## Open (non-blocking)
- LOS 23 nightly rebase; regen plan/tasks.
+13
View File
@@ -6,6 +6,19 @@ AVB/signing gaps (or “impossible on this stack”). This file is that list for
**kernel** half. Userspace AVB is tracked separately below as still open.
**LAB device:** `11231JEC207477` only.
> **2026-10-07 correction / next bacon:** LAB currently runs the
> `20261007T133154Z` PRO/user/release-keys artifact. Audit found that Stage1
> treated `misc/0021` as reversed and silently changed the LOS base's
> `KSYM_NAME_LEN` from 192 to 128 while `CONFIG_CFI_CLANG=y`. The next recipe
> removes that already-present patch, invokes Stage1 with `patch --forward`,
> and gates `KSYM_NAME_LEN=192`. Artifact `20261007T161115Z` is quarantined
> and must not be flashed. Its corrected replacement,
> `20261007T171530Z`, completed all artifact gates and keeps the original CVE
> one-liner denylisted while testing only the lock-scope companion as the new
> kernel delta. It remains **unflashed** pending LAB USB-gadget acceptance.
> The historical acceptance record below remains unchanged.
**Accepted kernel ship pack (LAB 2026-10-01):** Stage1 Graphene/AH + combo4 +
Clang FORTIFY + Magisk preroot + **CVE clean-offset 429** + **hi-batch1+2 (15)**
with `CVE-2022-49980` denylisted. Clean-only `#47` / hi-batch1 `#48` remain bisect GOODs.
+24
View File
@@ -0,0 +1,24 @@
# GOLDEN reference vs LAB packages — 2026-10-09
GOLDEN was **not** on USB. Comparison uses the frozen oracle dump
`/srv/rom/references/axp/golden/2026-09-13/packages.txt` (owner-installed /
spot-check format, not `pm list packages -f` of the full system image).
A live LAB `pm list packages -f` was **not** captured in this pass: scratch
started immediately after `adb reboot bootloader` (broken pipe during reboot).
## Reference excerpt (GOLDEN dump)
Curated hits from the oracle file include Island, Swissquote, microG/Phonesky,
F-Droid/Aurora, Lineage recorder, Yuh, etc. See the full file on `rom-dev01`.
## When GOLDEN is on USB
```bash
adb -s 11071JEC203839 shell pm list packages -f | sort > /tmp/golden-live.txt
adb -s 11231JEC207477 shell pm list packages -f | sort > /tmp/lab-live.txt
diff -u /tmp/golden-live.txt /tmp/lab-live.txt
```
Merge results into `docs/009-closure-parity-20261009.md` (system surface table
stays authoritative for deblob; package list diff is for user-installed drift).
+644
View File
@@ -0,0 +1,644 @@
# 009 handoff — 2026-10-08 evening
Start here. Older remaining-work docs still describe the tree from before
LAB booted the 2026-10-08 zip. This file is the execution map for the
final ROM. Do not commit unless the owner asks. Do not flash GOLDEN.
## Hard rules already paid for
- LAB only: `11231JEC207477`. GOLDEN `11071JEC203839` is the live oracle.
It may be plugged in at the same time. Never flash, wipe, or change Magisk
on it. Always `adb -s 11231JEC207477`.
- Do not factory-reset LAB except an **owner-driven scratch** (recovery wipe +
sideload closure zip). The recovery screen "Can't load Android system /
data may be corrupt / reason=init_user0_failed" is **not** corrupt data.
- Never lower `VENDOR_SECURITY_PATCH` below `2023-09-05`.
- KernelSU stays out of Magisk ship boots.
- Do not run AXP `Deblob.sh` defaults.
- Do not start a second ninja on the same `out/`.
- Sideload the full zip. A boot-only flash does not carry userspace.
- User builds reject an undeclared downgrade (error 28).
- `fastboot flash vendor` of a sparse image: `fastboot reboot fastboot`
(`is-userspace=yes`) and `fastboot -S 128M`. A 256 MB chunk drops USB.
Do not `fastboot -w`. Do not flash another build's vbmeta in the same test.
- Host: `ssh -F docs/ssh/config a16-build`. Long builds via
`/srv/rom/artifacts/bin/rom-detach` (tmux). Do not leave `mka` on a laptop SSH.
- Keep the last booting zip plus two test builds. Laptop disk was tight.
Host artifacts are canonical.
## Running now
**Closure baseline (booted, Swissquote Island PASS):** wave 3 zip
`20261009T145241Z-axp-stage1-combo4-cve-hi-releasekeys`
SHA256 `7cd20e4d218bc04f0b4a31838a5bb9040db36148a2b38d90342b3bc9f224a5fd`.
LAB slot `_a`, `boot_completed=1`. Owner confirmed Swissquote no longer
exits `-69` in Island after wave 3 (Lineage tools removal). Parity notes:
`docs/009-closure-parity-20261009.md`. Do not sideload this zip again.
**Closure checklist (2026-10-09 evening):**
| Item | Status |
|------|--------|
| Keys → `/srv/rom/secrets/android-certs` | done |
| Scratch reinstall (virgin `/data`, same closure zip) | `boot_a`/`boot_b` flashed 2026-10-09; recovery **unauthorized** — owner: USB auth → Factory reset → sideload zip in `/tmp/axp-closure-scratch/` (laptop) or host artifact dir |
| `~/.android-certs.pre-secrets-20261009` removed on host | done |
| GOLDEN package/cmdline parity | needs GOLDEN USB or reference dump |
| `bacon009wave4` (diag drop) | **running** `2026-10-09T15:29:17Z` — log `detach-bacon009wave4-20261009T152917Z.log` |
| `bacon012malloc` (hardened_malloc) | **queue after wave4** (one `out/`): `rom-detach bacon012malloc -- /srv/rom/artifacts/bin/009-bacon-hardened-malloc.sh` |
| Physical-SIM call gate | owner — before more radio deblob |
| Git commit of repo closure deltas | owner asked 2026-10-09 |
Check detach logs under `/srv/rom/artifacts/detach-bacon009*.log`. Do not run
two ninjas on the same `out/`.
Last closure build `bacon009tools` finished `exit=0` `2026-10-09T14:54:03Z`.
`bacon009deblob4` finished `exit=0` at `2026-10-09T12:01:31Z`. Zip
`/srv/rom/artifacts/009-axp-los23-port/20261009T120025Z-axp-stage1-combo4-cve-hi-releasekeys/AXP-Resurection-Pro-23.2-20261009-sunfish.zip`
SHA256 `bd1ce97896b5da31d4c22d4ffc3f2050ad204eea45768bbf86f7527f44b70c51`.
Sideloaded to LAB at 14:58 local (`Total xfer: 2.00x`). Owner
confirmed boot. Slot `_b`. `ro.build.date` `Fri Oct 9 11:04:11 UTC 2026`.
The nine wave-2 paths are absent from the image. `/data` leftovers
`com.google.android.ims` and `com.google.android.apps.carrier.carrierwifi`
were uninstalled for user 0 and user 10. `ims` and `qcrilmsgtunnel` remain.
Previous slot `_a` is the wave-1 zip. If this slot dies later,
`fastboot -s 11231JEC207477 --set-active=a`. Do not wipe. Do not sideload
this zip again.
Wave 2 drops
CarrierServices, CarrierSettings, CarrierWifi, CarrierSetup, RilConfigService,
grilservice, ImsServiceEntitlement, PresencePolling, and RcsService.
Qualcomm radio packages stay. AVB flags 0. Boot of this zip confirmed on LAB slot `_b`.
Log `/srv/rom/artifacts/detach-bacon009deblob4-20261009T110253Z.log`.
`bacon009deblob3` finished `exit=0` at `2026-10-09T10:18:22Z`,
was sideloaded to LAB at 12:33 local (`Total xfer: 2.00x`), and the owner
confirmed boot. Slot `_a`. `ro.build.date` is `Fri Oct 9 09:18:53 UTC 2026`.
`/product/priv-app/EuiccGoogle` and
`/system_ext/priv-app/EuiccGoogleOverlay` are absent. The `/data` leftover
`com.google.android.euicc` was uninstalled for user 0 and Island user 10
(`pm uninstall` Success, `pm path` empty). EuiccSupportPixel,
EuiccSupportPixelPermissions, and OpenEUICC remain.
`im.angry.openeuicc/.ui.PrivilegedMainActivity` stayed up and opened
`DownloadWizardActivity` (empty card). That is the wave-1 pass. Do not
sideload this zip again. Next gate is a physical-SIM call, then VoLTE and
VoWiFi, before any wave-2 bacon. If this slot dies later,
`fastboot -s 11231JEC207477 --set-active=b` (flags-0 zip). Do not wipe.
`bacon009deblob2` finished `exit=0` at `2026-10-09T09:09:32Z`. Do not
sideload it. Zip
`/srv/rom/artifacts/009-axp-los23-port/20261009T090831Z-axp-stage1-combo4-cve-hi-releasekeys/AXP-Resurection-Pro-23.2-20261009-sunfish.zip`
SHA256 `722e6d2e5cd639c3dfcea8af1585d5a741ea3ffd13ff5e0fad62d2c76722b70e`.
Flags 0. `EuiccGoogle.apk` and `EuiccGoogleOverlay` are still in the image.
`bacon009deblob` finished `exit=0` at `2026-10-09T07:38:53Z`. Do not
sideload it: flags are 0, but both Google LPA packages are still in the
images. Zip
`/srv/rom/artifacts/009-axp-los23-port/20261009T073753Z-axp-stage1-combo4-cve-hi-releasekeys/AXP-Resurection-Pro-23.2-20261009-sunfish.zip`
SHA256 `63851d964a259aefa8247883401481296dd7c6bb51dbcc13ee901361c7d8ab50`.
`bacon009timeouts` finished `exit=0` at `2026-10-08T22:02:51Z`. Zip, not
flashed:
`/srv/rom/artifacts/009-axp-los23-port/20261008T220151Z-axp-stage1-combo4-cve-hi-releasekeys/AXP-Resurection-Pro-23.2-20261008-sunfish.zip`
SHA256 `1717d23db2024f524dfe48364b13ee66c998364adfc10c3f9bde676a3d926a58`.
Same hash on the `lineage-23.2-20261008-PRO-sunfish.zip` twin. vbmeta flags
still 3. It carries the timeout settings and Droidify 0.7.8.
`bacon009avb0` finished `exit=0` at `2026-10-09T00:26:40Z`. Log line
`avb_infra=... flags=0`. Sideloaded to LAB `11231JEC207477` on 2026-10-09
morning. `Total xfer: 2.00x`. Before the sideload the running slot was
`_a` (cmdline zip). Recovery adb came back unauthorized, so the owner
reboots from the recovery menu. If it sticks on the Google logo,
`fastboot -s 11231JEC207477 --set-active=a`. Do not wipe.
Zip:
`/srv/rom/artifacts/009-axp-los23-port/20261009T002537Z-axp-stage1-combo4-cve-hi-releasekeys/AXP-Resurection-Pro-23.2-20261008-sunfish.zip`
SHA256 `1aabda75c672e1e7d7a7df046a3428ef50fa47aaa3501808ff3d12bbb075e96b`.
Local copy `/tmp/axp-flash/AXP-Resurection-Pro-23.2-20261009-avb0-sunfish.zip`.
No ninja is running. The deblob Soong files are still only on the laptop.
## What is on LAB right now
**Closure baseline (wave 3)** unless a scratch reinstall is in progress.
| | |
|---|---|
| Artifact dir | `20261009T145241Z-axp-stage1-combo4-cve-hi-releasekeys` |
| Zip | `AXP-Resurection-Pro-23.2-20261009-sunfish.zip` |
| SHA256 | `7cd20e4d218bc04f0b4a31838a5bb9040db36148a2b38d90342b3bc9f224a5fd` |
| Host path | `/srv/rom/artifacts/009-axp-los23-port/20261009T145241Z-axp-stage1-combo4-cve-hi-releasekeys/` |
| Slot (last known) | `_a` after wave 3 sideload |
| deblob | wave1 euicc + wave2 carrier/rcs + wave3 lineage tools |
| AVB | flags `0` on shipped `boot.img` |
| Swissquote Island | **PASS** after wave 3 (owner confirmed); prior `-69` was Lineage tools on system |
Wave 3 removes `bash`, `htop`, `nano`, `unrar`, `system/addon.d/50-lineage.sh`.
`curl` and `vim` stay. Wave 4 diag/`bpftool`/… are **still present** on this
image; use `scripts/009-bacon-wave4-deblob.sh` for a candidate without them.
**Rollback slots (do not wipe casually):** other slot may hold an older zip
(wave 2 on `_b`, wave 1, or flags-only builds). `fastboot -s 11231JEC207477
--set-active=a|b` only if the active slot boot-loops.
**Scratch reinstall (owner 2026-10-09):** bootloader → `fastboot flash boot_a`
and `boot_b` with closure `boot.img` → recovery → **Factory reset** → sideload
the closure zip above → **Reboot system** from recovery (not `adb reboot` if adb
is unauthorized). Re-verify Magisk, Island, Swissquote after setup.
**Physical-SIM call gate:** place a normal mobile call (not only Wi‑Fi) before
any further Qualcomm radio deblob. Record pass/fail in this file.
Parity matrix: `docs/009-closure-parity-20261009.md`.
## 1. hardened_malloc — 2 GiB rebacon finished, not flashed
`bacon012malloc3` finished `exit=0` at `2026-10-08T18:43:47Z` (58 min, ccache).
Zip: `/srv/rom/artifacts/009-axp-los23-port/20261008T184238Z-axp-stage1-combo4-cve-hi-releasekeys/AXP-Resurection-Pro-23.2-20261008-sunfish.zip`
SHA256 `f85a0233be1ca83d6a866c081e7cf314e9b5e547a4d6324addb7139ed90030e9`.
Vendor SPL `2023-09-05`. Not sideloaded. The phone is still on the morning
zip in slot `a`.
The first malloc zip boot-looped. `bacon012malloc2`
(`20261008T170027Z`, SHA256
`28c7f0bfa11c60f24e86d35c080cd1e52c244c3640168e34673e4ef0252d690c`)
was sideloaded to LAB slot `b` at 19:17 local and rebooted at the Google logo.
Recovered without a wipe: `fastboot --set-active=a`, morning zip booted
(`boot_completed=1`, vendor SPL `2023-09-05`). Slot `b` still holds the bad
build and is not marked successful. Do not set it active.
Cause: sunfish msm-4.14 is `CONFIG_ARM64_VA_BITS=39` (3-level page tables),
on LAB and on GOLDEN. Upstream `Android.bp` uses
`CONFIG_CLASS_REGION_SIZE=34359738368` (32 GiB), which needs 48-bit VA;
the README says the defaults do not work on 39-bit. DivestOS Patch.sh
(LineageOS-17.1/20.0/21.0) does
`sed -i 's/34359738368/2147483648/' Android.bp` ("revert 48-bit address
space requirement"). GOLDEN ships hardened_malloc on the same 39-bit kernel
and its `system_server` holds about 210 GiB of `---p` reservations, which
matches 2 GiB regions. `config/light.mk` is still 32 GiB; it is not a fix.
Fixed in `apply_012_hardened_malloc.py` and applied on the host tree:
- region size `2147483648`; the script exits if it is not 2 GiB;
- The `// 32GiB` comment next to `2147483648` was replaced after
`bacon012malloc3` exited. The line now says
`// 2GiB, 39-bit VA (DivestOS/AXP)`.
- the exclusion helper had been inserted between `__attribute__((noinline))`
and `__libc_preinit_impl`, which moved `noinline` off the function bionic
needs it on. The applier now places the helper before the attribute,
repairs the misplaced form, and exits if `noinline` is lost.
Next: sideload the `20261008T184238Z` zip to LAB only (it lands on slot `b`),
then smoke boot, USB, Magisk, camera, and that surfaceflinger stays on Scudo.
If it loops at the Google logo again, `fastboot --set-active=a`. Do not
factory-reset.
Original notes for the first attempt follow.
Session `bacon012malloc2`, started `2026-10-08T10:25:49Z`.
- Status: `/srv/rom/artifacts/detach-bacon012malloc2.status`
- Log: `/srv/rom/artifacts/detach-bacon012malloc2-20261008T102549Z.log`
- At 13:55 local: still `running`, ninja ~10 % (6453/63504), no error.
`libc` had already linked at `2026-10-08T10:30:05Z`.
- A 30-minute watcher lived in the 2026-10-08 chat. If that chat is gone,
read the status file. Do not start another ninja.
Wrapper: `scripts/009-bacon-hardened-malloc.sh`. It applies
`vendor/sunfish-a16/patches/012-hardened-malloc/apply_012_hardened_malloc.py`,
builds `libc`, then execs the main Stage1 bacon. The host tree is already
patched, so a plain re-run of the main bacon script also ships malloc.
Do not revert `bionic` unless this experiment is abandoned.
Pin: GrapheneOS `hardened_malloc` `1d7fc7ffe0f8068a32ab2ba33df7515e7bad3f5f`
(branch 14 tip, "support GCC15"). 64-bit libc uses it; 32-bit stays Scudo.
`frameworks/av` `RLIMIT_AS` hunk does not exist on this LOS 23 tree and was
not ported. Sunfish has no MTE; the `device_has_arm_mte` product variable was
stripped from `Android.bp`.
Exclusions, required: `/system/bin/surfaceflinger` exact path, and any exe
whose path contains `camera`. Real LAB paths:
`/vendor/bin/hw/android.hardware.camera.provider@2.7-service-google` and
`/system/bin/cameraserver`. Env `DISABLE_HARDENED_MALLOC` also disables it.
`init.rc` sets `vm.max_map_count` to `1048576`.
Previous failure, already fixed: `h_malloc_info` was undeclared
(`h_malloc.h` omits it on Android). Declaration is in `malloc_common.h`.
The dead log is `detach-bacon012malloc-20261008T101328Z.log`. Do not treat
that log as the current job.
This bacon does **not** contain the Swissquote cmdline fix, the Settings
timeout patches, deblob, or AVB flags 0.
When it finishes with `exit=0`: verify the zip hash, sideload only LAB,
then smoke boot, USB, Magisk, camera, and that surfaceflinger is still on
Scudo. Keep the 20261008 zip until that smoke passes. If the bacon fails,
diagnose before relaunching. Do not factory-reset.
## 2. Swissquote — next bacon only after malloc boots
Measured 2026-10-08 with both phones online. Same app on both:
`com.swissquote.android` 9.10.0 (`versionCode=91000000`). Both have Island
user 10, Zygisk, DenyList on, and the same module set except GOLDEN also
has `flag-secure-disabler`. DenyList on both already has
- `com.swissquote.android|com.swissquote.android`
- `com.swissquote.android|com.swissquote.android_zygote`
DenyList has no user id. It covers Island. Vector's scope is `user_id=0`
on both, and GOLDEN does not even have Swissquote installed on user 0
(only user 10) and it still passes. The module screen that cannot pick
work-profile apps is a red herring. Do not chase it.
### Proven ROM difference
GOLDEN `/system/bin/init` contains `chmod("/proc/cmdline", 0440)`.
LAB's init does not. Magisk's binary on neither phone contains that string.
No module script was found chmod'ing it.
| | LAB | GOLDEN |
|---|---|---|
| `/proc/cmdline` mode | `0444` root:radio | `0440` root:radio |
| shell can read it | yes, sees `verifiedbootstate=orange` | no (`Permission denied`) |
| `init.rc` | `chown root radio` only | same `chown`, no chmod line |
Both `init.rc` files only chown. The mode change is a C call inside GOLDEN's
init. Clone that call. Do not implement it as a Magisk module. The owner
asked for that specifically.
Where it lives: `apply_009_cmdline_mode.py`, called by the ship bacon.
It inserts AOSP's `CHECKCALL(chmod("/proc/cmdline", 0440))` in
`first_stage_init.cpp` immediately after the proc mount. That CHECKCALL
is what logs `chmod("/proc/cmdline", 0440) failed`, the string found in
GOLDEN's init. LOS 23.2 already chmod's `/proc/bootconfig` and did not
chmod cmdline. `bacon009cmdline` is the build for this and keeps the
2 GiB hardened_malloc already in the tree. Do not flash it until it
finishes and the owner asks.
The compiling malloc tree does not have this call (`init.cpp` has no
`cmdline` match; `init.rc` only has the chown at line 1282).
### 2026-10-08 22:30 — writer found, do not bake the spoof into the ROM
Play Integrity Fix `service.sh` (same file on both phones) runs after
`sys.boot_completed=1` and calls `resetprop_if_diff`. That helper does
nothing when the property is empty:
```
[ -z "$CURRENT" ] || [ "$CURRENT" = "$EXPECTED" ] || resetprop -n "$NAME" "$EXPECTED"
```
`spoofProps=false` does not gate this block. Both phones now use the same
PIF print: `oriole` Pixel 6 CANARY, `ZP11.260618.005`. The earlier
`tegu` / `stallion` note is stale.
GOLDEN's bootloader cmdline creates the properties, so the module rewrites
them. LAB's cmdline never creates `vbmeta.device_state` or `veritymode`, so
the same script skips them.
| `getprop` | LAB | GOLDEN |
|---|---|---|
| `ro.boot.verifiedbootstate` | `green` (was `orange`) | `green` (was `orange`) |
| `ro.boot.flash.locked` | `1` | `1` |
| `ro.boot.vbmeta.device_state` | empty | `locked` (cmdline said `unlocked`) |
| `ro.boot.veritymode` | empty | `enforcing` (real cmdline value, not a spoof) |
| `sys.oem_unlock_allowed` | empty on the 22:32 read | `0` (cmdline-era value was `1`) |
Vector's `/data/adb/lspd/log/props.txt` on GOLDEN still records the real
values (`orange`, `unlocked`, `enforcing`, `oem_unlock_allowed=1`). They
are not in `/data/property/persistent_properties`. Island sees the rewritten
`getprop` because the property area is global. It cannot open
`/proc/cmdline` (`0440`), so it never sees the raw `unlocked` / `orange`.
The owner said Swissquote on GOLDEN also worked with no Magisk modules.
The PIF rewrite is therefore not the first fix. Hope is that the init
chmod alone is enough. Do not set `sys.oem_unlock_allowed=0` in the ROM;
it conflicts with the OEM-unlock escape hatch. Do not add the missing
cmdline tokens in this bacon. If Island still fails after the chmod,
those tokens belong to the separate AVB flags experiment.
TEESimulator 71 on LAB (`71-17be1f0`) did not change the outcome. Its
target list is still only `com.google.android.gms`, `com.android.vending`,
and `io.github.vvb2060.keyattestation`. GOLDEN is still TEE 67 with the
same three targets. The app is not in that list.
Test only inside Island. GOLDEN does not have the app on user 0, and the
owner said it has never worked in the main profile there. LAB currently
has it on user 0 and user 10. `ps` shows `magiskd`, `su`, `zygiskd64`,
`teesim`, and `vectord` on both phones, so a visible process list does not
explain why Island passes on GOLDEN.
### 2026-10-08 22:53 — chmod booted, Island still closes
Slot `_a`, zip SHA256 `40ed3213…ccfa`. `/proc/cmdline` is `0440`; shell
gets permission denied; root still reads `orange`. The app still exits in
Island and in the main profile. The 22:52 log is the same sequence as
before the chmod: DenyList hit, Play services `261200000` vs microG
`252432034`, `libsigner.so`, SELinux and `/cache` denied, `ps`, suppressed
toast, process dies. GOLDEN's microG is older (`252432033`) and Island
works there, so the version warning is not the gap.
What Island can still see: `ro.boot.vbmeta.device_state` and
`ro.boot.veritymode` are empty on LAB. GOLDEN's bootloader cmdline creates
both (`unlocked` and `enforcing`) even with no modules. Do not chase that
inside the timeouts bacon. It stays with the AVB flags 0 experiment.
### If the app still fails after the chmod
These are owner-installed modules, not ROM payload
(`docs/009-magisk-preinstall-runbook.md`):
1. TEESimulator version is already ruled out. LAB is `71-17be1f0`, GOLDEN
is `67-4f42350`, same three targets, and 71 changed nothing. Config
holds a keybox; do not copy it into git or into a doc.
2. PIF print is already the same on both (`oriole` Pixel 6 CANARY). Do not
chase `stallion`. A wrong CANARY print on sunfish already broke this
surface once (`comet`); that is not the current gap.
Banking flow if the app is reinstalled (constitution): Aurora, do not open,
DenyList including processes before the first process, clone into Island
(`com.oasisfeng.island.fdroid`, not Insular), first launch inside Island,
Play update from the work profile. Spec 009 still marks banking deferred.
The owner asked to make Swissquote work on LAB anyway. Yuh on GOLDEN needed
Vector's bootloader spoofer on top of DenyList; Swissquote on GOLDEN did not.
## 3. After that, one bacon each
Do not stack these into the malloc zip or into each other.
1. **Settings timeouts and sensitive QS tiles.** Already inside
`bacon009timeouts` (see "Running tonight"). Defaults stay off except
the sensitive QS tiles, which ask for the PIN on the lock screen.
After boot, confirm the three settings exist and Droidify is 0.7.8.
2. **Selective deblob.** Own bacon, after the timeouts zip exists. Study
and the wave-1 Soong override are in the section below. Do not run
`Deblob.sh`. Do not put wave 2 in the same bacon.
3. **AVB flags 0.** Script is ready, not launched.
`scripts/009-bacon-avb-flags0.sh` exports `AXP_AVB_FLAGS=0` and
`WITH_AVB=true`. That is the only Lineage switch: `BoardConfigLineage.mk`
skips `--flags 3`. Same `avb.pem`, same `SHA256_RSA4096`. Pubkey sha1
`ee85ac5a2dcec0846e95dd575f874a659026d790`. The default ship script still
expects flags 3 unless that env is set. Do not rsync the ship script over
`/srv/rom/artifacts/bin/` while `bacon009timeouts` is running. Launch only
after that status file shows `exit=0`, and do not flash until the owner
asks. On LAB check Magisk, a full sideload, dm-verity, and whether the
raw cmdline gained `veritymode` and `vbmeta.device_state`. Relock stays
**wont**. If it boot-loops, set the active slot back to the previous zip.
Measured on the current LAB image, before that experiment:
`getprop ro.boot.vbmeta.device_state` and `getprop ro.boot.veritymode`
are empty. `ro.boot.verifiedbootstate=green` is the PIF spoof; the raw
cmdline is still `verifiedbootstate=orange`. These two missing tokens
are bootloader cmdline properties. Flags 3 is
`HASHTREE_DISABLED|VERIFICATION_DISABLED`. GOLDEN's raw cmdline, read
earlier the same night, contains `androidboot.vbmeta.device_state=unlocked`
and `androidboot.veritymode=enforcing`. Do not write `locked` or
`enforcing` into the ROM, and do not set `sys.oem_unlock_allowed=0`.
4. **Lock-scope concurrency review.**
`vendor/sunfish-a16/patches/009-kernel-hardening/ports/cve-2022-49980-lock-scope.patch`.
Boot and USB already passed on LAB. Review the companion. Do not revert
it unless a new failure shows up. The one-liner stays denylisted.
5. **Leftover high-offset kernel CVEs.** Small reviewed batches (≤10),
strict applicator, boot each batch. nf_tables leftovers stay skipped.
Failed Stage1 probes (allocsize/0007, ro/0007+0034, slab) are low
priority. Do not reflash `20260918*-axp-kh-cve` or `kh-full`.
6. **Droidify.** `bacon009timeouts` ships `0.7.8` (`versionCode=780`,
targetSdk 37). Host file
`/srv/rom/prebuilts/axp-apps/Droidify.apk`, SHA256
`3e7934ff9b811c2f77794793381241095a02bac2d163bf922cb5a5a0ed25ca82`.
The previous `0.7.7` apk is kept beside it as `Droidify-0.7.7.apk`.
Aurora and Fossify stay.
7. **GPG SHA256 sums** of published zips, the way AXP does. Owner on
2026-10-09: not needed now. Do not start it. AXP's current publish
path is a SHA512 file plus `gpg --verify` on that file
(`docs/knowledge/signatures`).
8. **Release-key custody.** Signing works (the booted zip is `release-keys`).
Keys are still `$HOME/.android-certs/` on rom-dev01 plus a gitignored
copy under `vendor/lineage-priv/keys` and `vendor/sunfish-a16/certs`.
Move the private material to `/srv/rom/secrets/` and confirm the
encrypted offline copy. Never commit `.pk8`. Cert tar already at
`/home/android-build/backups/android-certs-20260929T1637Z.tar.gz`.
9. **SetupWizard language bounce** after Wi-Fi connect. Wi-Fi stays up.
Non-blocking. Looked at on 2026-10-08: `lineage_wizard_script.xml` goes
locale, then SIM, then network, and every network result advances to
datetime. There is no result edge that returns to language. Do not invent
a patch. `LocaleActivity` can still call `updateLocale` from the SIM MCC
while that page is open; that is a different moment from the Wi-Fi step.
10. **LOS 23 nightly rebase**, then re-apply this pack. Do it after the
malloc and cmdline bacons, not during them.
11. **Regenerate** `plan.md` / `tasks.md` after any `spec.md` edit.
Spec Kit order stands. Do not use upstream `taskstoissues`.
## Selective deblob — studied 2026-10-08
Do not run AXP `Deblob.sh`. It is not in the public AXP-OS repos or in
`/srv/rom/references/axp/archive/codeberg/git`, and its usual defaults drop
CNE, IZat/XTRA, and sometimes Widevine. Shipped GOLDEN still has
`com.qualcomm.qti.cne`. Spec FR-018 and the constitution forbid deleting
the IZat/XTRA/xtwifi blobs. The positive list is the owner's finish list.
The `[ -e $f ]` loop that printed ABSENT for every vendor binary was a
quoting bug. `ls` of the literal path is the check: "Permission denied"
means the file exists and shell cannot stat it; "No such file" means gone.
### Positive list — do not override, do not delete
Present on the booted LAB image (`11231JEC207477`). GPS daemons and
`imsrcsd` were also checked on GOLDEN before it was unplugged.
| Keep | LAB evidence | GOLDEN |
|---|---|---|
| `xtra-daemon`, `lowi-server`, `loc_launcher` | `/vendor/bin/…` exists (permission denied) | those three paths are "No such file" on GOLDEN. Keep them. Default is the precise Lineage stack. The two Settings switches below, both default off, are how the owner turns that assistance off and matches the AXP posture without deleting the binaries. |
| `xtwifi.conf` | copied by `sunfish-vendor.mk` to `/vendor/etc/xtwifi.conf` | absent on GOLDEN (`No such file`). Keep the copy rule. The location switches are what turn the assistance off. |
| Widevine | `/vendor/bin/hw/android.hardware.drm-service.widevine` exists. Soong module `android.hardware.drm-service.widevine` | same path exists |
| IMS | `/system_ext/priv-app/ims/ims.apk` (`org.codeaurora.ims`); `/vendor/bin/ims_rtp_daemon`, `imsdatadaemon`, `imsqmidaemon`, `imsrcsd` all exist | `imsrcsd` is absent on GOLDEN. Keep it. It is the Qualcomm RCS daemon, and the positive list says IMS. |
| IPA | `/vendor/bin/ipacm` exists | directory listing earlier the same night also showed it on GOLDEN |
| IWlan | `/vendor/app/IWlanService/IWlanService.apk` (`vendor.qti.iwlan`); `vendor.qti.hardware.data.iwlan@1.0.so` exists on both | keep |
| CNE | `/vendor/app/CneApp/CneApp.apk` (`com.qualcomm.qti.cne`). Soong `CneApp`, `libcne*` | package was on GOLDEN too |
| eUICC hardware | `/system_ext/priv-app/EuiccSupportPixel/EuiccSupportPixel.apk` and `EuiccSupportPixelPermissions` (`com.google.euiccpixel`, `.permissions`) | both phones |
| OpenEUICC | `/system_ext/priv-app/OpenEUICC/OpenEUICC.apk` (`im.angry.openeuicc`) | both phones |
| Radio packages already on both | `com.qualcomm.qti.telephonyservice`, `com.qualcomm.timeservice`, `com.qualcomm.uimremoteclient`, `com.qualcomm.uimremoteserver`, `com.android.carrierconfig`, `com.android.carrierdefaultapp`, `com.android.providers.telephony` | keep |
GPS precision stays the default. `apply_009_supl_privacy.py` is already in
the ship bacon. Both switches live in Location settings and start off, so
SUPL, LPP, XTRA and IZat keep running:
- "Force disable Secure User Plane Location (SUPL)" (`force_disable_supl`).
On sets `SUPL_MODE=0`. IMSI and the phone number are never sent to a SUPL
server, whether this switch is on or off.
- "Stop extra location assistance" (`force_disable_location_assist`). On
sets `LPP_PROFILE=0` (LPPE goes with that session), skips the framework
XTRA download, and stops `loc_launcher` (`xtra-daemon`, lowi, IZat).
Satellite fixes keep working. Turning it off starts `loc_launcher` again.
That second switch is the AXP location posture. Do not delete the blobs
and do not change either default to on.
`/vendor/lib64/libimsmedia_jni.so` is missing on both phones. The JNI
libs ship inside the ims app (`system_ext_priv-app_ims_lib_arm64_libimsmedia_jni_so`).
That is not a deletion.
### Wave 1 — booted 2026-10-09, pass
`bacon009deblob3` (`20261009T101715Z`, SHA256
`efbec33ac2bafc75ee2f90970a9286f51fe8073b61fc3ed11c01689c9e8a28cf`) is
the running LAB slot `_a`. `EuiccGoogle` and `EuiccGoogleOverlay` are
absent from the image. The `/data` update survived the sideload and was
removed for user 0 and user 10. EuiccSupportPixel and OpenEUICC stayed.
OpenEUICC opened onto `DownloadWizardActivity`. Empty card is the pass.
The notes below are the failed attempts; do not rebuild them.
Why the old override did nothing: `EuiccGoogle` is a Soong
`android_app_import` in `vendor/google/sunfish/Android.bp` (`product_specific:
true`). `NoEuiccGoogle` was a Make `BUILD_PHONY_PACKAGE`. Make's
`LOCAL_OVERRIDES_PACKAGES` only filters Make modules (`build/make/core/main.mk`
`module-overrides`). `out/soong/soong.lineage_sunfish.variables` contains
both `NoEuiccGoogle` and `EuiccGoogle` in the product package list, and
`PackageNameOverrides` is empty.
Laptop tree now, not rsynced:
- `vendor/sunfish-a16/deblob/Android.bp` — `NoEuiccGoogle` (`product_specific`)
overrides `EuiccGoogle`; `NoEuiccGoogleOverlay` (`system_ext_specific`)
overrides `EuiccGoogleOverlay`. It does not name `EuiccSupportPixel`.
- `vendor/sunfish-a16/apps.mk` installs both phonies.
- The Make phony was removed from `vendor/sunfish-a16/Android.mk`. Do not
put an `Android.bp` beside that `Android.mk`; Soong would skip the
prebuilt APKs in the same directory.
After the timeouts bacon exits, rsync those three files, then a deblob
bacon. Do not flash until asked. On the installed-files list, confirm
`EuiccGoogle.apk` and `EuiccGoogleOverlay.apk` are gone and
`EuiccSupportPixel.apk` and `OpenEUICC.apk` remain. The `/data` update
can survive a sideload. If `pm path com.google.android.euicc` still points
at `/data/app`, `adb -s 11231JEC207477 shell pm uninstall --user 0
com.google.android.euicc` after boot. The owner has no eSIM profile, so
do not block on downloading one. Open OpenEUICC: it must start and not
crash. An empty card or "no profiles" is a pass. `pm path` must still
show EuiccSupportPixel and OpenEUICC.
### Wave 2 — not in the wave-1 bacon
Rechecked read-only on GOLDEN `11071JEC203839` at 2026-10-08 23:24 local,
with LAB unplugged. `pm path` printed no path for every row below, and
`ls` returned `No such file` for `xtra-daemon`, `lowi-server`,
`loc_launcher`, `imsrcsd`, and `/vendor/etc/xtwifi.conf`. Present on
GOLDEN, same as LAB: EuiccSupportPixel, EuiccSupportPixelPermissions,
OpenEUICC (`/system_ext/priv-app/OpenEUICC_AXP-OS/OpenEUICC_AXP-OS.apk`),
CneApp, `org.codeaurora.ims`, IWlanService, QtiTelephonyService,
TimeService, both uimremote packages, CarrierConfig, CarrierDefaultApp,
TelephonyProvider, `ims_rtp_daemon`, `imsdatadaemon`, `imsqmidaemon`,
`ipacm`, and the Widevine HAL. One bacon, after wave 1 has booted and a
physical-SIM call, VoLTE, and VoWiFi have passed. No eSIM profile is
required. Same Soong-phony pattern, matching partition flag.
Google, not the Qualcomm IMS stack:
| Module | Partition flag | LAB path | Package |
|---|---|---|---|
| `CarrierServices` | `product_specific` | `/product/priv-app/CarrierServices` (active path is `/data/app/…/com.google.android.ims…`) | `com.google.android.ims` |
| `CarrierSettings` | `product_specific` | `/product/priv-app/CarrierSettings/CarrierSettings.apk` | `com.google.android.carrier` |
| `CarrierWifi` | `product_specific` | active path `/data/app/…/com.google.android.apps.carrier.carrierwifi…` | `com.google.android.apps.carrier.carrierwifi` |
| `CarrierSetup` | `system_ext_specific` | `/system_ext/priv-app/CarrierSetup/CarrierSetup.apk` | `com.google.android.carriersetup` |
| `RilConfigService` | `system_ext_specific` | `/system_ext/priv-app/RilConfigService/RilConfigService.apk` | `com.google.RilConfigService` |
| `grilservice` | `system_ext_specific` | `/system_ext/priv-app/grilservice/grilservice.apk` | `com.google.android.grilservice` |
AOSP, not vendor blobs. Override only after the same call test:
- `ImsServiceEntitlement` in `packages/apps/ImsServiceEntitlement/Android.bp`,
installed at `/product/priv-app/ImsServiceEntitlement/ImsServiceEntitlement.apk`
(`com.android.imsserviceentitlement`).
- `com.android.service.ims` is `/system_ext/app/RcsService/RcsService.apk`.
- `com.android.service.ims.presence` is
`/system_ext/app/PresencePolling/PresencePolling.apk`.
Grep `name: "RcsService"` and `name: "PresencePolling"` before writing
the phony. The names were not in `packages/services/Telephony/Android.bp`.
Leave these alone until a zip that removed only the Google rows above
has made a call. They were absent on GOLDEN, and they sit next to radio:
- `atfwd` → `/system_ext/app/atfwd/atfwd.apk` (`com.qualcomm.atfwd`)
- `qcrilmsgtunnel` → `/system_ext/priv-app/qcrilmsgtunnel/qcrilmsgtunnel.apk`
- `uceShimService` → `/system_ext/app/uceShimService/uceShimService.apk`
- `com.qualcomm.qti.services.secureui` → same directory name under
`/system_ext/app/`
- `CACertService` → `/vendor/app/CACertService/CACertService.apk`
(`vendor.qti.hardware.cacert.server`, `soc_specific: true`)
Do not remove `ims`, `QtiTelephonyService`, `CneApp`, `xtra-daemon`,
`lowi-server`, `loc_launcher`, `imsrcsd`, Widevine, or `EuiccSupportPixel`
in either wave.
## Already on the booted zip — do not redo
- Magisk 30.7 preroot (`/system_ext/bin/magisk`). GOLDEN oracle is still
Magisk 29.0. Ship stays 30.7 unless 30.7 itself breaks a bank after the
cmdline fix. Dormant until Additional Setup.
- microG GmsCore `0.3.17.252432` (`252432034`), not `-hw`.
- Phonesky from `/srv/rom/prebuilts/phonesky/`. Never git.
- OpenEUICC privileged, platform cert, deps SDK forced to 36
(`prebuilts/openeuicc-deps` `5402167` was generated for SDK 37).
- Sigspoof (`apply_005_sigspoof.py`) and call recording / FLAG_SECURE
(`apply_008_call_rec_flag_secure.py`) are in the ship script.
FLAG_SECURE override is a project extra, default off.
- Private DNS, connectivity check, the two location switches (both default
off, so SUPL / XTRA / IZat stay on for precision), Yama
`persist.native_debug`, disable-app.
- Jelly stays. Gallery is Fossify. Gallery2 APK stays absent. WebView stays
LOS 23. No NeoLauncher. No `axp.os` OTA endpoints.
- Vendor SPL guard: `apply_009_axp_identity.py` rewrites a `2023-08-05`
rollback back to `2023-09-05` and exits otherwise. AXP GOLDEN really is
`2023-08-05`. This divergence is deliberate.
- `LINEAGE_BUILDTYPE=PRO`, `user`, project AVB key, flags 3.
- Stage1 + combo4 + FORTIFY + CVE-clean + hi-batch1–3 + US-S5-1 Yama/slab
+ lock-scope. `KSYM_NAME_LEN` stays 192. Stage1 must use `patch --forward`
and must omit `misc/0021` (already in the LOS base; GNU patch would
reverse it and shrink KSYM 192→128).
## init_user0 — do not rediscover
The 17:15 zip (`SHA256 2c16d2385094197181c07dde4e9604137883eefedc2b346cc1071e38abb57a0b`)
lowered vendor SPL from `2023-09-05` to `2023-08-05`. Keymaster blobs on the
existing install refuse that rollback. `vdc cryptfs init_user0` fails and
recovery writes
`boot-recovery --prompt_and_wipe_data --reason=init_user0_failed`.
Proof, same userdata: nols boot failed, full 13:31 boot on the 17:15
userspace failed, flashing only the 13:31 vendor (`2023-09-05`) made Android
boot. Data was fine. The 13:31 zip
(`SHA256 2c4eb9cb831e96a57c2c068e5c044ae5d29ef140f53817e9246cac3c61dbe008`)
is the older known-good, without lock-scope. Quarantine
`20261007T161115Z` (`SHA256 df2ad13f6afeb2d25f5e8f7a32ee2caa0b926550000e5cb3078c8a56d8efffd7`)
stays unflashed.
Emulator and an on-screen boot debugger do not explain this. The emulator
cannot boot the sunfish 4.14 / keymaster / userdata stack. The debug console
is UART `ttyMSM0`. pstore on this LAB was encrypted and unreadable.
`docs/007-debug-boot-panic-screen.md`.
## Uncommitted work the next session must keep
Not committed. Do not revert while picking up a later item.
- `apply_009_axp_identity.py` — vendor SPL stays `2023-09-05`.
- `scripts/009-bacon-axp-stage1-combo4-cve-hi-releasekeys.sh` — lock-scope
applied, vendor SPL grep, Jelly/Fossify gates, the userspace applies listed
above.
- `scripts/009-bacon-hardened-malloc.sh` and
`vendor/sunfish-a16/patches/012-hardened-malloc/`.
- `tests/test_009_next_bacon_guards.py`, `tests/test_012_hardened_malloc.py`,
`tests/test_009_a15_reboot_fixups.py`, `tests/test_009_a15_small_ports.py`.
- Host tree under `/srv/rom/src/lineage` is dirty with the malloc apply and
the identity/kernel applies. Laptop scripts were rsynced to
`/srv/rom/artifacts/bin/`.
## Docs that will lie to you
- `docs/009-axp-graphene-remaining.md` — pre-boot order. Pointer added.
- `docs/009-hardening-signing-checklist.md` — still says test-keys,
`userdebug`, and "hardened_malloc later". The booted zip is `user` +
`release-keys`, and malloc is the bacon in flight.
- `docs/009-user-zip-audit-20261007.md` — an older unflashed zip. Jelly is
**kept** now; that audit's "Jelly absent" line is obsolete.
- `docs/009-version-pins.md` — still right for microG, Magisk 30.7, Phonesky,
OpenEUICC. Add the malloc pin when that bacon is accepted.
+16 -3
View File
@@ -211,14 +211,27 @@ attribution. One stage per bacon when risk is unclear.
- [x] **US-S5-1** LAB OK (`162840Z`): `CONFIG_SECURITY_YAMA=y` +
`CONFIG_SLAB_FREELIST_RANDOM=y` — see
`docs/009-lab-acceptance-20261002-uss5.md`
- [ ] Optional later: `CVE-2022-49980` lock-scope port; Stage1 slab FAIL
retries; `hardened_malloc`
- [ ] **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
- [ ] Switch default lunch to `lineage_sunfish-user` for ship
- [ ] 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**
@@ -0,0 +1,180 @@
# Swissquote differential investigation — 2026-10-09
Status: **failure classified, underlying root-detection signal not yet isolated**.
Live observations approximately 15:18–15:26 Europe/Berlin. This supersedes
the Swissquote runtime inventory in `009-handoff-20261008.md`, not its
build/recovery instructions. No firmware was flashed and GOLDEN was read-only.
## Confirmed new finding
Swissquote 9.11.0 stores this nonfatal exception on LAB, Island user 10:
```text
com.swissquote.android.FreeRaspException
freeRASP: onPrivilegedAccess
com.swissquote.android.ThreatDetectedListenerKt$getThreatDetectedListener$1.onPrivilegedAccess
ThreatDetectedListener.kt:24
app.talsec.rasp.security.api.ThreatListener.onReceive
```
This was read from the app's existing local Crashlytics event JSON. Only
the exception classification and stack symbols were retained; no raw
Crashlytics report, account data, keys, or session identifiers enter Git.
`scripts/009-read-swissquote-rasp.py --device LAB` reproduces that limited,
read-only inspection. Retained events may be uploaded/removed by the app;
an empty result does not prove compatibility or that no callback occurred.
Static inspection of the installed APK established that **-69 is an
application-defined `RASP_FAILING_EXIT_CODE`**, shared by four handlers:
`onPrivilegedAccess`, `onDebug`, `onAppIntegrity`, and `onHooks`. Each records
a distinct FreeRaspException, requests the same warning, then calls
`System.exit(-69)`. Consequently the exit code alone was not enough to
attribute the failure to root. The retained exception now identifies the
privileged-access branch for the observed LAB failure. It does not identify
which underlying native check fired or exclude additional failures later.
The APK contains Talsec `v19_2_0_fullRelease` markers and loads `libts.so`.
`libsigner.so` belongs to the separately present Adjust signer integration;
loading it is not evidence that it caused this exit.
The previously suppressed toast was recovered by temporarily granting
POST_NOTIFICATIONS to LAB user 10, then revoking it in a finally block.
Its text lists generic device-safety requirements (root, debugging,
emulation, modification, malware, official store); it does **not** disclose
the failing check. A later observation showed the permission granted with
USER_SET, apparently from concurrent user activity; it was not overwritten.
## Current comparison, not the earlier handoff inventory
- Both devices have Swissquote **9.11.0 / 91100003**, target SDK 37,
arm64, with Play Store as installer and initiating package.
- All three installed APKs match byte for byte:
- base: `734293c7b6a7ecf48117e23ffc72ad0a1e3279336025551faf23c1c3c239c8f2`
- arm64 split: `6ce67d98a2a4dc4b1ac38a9b3c864e11167b17ba9ed62e8a9d69cf48cb3e090d`
- density split: `4e0ab62d8eb7c2faa959014552204c8378977459109b3ba2a0b400a87bb9c3a2`
- Both now have Swissquote installed for user 0 **and** user 10. The earlier
statement that GOLDEN has it only for user 10 is obsolete.
- Island matches: `6.4.2-d46911c9`, versionCode 51801.
- GOLDEN: Android 15 / SDK 35, Magisk 29.0. LAB: Android 16 / SDK 36,
SDK_FULL 36.1, Magisk 30.7, deblob4 slot b.
- Both are user/release-keys with `ro.debuggable=0`, SELinux Enforcing,
Zygisk enabled and DenyList **enforced**. Both list the main Swissquote
process and its `_zygote` entry. LAB logs the DenyList hit for user 10.
- Both expose `verifiedbootstate=orange`, `vbmeta.device_state=unlocked`,
`flash.locked=0`, and `veritymode=enforcing` at this measurement.
Both protect `/proc/cmdline` with mode 0440.
- GOLDEN has only hosts and flag-secure-disabler modules installed.
LAB has hosts plus PIF and TEESimulator directories; both latter modules
have **disable markers**. Directory presence is not active-module proof.
- Owner confirmed GOLDEN Swissquote still opens in Island in this state.
Its existing user-10 process was alive during read-only inspection.
- Shell developer/ADB settings are enabled on both. No enabled
accessibility service was returned for user 10 on either device.
This is strong evidence against treating spoofed green/locked properties,
PIF, or TEESimulator as prerequisites for the observed GOLDEN success.
It does not prove that every path through either banking app is independent
of attestation.
## Process-level observations and controlled experiment
Read `/proc/<Swissquote PID>/mountinfo` and selected maps using root, without
attaching a debugger or modifying the banking process. Both mount tables
lacked obvious Magisk/module mounts; neither selected maps output contained
Magisk/Zygisk/LSPosed libraries. The injected `/system_ext/bin/magisk` was
absent beneath each inspected process's `/proc/<PID>/root` view even though
it exists in the root shell's view. This supports actual unmounting, beyond
merely checking DenyList configuration. It **does not** prove absence of
all detectable root signals, sockets, policy changes, or runtime effects.
Important real ROM difference: GOLDEN has no Yama ptrace_scope node; LAB
has Yama enabled and scope 2. The local `apply_009_yama_ptrace.py` already
documents that the AXP init rule was inert on GOLDEN's kernel.
At 15:22, changed **only** LAB's transient ptrace_scope from 2 to 0, cold
started Swissquote in user 10, and observed `System.exit(-69)` again.
Restored scope **2**, with readback. No persistent property was changed.
Thus relaxing ptrace_scope alone does not fix the observed failure; this
is not a blanket clearance of every kernel-hardening difference.
## Why GOLDEN passes: what is known and what remains open
GOLDEN's actual process environment satisfies Swissquote's startup checks
despite unlocked/orange state, Magisk, and USB debugging. LAB triggers the
privileged-access callback despite working basic DenyList unmounting.
The APK and Island version are controlled; the operating system, kernel,
SELinux policy and Magisk implementation are **not** controlled yet.
Leading branches for investigation, not established causes:
1. A Magisk 29 → 30.7 difference leaves a root-detection surface after
unmounting. Upstream changed file injection, mounting and sepolicy-related
handling; simply seeing the same DenyList checkboxes cannot exclude this.
2. A ROM/kernel/SELinux difference is interpreted as privileged access by
Talsec on Android 16. Yama scope alone has been tested unsuccessfully.
3. App-visible package/file/policy differences or retained per-install state.
Package listings from shell are not automatically app-visible listings.
Do not claim “Magisk 30.7 is broken” from upstream issue 9866: that report
had **Enforce DenyList OFF**, unlike this LAB, and was closed not planned.
Do not infer a specific Talsec false positive from unrelated hardware-
attestation reports. Do not mask callbacks, patch Swissquote, or transfer
GOLDEN banking data as a diagnostic shortcut.
## Prepared next discriminator — same build, without Magisk
The current artifact directory is:
`/srv/rom/artifacts/009-axp-los23-port/20261009T120025Z-axp-stage1-combo4-cve-hi-releasekeys/`
Unpacked both boot images with the host's magiskboot. Verified:
- `boot.img`: SHA256 `e5822d8e8cbb39fe74ca638cdfcd52cd66fc741d130bdf962f433ae4f16299d2`;
ramdisk `cpio test` = **1**, Magisk patched.
- `boot-make-out.img`: SHA256
`6caceaca1e911d327e7ad86217cc6bedb8cd32fe690cadd11c9cedeed0440d1b`;
ramdisk `cpio test` = **0**, stock/unpatched.
- The extracted kernel SHA256 is identical in both:
`e093a3c7edfb2560f109c0b3df68d2722a1e05f06357316d1278b8c0348e8f89`.
This prepares a **temporary LAB-only boot experiment**, subject to owner
agreement and confirming the phone is still on this same build. No image
has been booted or flashed in this investigation. Preserve data, vendor SPL,
the active slot and the installed APK; do not reinstall/reset the app.
Prefer booting the image temporarily if the bootloader supports that path.
The stock image has not yet been boot-tested; a verified hash is not boot
acceptance. Confirm actual absence of running Magisk after boot before
interpreting the Swissquote result; the manager APK and historical `/data`
files can remain, so this is not a clean factory/rootless baseline.
- If it starts, focus on active Magisk integration; consider a compatible,
isolated comparison against GOLDEN's version, not a blind ship downgrade.
- If it still exits, reclassify the failure before investigating ROM-visible
surfaces. Rootless boot failure alone would not prove a Talsec bug.
- Return to the installed boot and repeat the original failure for an A/B/A
comparison. A process surviving briefly is not a usable-session PASS.
LAB became unavailable in ADB during investigation. On the next turn the
owner confirmed it had been reconnected and authorized the remaining tests.
USB enumeration then confirmed LAB was physically present at 3-5.3 as
18d1:4ee1 (MTP only), while GOLDEN was 18d1:4ee2 (MTP + debug). The earlier
user-facing assertion that LAB was disconnected was too broad: USB was
present, but the ADB interface was unavailable. Requested that the owner
enable USB debugging on LAB; do not mistake GOLDEN's ADB presence for LAB.
## Sources and audit trail
- Project: `009-handoff-20261008.md`, constitution, feature 009 spec,
Magisk runbook, current tracked diff and new patch scripts. Relevant recent
changes include user/release signing, AXP identity and vendor-SPL guard,
user-build signature spoofing, cmdline permissions, AVB flags 0, malloc
2-GiB regions, kernel ports/forward-only patching, Yama, USERFAULTFD off,
Settings/privacy ports and two deblob waves. No existing work was reverted.
- [Talsec callback API](https://docs.talsec.app/freerasp/freerasp/integration/flutter/api)
- [Magisk 30.7 DenyList source](https://github.com/topjohnwu/Magisk/blob/v30.7/native/src/core/deny/utils.cpp)
- [Magisk changelog](https://github.com/topjohnwu/Magisk/blob/v30.7/docs/changes.md)
- [Magisk issue 9866, different enforcement configuration](https://github.com/topjohnwu/Magisk/issues/9866)
- [Adjust upstream libsigner reference](https://github.com/adjust/android_sdk/issues/655)
- Local scratch: `/tmp/axp-swissquote-20261009/`. Proprietary APKs and
decompiled files remain outside the repository and must not be committed.
Partial full-APK decompilation was replaced by targeted inspection.
+35
View File
@@ -0,0 +1,35 @@
# User zip audit — 20261007T022805Z (not flashed)
Build finished `exit=0` at 2026-10-07T02:29:08Z. Session `009-user-bacon`.
Log: `/srv/rom/artifacts/detach-009-user-bacon-20261006T203157Z.log`.
No LAB flash, no wipe.
Artifact: `/srv/rom/artifacts/009-axp-los23-port/20261007T022805Z-axp-stage1-combo4-cve-hi-releasekeys/`
Ship zip (same bytes as the lineage twin):
`AXP-Resurection-Pro-23.2-20261006-sunfish.zip`
SHA256 `7c70944614a1002e83bba7c38e1ef3ab8baeb56bfb3f60a474f59230575ea5a9`
## Checked on the host images
| Check | Result |
|-------|--------|
| Lunch | `ro.build.flavor=lineage_sunfish-user`, `ro.build.type=user`, `ro.debuggable=0` |
| Version | `23.2-20261006-UNOFFICIAL-AXP-Resurection-Pro-sunfish`, track `pro` |
| OTA cert | `O=AXP-Resurection, OU=sunfish-a16, CN=release` (not AOSP testkey) |
| `ro.build.tags` | `dev-keys` (Lineage unofficial). GOLDEN official PRO is `release-keys` |
| `ro.lineage.releasetype` | `UNOFFICIAL` |
| AVB | project key SHA256_RSA4096, flags `3`, boot/dtbo/vendor/system chain verified |
| Magisk | `magiskboot_cpio_test=1` on the shipped boot |
| KernelSU | no KernelSU strings in `boot.img`; ship script refused `CONFIG_KSU=y` |
| microG | `system_ext/app/GmsCore`, versionCode `252432034`, versionName `0.3.17.252432` |
| Jelly / Gallery2 | **absent** on product. FossifyGallery present |
| Kernel pack | Stage1 + combo4 + FORTIFY + CVE-clean 429 + hi-batch1–3 + Yama |
## Notes for the morning
- `dev-keys` is the unofficial Lineage tag. The zip itself is signed with the project release key.
- Jelly and Gallery2 were already removed in the tree, and the ship script still fails the build if those APKs are present. This zip does not contain them.
- Flash stays a separate decision. Sideload the AXP zip; a boot-only flash is not enough.
+2 -1
View File
@@ -8,7 +8,7 @@ Never ship from stale laptop caches.
| component | version_name | version_code | upstream_ref | policy | verified_on | notes |
|-----------|--------------|--------------|--------------|--------|-------------|-------|
| VanillaBaseline | lineage-23.2 sunfish | | `/srv/rom/artifacts/003-vanilla/20260914-lineage-23.2-sunfish` | los_default | 2026-09-14 | Known-good vanilla artifact on host |
| microG GmsCore | v0.3.16.252432 | | microG official (matches GitHub latest 2026-09-17) | newest_stable | 2026-09-16 staged | `/srv/rom/prebuilts/microg/` |
| microG GmsCore | v0.3.17.252432 | 252432034 | https://github.com/microg/GmsCore/releases/tag/v0.3.17.252432 | newest_stable | 2026-10-06 staged | default flavor, not `-hw`. PRESIGNED on `system_ext`. GOLDEN live overlay was 0.3.16.1 |
| GSF Proxy | v0.1.0 | | microG official | newest_stable | 2026-09-16 staged | |
| Phonesky | 52.9.22-24 | | project patched host artifact | project_phonesky | 2026-09-14 | `/srv/rom/prebuilts/phonesky/`; never Git |
| Magisk | v30.7 | | https://github.com/topjohnwu/Magisk/releases/tag/v30.7 | newest_stable | 2026-09-17 | Staged APK; GOLDEN oracle **29.0** layout only |
@@ -17,6 +17,7 @@ Never ship from stale laptop caches.
| F-Droid | present on GOLDEN | | `org.fdroid.fdroid` | newest_stable | 2026-09-17 seen | |
| Fossify Gallery | present on GOLDEN | | `org.fossify.gallery` | newest_stable | 2026-09-17 seen | |
| Seedvault | present on GOLDEN | | `com.stevesoltys.seedvault` | los_default / newest | 2026-09-17 seen | Often LOS-provides |
| OpenEUICC | upstream 2026-09-28 | | `im.angry.openeuicc` `89d677e07d0e` | newest_upstream | 2026-10-07 wired | Privileged system_ext LPA, platform cert. Latest PeterCxy master, not the AXP fork (1.6.2 / GOLDEN 1.6.1). Datastore from LOS androidx. Deps `prebuilts/openeuicc-deps` @ `5402167` for zxing |
| Island | deferred | | `com.oasisfeng.island.fdroid` | | | US3 deferred |
## Host artifact hashes (copy from host VERSIONS when refreshed)
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# Isolated AVB flags 0 candidate. Same ship pack as the release-key bacon.
# WITH_AVB=true makes device/google/sunfish/BoardConfigLineage.mk skip
# `--flags 3`. The project avb.pem and SHA256_RSA4096 stay.
# Do not start this while another ninja owns /srv/rom/src/lineage/out.
# Do not flash until the zip is checked and the owner asks. LAB only.
set -euo pipefail
export AXP_AVB_FLAGS=0
export WITH_AVB=true
exec "$(dirname "$0")/009-bacon-axp-stage1-combo4-cve-hi-releasekeys.sh"
@@ -1,9 +1,21 @@
#!/bin/bash
# 009 bacon — ship pack with project release keys (US-S2).
# Stage1+combo4+FORTIFY + CVE clean + hi-batch1+2+3 + PRODUCT_DEFAULT_DEV_CERTIFICATE.
# Requires $HOME/.android-certs/releasekey.{pk8,x509.pem}. userdebug.
# CVE-2022-49980 ships as the lock-scope port; the AXP one-liner (uevent under
# udc_lock) stays denylisted because it hung LAB at the Google logo.
# Requires $HOME/.android-certs/releasekey.{pk8,x509.pem}.
# Lunch is lineage_sunfish-user (same flavor GOLDEN reports). PRO is branding,
# not a different lunch target.
set -e
cd /srv/rom/src/lineage
# Default ship keeps vbmeta flags 3. The isolated flags-0 candidate sets
# AXP_AVB_FLAGS=0, which exports WITH_AVB=true before lunch. That is the only
# effect in device/google/sunfish/BoardConfigLineage.mk: skip `--flags 3`.
AXP_AVB_FLAGS="${AXP_AVB_FLAGS:-3}"
if [[ "$AXP_AVB_FLAGS" == "0" ]]; then
export WITH_AVB=true
fi
echo "AXP_AVB_FLAGS=$AXP_AVB_FLAGS WITH_AVB=${WITH_AVB:-unset}"
export HOME=/home/android-build
export CCACHE_DIR=/srv/rom/ccache
export USE_CCACHE=1
@@ -20,7 +32,15 @@ exec > >(tee -a "$LOG") 2>&1
echo "=== START 009-bacon-axp-cve-hi-releasekeys $(date -Is) ==="
CERT_DIR="${ANDROID_CERTS_DIR:-$HOME/.android-certs}"
if [[ -z "${ANDROID_CERTS_DIR:-}" ]]; then
if [[ -f /srv/rom/secrets/android-certs/releasekey.pk8 ]]; then
CERT_DIR=/srv/rom/secrets/android-certs
else
CERT_DIR="$HOME/.android-certs"
fi
else
CERT_DIR="$ANDROID_CERTS_DIR"
fi
if [[ ! -f "$CERT_DIR/releasekey.pk8" || ! -f "$CERT_DIR/releasekey.x509.pem" ]]; then
echo "FAIL: missing $CERT_DIR/releasekey.{pk8,x509.pem}" >&2
exit 1
@@ -50,6 +70,22 @@ chmod +x "$KH"/apply_stage1_combo4.sh "$KH"/apply_cve_strict.sh \
bash "$KH/apply_stage1_combo4.sh" "$LIVE"
# AXP hunks that no longer apply cleanly, ported onto this msm-4.14.
# misc/0021 stays omitted: LOS already has its KSYM_NAME_LEN=192 result.
# slab/0010 and slab/0018 are already covered by virt_to_cache() plus
# CONFIG_SLAB_FREELIST_HARDENED.
grep -q '^#define KSYM_NAME_LEN 192$' "$LIVE/include/linux/kallsyms.h" \
|| { echo "FAIL: KSYM_NAME_LEN is not 192 after Stage1" >&2; exit 1; }
for PORT in \
"$KH/ports/allocsize-0007-vmalloc-alloc-size.patch" \
"$KH/ports/ro-0007-0034-sysctl-read-only.patch"
do
test -f "$PORT"
patch -d "$LIVE" -p1 --forward --dry-run < "$PORT"
patch -d "$LIVE" -p1 --forward < "$PORT"
echo "applied $(basename "$PORT")"
done
# US-S5-1 — Yama + SLAB_FREELIST_RANDOM (Graphene/AXP defconfig gaps)
chmod +x "$KH/apply_uss5_1_defconfig.sh" 2>/dev/null || true
bash "$KH/apply_uss5_1_defconfig.sh" "$LIVE"
@@ -67,6 +103,14 @@ for L in "$LIST_HI1" "$LIST_HI2" "$LIST_HI3"; do
bash "$KH/apply_cve_strict.sh" "$LIVE"
done
PORT49980="$KH/ports/cve-2022-49980-lock-scope.patch"
test -f "$PORT49980"
patch -d "$LIVE" -p1 --forward --dry-run < "$PORT49980"
patch -d "$LIVE" -p1 --forward < "$PORT49980"
grep -q 'device_add() emits a uevent' "$LIVE/drivers/usb/gadget/udc/core.c" \
|| { echo "FAIL: CVE-2022-49980 lock-scope missing" >&2; exit 1; }
echo "applied $(basename "$PORT49980")"
DEF=$LIVE/arch/arm64/configs/sunfish_defconfig
if grep -qE "^CONFIG_KSU.*=y" "$DEF"; then
echo "FAIL: KernelSU in defconfig" >&2
@@ -77,6 +121,12 @@ for k in FORTIFY_SOURCE INIT_ON_ALLOC_DEFAULT_ON INIT_ON_FREE_DEFAULT_ON \
SECURITY_YAMA SLAB_FREELIST_RANDOM; do
grep -q "^CONFIG_${k}=y" "$DEF" || { echo "FAIL missing $k" >&2; exit 1; }
done
grep -q '^# CONFIG_USERFAULTFD is not set$' "$DEF" \
|| { echo "FAIL: USERFAULTFD still enabled (GOLDEN leaves it unset)" >&2; exit 1; }
if grep -q '^CONFIG_USERFAULTFD=y$' "$DEF"; then
echo "FAIL: CONFIG_USERFAULTFD=y" >&2
exit 1
fi
grep -q "ret = __underlying_strlen(q);" "$LIVE/include/linux/string.h"
grep -q "mmget_still_valid" "$LIVE/mm/madvise.c"
echo "OK: Stage1+combo4+FORTIFY + CVE-clean + hi-batch1+2+3 markers present"
@@ -85,13 +135,62 @@ rm -f out/target/product/sunfish/obj/KERNEL_OBJ/arch/arm64/boot/Image.lz4 \
out/target/product/sunfish/obj/KERNEL_OBJ/arch/arm64/boot/Image \
out/target/product/sunfish/kernel
python3 vendor/sunfish-a16/patches/008-call-rec-flag-secure/apply_wms_disable_secure_debuggable_gate.py
python3 vendor/sunfish-a16/patches/005-sigspoof/apply_005_sigspoof.py
python3 vendor/sunfish-a16/patches/008-call-rec-flag-secure/apply_008_call_rec_flag_secure.py
grep -q 'SUNFISH_A16_008_CALL_REC_FLAG_SECURE' \
frameworks/base/core/java/android/provider/Settings.java
grep -q 'extendrom_call_recording' \
packages/apps/Dialer/java/com/android/incallui/call/CallRecorder.java
grep -q 'extendrom_call_recording' \
packages/apps/Settings/res/xml/development_settings.xml
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_disable_user_apps.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_private_dns_providers.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_connectivity_check.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_unofficial_build_id.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_supl_privacy.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_axp_identity.py
grep -q '^VENDOR_SECURITY_PATCH = 2023-09-05$' device/google/sunfish/device-common.mk
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_yama_ptrace.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_cmdline_mode.py
grep -q 'CHECKCALL(chmod("/proc/cmdline", 0440));' \
system/core/init/first_stage_init.cpp
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_a15_small.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_a15_reboot_timeouts.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_a15_settings_timeouts.py
grep -q 'settings_reboot_after_timeout' \
frameworks/base/core/java/android/provider/Settings.java
grep -q 'isAllowedWhenLocked' \
frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
grep -q 'auto_reboot' packages/apps/Settings/res/xml/security_dashboard_settings.xml
grep -q 'bluetooth_timeout' packages/apps/Settings/res/xml/connected_devices.xml
grep -q 'wifi_timeout' packages/apps/Settings/res/xml/wifi_configure_settings.xml
grep -q 'MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 5;' \
frameworks/base/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
grep -q 'final String buildSerial = Build.UNKNOWN;' \
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
grep -q 'AXP/Calyx 0042' \
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
grep -q 'Serial number constraint not permitted' \
bootable/recovery/install/install.cpp
# openeuicc-deps 5402167 was generated against SDK 37. LOS 23.2 ships
# prebuilts/sdk/37 without public/android.jar, so compile these prebuilt
# libraries against 36 (Android 16).
sed -i 's/^ sdk_version: "37",$/ sdk_version: "36",/' prebuilts/openeuicc-deps/Android.bp
if grep -q 'sdk_version: "37"' prebuilts/openeuicc-deps/Android.bp; then
echo "FAIL: openeuicc-deps still asks for SDK 37" >&2
exit 1
fi
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_openssh_packages.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_jelly_gallery2.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_euicc_google.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_carrier_google.py
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_lineage_tools.py
# Wave 4 (diag/bpftool/imsrcsd): only when AXP_DEBLOB_WAVE4=1 (see 009-bacon-wave4-deblob.sh).
AXP_DEBLOB_WAVE4="${AXP_DEBLOB_WAVE4:-0}"
if [[ "$AXP_DEBLOB_WAVE4" == 1 ]]; then
python3 vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_lab_only_commands.py
fi
# Jelly stays (owner 2026-10-07). Gallery is FossifyGallery. Do not re-add
# Gallery2. The product gate requires Jelly.apk and FossifyGallery, and
# fails if Gallery2.apk is present.
# Host prebuilts → vendor/sunfish-a16/prebuilt (rsync --delete must not wipe these).
mkdir -p vendor/sunfish-a16/prebuilt
@@ -110,7 +209,7 @@ link_prebuilt FossifyGallery.apk /srv/rom/prebuilts/axp-apps/FossifyGallery.apk
AAPT=/srv/rom/src/lineage/prebuilts/sdk/tools/linux/bin/aapt
"$AAPT" dump permissions vendor/sunfish-a16/prebuilt/Phonesky.apk 2>/dev/null | grep -q USE_EXACT_ALARM
# Early hook so LINEAGE_VERSION includes AXP-Resurection-Pro (before version.mk).
# Early hook before version.mk (identity itself is apply_009_axp_identity.py).
mkdir -p vendor/extra
ln -sfn ../sunfish-a16/extra-product.mk vendor/extra/product.mk
test -f vendor/extra/product.mk
@@ -132,15 +231,27 @@ set +u
# shellcheck disable=SC1091
source build/envsetup.sh
set -e
# re-export after envsetup (some trees clear it)
export PRODUCT_DEFAULT_DEV_CERTIFICATE="$CERT_DIR/releasekey"
breakfast sunfish userdebug
# re-export after envsetup (some trees clear it). signing.mk sets the same path.
export PRODUCT_DEFAULT_DEV_CERTIFICATE=vendor/lineage-priv/keys/releasekey
breakfast sunfish user
test "${TARGET_RELEASE:-}" = "bp4a"
BT_SEPOL=$(get_build_var MAINLINE_BLUETOOTH_SEPOLICY_DEV_CERTIFICATES)
echo "MAINLINE_BLUETOOTH_SEPOLICY_DEV_CERTIFICATES=$BT_SEPOL"
case "$BT_SEPOL" in
*vendor/sunfish-a16/certs*) ;;
*) echo "FAIL: BT sepolicy certs still AOSP default ($BT_SEPOL)" >&2; exit 1 ;;
*vendor/lineage-priv/keys*) ;;
*) echo "FAIL: BT sepolicy certs not vendor/lineage-priv/keys ($BT_SEPOL)" >&2; exit 1 ;;
esac
SYS_CERT=$(get_build_var DEFAULT_SYSTEM_DEV_CERTIFICATE)
B_TYPE=$(get_build_var LINEAGE_BUILDTYPE)
L_VER=$(get_build_var LINEAGE_VERSION)
echo "DEFAULT_SYSTEM_DEV_CERTIFICATE=$SYS_CERT LINEAGE_BUILDTYPE=$B_TYPE LINEAGE_VERSION=$L_VER"
case "$SYS_CERT" in
vendor/lineage-priv/keys/releasekey) ;;
*) echo "FAIL: system cert $SYS_CERT would tag dev-keys" >&2; exit 1 ;;
esac
[[ "$B_TYPE" == "PRO" ]] || { echo "FAIL: LINEAGE_BUILDTYPE=$B_TYPE (want PRO)" >&2; exit 1; }
case "$L_VER" in
*UNOFFICIAL*) echo "FAIL: version still UNOFFICIAL ($L_VER)" >&2; exit 1 ;;
esac
AVB_KEY=$(get_build_var BOARD_AVB_KEY_PATH)
AVB_ALG=$(get_build_var BOARD_AVB_ALGORITHM)
@@ -192,18 +303,18 @@ for f in vendor.img product.img system.img system_ext.img; do
done
# Keep make OUT boot for forensics only (often stock / non-Magisk).
cp -v "$PROD/boot.img" "$DEST/boot-make-out.img" 2>/dev/null || true
cp -v "$PROD"/lineage-*-UNOFFICIAL*.zip "$DEST/" 2>/dev/null || \
cp -v "$PROD"/lineage-*-PRO-sunfish.zip "$DEST/" 2>/dev/null || \
cp -v "$PROD"/lineage-*.zip "$DEST/" 2>/dev/null || true
# Publish AXP Resurection Pro ship name (hardlink/copy). Lineage bacon.mk still
# emits lineage-*.zip; do not rebuild solely to rename.
ZIP_LINEAGE=$(ls "$DEST"/lineage-*.zip 2>/dev/null | head -1 || true)
if [[ -n "${ZIP_LINEAGE:-}" ]]; then
# lineage-23.2-YYYYMMDD-UNOFFICIAL[-AXP-Resurection[-Pro]]-sunfish.zip
# lineage-23.2-YYYYMMDD-PRO-sunfish.zip
# → AXP-Resurection-Pro-23.2-YYYYMMDD-sunfish.zip
ZIP_BASE=$(basename "$ZIP_LINEAGE")
ZIP_AXP=$(echo "$ZIP_BASE" | sed -E \
's/^lineage-([0-9]+\.[0-9]+)-([0-9]{8})(-[0-9]{6})?-UNOFFICIAL(-AXP-Resurection(-Pro)?)?-sunfish\.zip$/AXP-Resurection-Pro-\1-\2\3-sunfish.zip/')
's/^lineage-([0-9]+\.[0-9]+)-([0-9]{8})(_[0-9]{6})?-PRO-sunfish\.zip$/AXP-Resurection-Pro-\1-\2\3-sunfish.zip/')
if [[ "$ZIP_AXP" == "$ZIP_BASE" ]]; then
ZIP_AXP="AXP-Resurection-Pro-${ZIP_BASE#lineage-}"
fi
@@ -243,7 +354,20 @@ rm -rf "$MB_TMP"
[[ "$MB_EC" -ne 0 ]] || { echo "FAIL: ship boot is stock (magiskboot cpio test=$MB_EC); expected Magisk" >&2; exit 1; }
echo "magiskboot_cpio_test=$MB_EC (non-zero => Magisk present)"
ZIP=$(ls "$DEST"/lineage-*-UNOFFICIAL*.zip 2>/dev/null | head -1 || true)
for bp in "$PROD/system/build.prop" "$PROD/product/etc/build.prop"; do
[[ -f "$bp" ]] || continue
if grep -q '^ro\.[a-z.]*build\.tags=' "$bp" && ! grep -q '^ro\.[a-z.]*build\.tags=release-keys$' "$bp"; then
echo "FAIL: $bp build.tags is not release-keys" >&2
exit 1
fi
done
grep -q '^ro.lineage.releasetype=PRO$' "$PROD/product/etc/build.prop" || {
echo "FAIL: ro.lineage.releasetype is not PRO" >&2
exit 1
}
echo "identity OK (release-keys, ro.lineage.releasetype=PRO)"
ZIP=$(ls "$DEST"/lineage-*.zip 2>/dev/null | head -1 || true)
if [[ -n "$ZIP" ]]; then
unzip -p "$ZIP" META-INF/com/android/otacert 2>/dev/null | openssl x509 -noout -subject 2>/dev/null \
| tee "$DEST/otacert-subject.txt" || true
@@ -272,7 +396,7 @@ FLAGS=$(awk '/^Flags:/{print $2; exit}' "$DEST/vbmeta-info.txt")
echo "avb_pubkey_sha1 artifact=$GOT project=$EXP flags=$FLAGS"
[[ -n "$GOT" ]] || { echo "FAIL: could not parse vbmeta public key sha1" >&2; exit 1; }
[[ "$GOT" == "$EXP" ]] || { echo "FAIL: vbmeta pubkey != project avb_pkmd.bin" >&2; exit 1; }
[[ "$FLAGS" == "3" ]] || { echo "FAIL: expected vbmeta Flags 3 (Magisk track), got '$FLAGS'" >&2; exit 1; }
[[ "$FLAGS" == "$AXP_AVB_FLAGS" ]] || { echo "FAIL: expected vbmeta Flags $AXP_AVB_FLAGS, got '$FLAGS'" >&2; exit 1; }
# Cryptographic check against TF sibling images in $DEST (hardlinked/copied).
# Magisk boot footer is Algorithm NONE — chain trust is top-level vbmeta + project key.
@@ -329,17 +453,78 @@ if ! debugfs -R "stat /etc/init/init.openssh.rc" "$PROD_RAW" 2>&1 | grep -q 'Ino
fi
echo "US-S4-1 product surface OK (no ssh/rsync bins; init.openssh.rc present)"
# US-S4-2 — AXP PRO: no Jelly / Gallery2 *apps* on product (Fossify stays).
# Note: LOCAL_OVERRIDES removes Gallery2.apk but stale JNI symlink dirs under
# product/app/Gallery2/lib can remain from older out/ — gate on the APK.
for p in /app/Jelly/Jelly.apk /app/Gallery2/Gallery2.apk; do
if debugfs -R "stat $p" "$PROD_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW"
echo "FAIL: US-S4-2 expected absent $p on product.img" >&2
# Lineage tools absent on GOLDEN. curl and vim stay.
SYS_RAW=$(mktemp /tmp/axp-system-XXXX.img)
SYSEXT_RAW=$(mktemp /tmp/axp-systemext-XXXX.img)
"$SIMG" "$DEST/system.img" "$SYS_RAW"
"$SIMG" "$DEST/system_ext.img" "$SYSEXT_RAW"
if debugfs -R "stat /addon.d/50-lineage.sh" "$SYS_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$SYS_RAW" "$SYSEXT_RAW"
echo "FAIL: expected system/addon.d/50-lineage.sh absent" >&2
exit 1
fi
if ! debugfs -R "stat /bin/curl" "$SYS_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$SYS_RAW" "$SYSEXT_RAW"
echo "FAIL: expected system/bin/curl present" >&2
exit 1
fi
for p in /bin/bash /bin/htop /bin/nano /bin/unrar; do
if debugfs -R "stat $p" "$SYSEXT_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$SYS_RAW" "$SYSEXT_RAW"
echo "FAIL: expected system_ext$p absent" >&2
exit 1
fi
done
# Directory-only Gallery2 leftover (jni symlinks) is not a shipped app — warn.
if [[ "$AXP_DEBLOB_WAVE4" == 1 ]]; then
for p in /bin/bpftool /bin/alloctop /bin/pbtombstone /bin/apexd-lsof.sh; do
if debugfs -R "stat $p" "$SYS_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$SYS_RAW" "$SYSEXT_RAW"
echo "FAIL: expected system$p absent" >&2
exit 1
fi
done
fi
if ! debugfs -R "stat /bin/vim" "$SYSEXT_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$SYS_RAW" "$SYSEXT_RAW"
echo "FAIL: expected system_ext/bin/vim present" >&2
exit 1
fi
rm -f "$SYS_RAW" "$SYSEXT_RAW"
echo "lineage tools surface OK (no bash/htop/nano/unrar/addon.d; curl+vim kept)"
if [[ "$AXP_DEBLOB_WAVE4" == 1 ]]; then
VENDOR_RAW=$(mktemp /tmp/axp-vendor-XXXX.img)
"$SIMG" "$DEST/vendor.img" "$VENDOR_RAW"
for p in /bin/cnss_diag /bin/diag_klog /bin/diag_mdlog /bin/diag_socket_log /bin/diag_uart_log /bin/ssr_diag /bin/test_diag /bin/imsrcsd /bin/disable_configstore /etc/init/imsrcsd.rc; do
if debugfs -R "stat $p" "$VENDOR_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$VENDOR_RAW"
echo "FAIL: expected vendor$p absent" >&2
exit 1
fi
done
for p in /bin/loc_launcher /bin/lowi-server /bin/xtra-daemon; do
if ! debugfs -R "stat $p" "$VENDOR_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW" "$VENDOR_RAW"
echo "FAIL: expected vendor$p present" >&2
exit 1
fi
done
rm -f "$VENDOR_RAW"
echo "lab-only commands surface OK (diag/imsrcsd/debug tools gone; GPS daemons kept)"
fi
# US-S4-2 — Jelly stays. FossifyGallery stays; Gallery2 does not.
# Stale JNI symlink dirs under product/app/Gallery2/lib can remain from
# older out/ — gate Gallery2 on the APK.
if ! debugfs -R "stat /app/Jelly/Jelly.apk" "$PROD_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW"
echo "FAIL: US-S4-2 expected Jelly.apk on product.img" >&2
exit 1
fi
if debugfs -R "stat /app/Gallery2/Gallery2.apk" "$PROD_RAW" 2>&1 | grep -q 'Inode:'; then
rm -f "$PROD_RAW"
echo "FAIL: US-S4-2 expected Gallery2.apk absent on product.img" >&2
exit 1
fi
if debugfs -R "stat /app/Gallery2" "$PROD_RAW" 2>&1 | grep -q 'Inode:'; then
echo "WARN: US-S4-2 /app/Gallery2 dir still present (likely stale jni symlinks; no APK)"
fi
@@ -349,20 +534,27 @@ if ! debugfs -R "stat /app/FossifyGallery" "$PROD_RAW" 2>&1 | grep -q 'Inode:';
exit 1
fi
rm -f "$PROD_RAW"
echo "US-S4-2 product surface OK (no Jelly/Gallery2 APK; FossifyGallery present)"
echo "US-S4-2 product surface OK (Jelly + FossifyGallery; no Gallery2 APK)"
{
echo "artifact=$DEST"
echo "list_clean=$LIST_CLEAN (429)"
echo "list_hi=batch1+2+3"
echo "hardening=Stage1+combo4+FORTIFY + CVE-clean + hi-batch1+2+3 + US-S4-1-no-openssh + US-S4-2-no-jelly-gallery2 + US-S5-1-yama-slabrand"
echo "kernel_stage1=30 (misc/0021 omitted; base KSYM_NAME_LEN=192 retained)"
echo "hardening=Stage1+combo4+FORTIFY + CVE-clean + hi-batch1+2+3 + US-S4-1-no-openssh + US-S4-2-jelly-fossify + US-S5-1-yama-slabrand + CVE-2022-49980-lock-scope"
echo "vendor_security_patch=2023-09-05"
echo "branding=AXP-Resurection-Pro"
echo "signing=$PRODUCT_DEFAULT_DEV_CERTIFICATE"
echo "avb_infra=$AVB_KEY alg=$AVB_ALG flags=3 (HASHTREE_DISABLED|VERIFICATION_DISABLED)"
echo "avb_infra=$AVB_KEY alg=$AVB_ALG flags=$AXP_AVB_FLAGS"
echo "avb_runtime_enforce=no (unlocked Magisk track; not AVB-hardened boot)"
echo "avb_pubkey_sha1=$GOT"
echo "magiskboot_cpio_test=$MB_EC"
echo "ship_images=target_files/IMAGES (not OUT make images)"
if [[ "$AXP_DEBLOB_WAVE4" == 1 ]]; then
echo "deblob=wave1-euicc-google+wave2-carrier-google-and-aosp-rcs+wave3-lineage-tools+wave4-lab-only-commands"
else
echo "deblob=wave1-euicc-google+wave2-carrier-google-and-aosp-rcs+wave3-lineage-tools"
fi
echo "ship_zip_brand=AXP-Resurection-Pro-*-sunfish.zip (sibling of lineage-*.zip; not slim)"
echo "flash=sideload AXP-Resurection-Pro zip (or lineage twin); boot-only insufficient"
echo "recover=20261002T085915Z-axp-stage1-combo4-cve-hi-releasekeys"
@@ -375,6 +567,20 @@ echo "US-S4-2 product surface OK (no Jelly/Gallery2 APK; FossifyGallery present)
lineage-*.zip AXP-Resurection-Pro-*.zip 2>/dev/null || true) \
| tee "$DEST/SHA256SUMS"
# AXP publishes a hash file next to the zip and a detached signature of that
# file. Sign only when this user already has a secret key.
shopt -s nullglob
for pro in "$DEST"/AXP-Resurection-Pro-*.zip; do
(cd "$DEST" && sha512sum "$(basename "$pro")") | tee "$pro.sha512sum"
if gpg --batch --list-secret-keys 2>/dev/null | grep -q '^sec'; then
gpg --batch --yes --detach-sign --armor -o "$pro.sha512sum.asc" "$pro.sha512sum" \
|| echo "WARN: gpg detach-sign failed for $pro.sha512sum"
else
echo "SKIP: no GPG secret key; left $pro.sha512sum unsigned"
fi
done
shopt -u nullglob
echo "FLASH_READY $DEST"
echo "=== DONE $DEST $(date -Is) ==="
exit 0
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# 012 bacon: current ship pack plus Graphene hardened_malloc.
# Compiles libc first. The full bacon runs only if that link succeeds.
# Does not change AVB flags or run the userspace deblob.
set -e
cd /srv/rom/src/lineage
export HOME=/home/android-build
export CCACHE_DIR=/srv/rom/ccache
export USE_CCACHE=1
export PATH=/home/android-build/bin:/usr/local/bin:/usr/bin:/bin:$PATH
unset OUT_DIR || true
python3 vendor/sunfish-a16/patches/012-hardened-malloc/apply_012_hardened_malloc.py
grep -q 'SUNFISH_A16_012_HARDENED_MALLOC' bionic/libc/Android.bp
grep -q 'libhardened_malloc' bionic/libc/Android.bp
grep -q 'CONFIG_CLASS_REGION_SIZE=2147483648' external/hardened_malloc/Android.bp
grep -A1 '__attribute__((noinline))$' bionic/libc/bionic/libc_init_dynamic.cpp | grep -q '__libc_preinit_impl'
test -f external/hardened_malloc/h_malloc.c
set +u
# shellcheck disable=SC1091
source build/envsetup.sh
set -e
export PRODUCT_DEFAULT_DEV_CERTIFICATE=vendor/lineage-priv/keys/releasekey
breakfast sunfish user
echo "=== m libc $(date -Is) ==="
m libc -j8
echo "=== libc ok, starting bacon $(date -Is) ==="
exec /srv/rom/artifacts/bin/009-bacon-axp-stage1-combo4-cve-hi-releasekeys.sh
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# Wave 4 deblob candidate: closure pack plus diag/bpftool/imsrcsd drops.
# Same AVB flags-0 track as the booted closure baseline. Do not flash until
# checked. LAB only. Do not run while another ninja owns out/.
set -euo pipefail
export AXP_AVB_FLAGS=0
export WITH_AVB=true
export AXP_DEBLOB_WAVE4=1
exec "$(dirname "$0")/009-bacon-axp-stage1-combo4-cve-hi-releasekeys.sh"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Read only Swissquote's RASP exception classification; never dump reports.
Requires an already authorized adb shell su. Does not launch, stop, modify,
instrument, or grant root to Swissquote. Raw report data stays in memory.
APK/report/session identifiers and other application data are not emitted.
"""
import argparse
import json
import subprocess
def classifications(value):
if isinstance(value, dict):
exception = value.get("exception")
if isinstance(exception, dict):
reason = exception.get("reason", "")
if (exception.get("type") == "com.swissquote.android.FreeRaspException"
and isinstance(reason, str) and reason.startswith("freeRASP: on")):
yield reason
for child in value.values():
yield from classifications(child)
elif isinstance(value, list):
for child in value:
yield from classifications(child)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--device", required=True, choices=["LAB", "GOLDEN"])
args = parser.parse_args()
serial = {"LAB": "11231JEC207477", "GOLDEN": "11071JEC203839"}[args.device]
base = "/data/user/10/com.swissquote.android/files/.crashlytics.v3/com.swissquote.android"
def shell(command):
return subprocess.check_output(
["adb", "-s", serial, "shell", "su", "-c", "'" + command + "'"],
text=True, timeout=15,
)
results = []
for path in shell("find " + base + " -maxdepth 4 -type f").splitlines():
# Never interpolate arbitrary output into a shell command.
if not path.startswith(base + "/") or any(c not in
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/._:-" for c in path):
continue
if not any(part in path for part in ("/event", "/reports/", "/priority-reports/")):
continue
size = shell("stat -c %s " + path).strip()
if not size.isdigit() or int(size) > 4 * 1024 * 1024:
continue
try:
value = json.loads(shell("cat " + path))
except json.JSONDecodeError:
continue
results.extend(classifications(value))
print(json.dumps({"device": args.device, "profile": 10,
"rasp_exceptions": results,
"note": "Retained events only; absence is not proof of PASS."}, indent=2))
if __name__ == "__main__":
main()
+10 -8
View File
@@ -410,14 +410,16 @@ work when published.
equivalent — not an unrelated Graphene A13 / stock A13 prebuilt), with
**attributable kernel hardening** ported from AXP / Graphene / Divest where
still valid on non-GKI 4.14. Skipped items MUST be documented with rationale.
- **FR-018**: *(Deferred research — look later)* Compare how AXP/GOLDEN
hardens or constrains **localization / location** (network location,
backends, permissions, privacy toggles) versus stock LineageOS 23 behaviour.
Owner historically finds AXP aggressive here; do **not** freeze a
clone-or-LOS policy in this feature’s acceptance until that comparison
exists. Default until then: do not silently invent extra location lockdown
beyond what the rest of the AXP clone already implies; record the open
question in the capability matrix.
- **FR-018**: Location uses the **LineageOS 23.2** stack so a fix stays
accurate and fast. Do **not** port AXP's location lockdown and do **not**
delete the IZat / XTRA / xtwifi blobs. SUPL stays available. A settings
toggle may force it off and defaults off. A second settings toggle,
default off, stops LPP (LPPE rides that session), the framework XTRA
download, and `loc_launcher` (xtra-daemon, lowi, IZat) without removing
the binaries. The GNSS stack never sends IMSI or the phone number to a
SUPL server. GNSS NTP is `2.android.pool.ntp.org`. Degoogle elsewhere
where a free replacement keeps the function; microG and the
project-patched Play Store stay.
- **FR-019**: On a **final** shipping `009` image, owner MUST be able to
refresh bundled **system / privileged** packages by **sideload** (or the
documented Magisk-equivalent path) roughly every **2–3 months**, without
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""A16 fixups keep the AXP reboot and QS-tile behavior."""
import importlib.util
import pathlib
import tempfile
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
APPLIER = (
ROOT
/ "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_a15_reboot_timeouts.py"
)
def load():
spec = importlib.util.spec_from_file_location("apply_009_a15_reboot_timeouts", APPLIER)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class RebootFixupTest(unittest.TestCase):
def test_a16_anchors(self) -> None:
src = load()
with tempfile.TemporaryDirectory() as tmp:
tree = pathlib.Path(tmp)
xml = tree / "systemui.xml"
keyguard = tree / "KeyguardViewMediator.java"
tile = tree / "QSTileImpl.java"
xml.write_text(src.XML_OLD)
keyguard.write_text(src.KEYGUARD_OLD)
tile.write_text(src.IMPORT_OLD)
src.replace_once(xml, src.XML_OLD, src.XML_NEW)
src.replace_once(keyguard, src.KEYGUARD_OLD, src.KEYGUARD_NEW)
src.replace_once(tile, src.IMPORT_OLD, src.IMPORT_NEW)
src.replace_once(xml, src.XML_OLD, src.XML_NEW)
self.assertIn("android.permission.REBOOT", xml.read_text())
self.assertIn(
"sendUserPresentBroadcast();\n cancelDoRebootForOwnerAfterTimeoutIfEnabled();",
keyguard.read_text(),
)
self.assertIn("KeyguardStateController", tile.read_text())
self.assertNotIn("QsInCompose", tile.read_text())
if __name__ == "__main__":
unittest.main()
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""The A15 small-port applier matches AXP behavior and the A16 return type."""
import importlib.util
import os
import pathlib
import subprocess
import tempfile
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
APPLIER = ROOT / "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_a15_small.py"
def load_applier():
spec = importlib.util.spec_from_file_location("apply_009_a15_small", APPLIER)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def write(path: pathlib.Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text)
class A15SmallPortTest(unittest.TestCase):
def test_applier_on_a16_fixtures(self) -> None:
src = load_applier()
with tempfile.TemporaryDirectory() as tmp:
tree = pathlib.Path(tmp)
write(
tree / "frameworks/base/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java",
src.LOCKOUT_OLD + "\n",
)
write(
tree / "frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
src.SERIAL_OLD,
)
write(
tree / "frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java",
"import android.os.Build;\nimport java.time.format.DateTimeFormatter;\n"
+ src.EXIF_OLD,
)
write(
tree / "bootable/recovery/install/install.cpp",
src.RECOVERY_OLD,
)
env = os.environ.copy()
env["SUNFISH_A16_TREE"] = str(tree)
subprocess.check_call(["python3", str(APPLIER)], env=env)
subprocess.check_call(["python3", str(APPLIER)], env=env)
lockout = (tree / "frameworks/base/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java").read_text()
ams = (tree / "frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java").read_text()
exif = (tree / "frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java").read_text()
recovery = (tree / "bootable/recovery/install/install.cpp").read_text()
self.assertIn("MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 5", lockout)
self.assertNotIn("= 20", lockout)
self.assertIn("final String buildSerial = Build.UNKNOWN;", ams)
self.assertNotIn("sTheRealBuildSerial :", ams)
self.assertNotIn("TAG_SOFTWARE", exif)
self.assertNotIn("TAG_DATETIME_ORIGINAL", exif)
self.assertNotIn("import android.os.Build;", exif)
self.assertNotIn("DateTimeFormatter", exif)
self.assertIn("TAG_IMAGE_WIDTH", exif)
self.assertIn("Serial number constraint not permitted", recovery)
self.assertIn("return false;", recovery)
self.assertNotIn("return INSTALL_ERROR", recovery)
self.assertNotIn("serial_number_match", recovery)
if __name__ == "__main__":
unittest.main()
+406
View File
@@ -0,0 +1,406 @@
#!/usr/bin/env python3
"""Regression guards for the next 009 release-key bacon."""
from pathlib import Path
import os
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
KH = ROOT / "vendor/sunfish-a16/patches/009-kernel-hardening"
SHIP = ROOT / "scripts/009-bacon-axp-stage1-combo4-cve-hi-releasekeys.sh"
class NextBaconGuardsTest(unittest.TestCase):
def test_stage1_refuses_reverse_application(self) -> None:
apply_script = (KH / "apply_stage1.sh").read_text()
self.assertIn('args=(-p1 --batch --forward -d "$KERNEL_DIR")', apply_script)
def test_stage1_does_not_revert_existing_clang_cfi_kallsyms_fix(self) -> None:
stage1 = (KH / "stage1.list").read_text().splitlines()
self.assertNotIn(
"0008-Graphene-Kernel_Hardening-misc/4.14/0021.patch",
stage1,
)
def test_default_avb_flags_stay_3_and_zero_is_isolated(self) -> None:
ship = SHIP.read_text()
wrapper = (ROOT / "scripts/009-bacon-avb-flags0.sh").read_text()
self.assertIn('AXP_AVB_FLAGS="${AXP_AVB_FLAGS:-3}"', ship)
self.assertIn('[[ "$FLAGS" == "$AXP_AVB_FLAGS" ]]', ship)
self.assertNotIn('expected vbmeta Flags 3', ship)
self.assertIn("export AXP_AVB_FLAGS=0", wrapper)
self.assertIn("export WITH_AVB=true", wrapper)
def test_ship_script_applies_axp_timeouts(self) -> None:
ship = SHIP.read_text()
self.assertIn("apply_009_a15_reboot_timeouts.py", ship)
self.assertIn("apply_009_a15_settings_timeouts.py", ship)
self.assertLess(
ship.index("apply_009_a15_reboot_timeouts.py"),
ship.index("apply_009_a15_settings_timeouts.py"),
)
self.assertIn("settings_reboot_after_timeout", ship)
self.assertIn("isAllowedWhenLocked", ship)
self.assertIn("wifi_timeout", ship)
def test_ship_script_hides_proc_cmdline(self) -> None:
ship = SHIP.read_text()
self.assertIn("apply_009_cmdline_mode.py", ship)
self.assertIn('CHECKCALL(chmod("/proc/cmdline", 0440));', ship)
def test_ship_script_reapplies_complete_feature_008(self) -> None:
ship = SHIP.read_text()
self.assertIn(
"python3 vendor/sunfish-a16/patches/008-call-rec-flag-secure/"
"apply_008_call_rec_flag_secure.py",
ship,
)
self.assertNotIn(
"python3 vendor/sunfish-a16/patches/008-call-rec-flag-secure/"
"apply_wms_disable_secure_debuggable_gate.py",
ship,
)
def test_ship_script_gates_kallsyms_and_call_recording(self) -> None:
ship = SHIP.read_text()
self.assertIn("#define KSYM_NAME_LEN 192", ship)
self.assertIn("SUNFISH_A16_008_CALL_REC_FLAG_SECURE", ship)
self.assertIn("extendrom_call_recording", ship)
def test_artifact_metadata_names_49980_and_vendor_spl(self) -> None:
ship = SHIP.read_text()
self.assertIn(
"CVE-2022-49980-lock-scope",
ship,
)
self.assertIn(
"vendor_security_patch=2023-09-05",
ship,
)
def test_euicc_google_is_stripped_from_the_vendor_makefile(self) -> None:
ship = SHIP.read_text()
apply = (
ROOT
/ "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_euicc_google.py"
).read_text()
apps = (ROOT / "vendor/sunfish-a16/apps.mk").read_text()
self.assertIn("apply_009_drop_euicc_google.py", ship)
self.assertIn('"EuiccGoogle"', apply)
self.assertIn('"EuiccGoogleOverlay"', apply)
self.assertIn("EuiccSupportPixel", apply)
self.assertNotIn("filter-out EuiccGoogle", apps)
def test_carrier_google_is_stripped_where_it_is_added(self) -> None:
ship = SHIP.read_text()
apply = (
ROOT
/ "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_carrier_google.py"
).read_text()
self.assertIn("apply_009_drop_carrier_google.py", ship)
for name in (
"CarrierServices",
"CarrierSettings",
"CarrierWifi",
"CarrierSetup",
"RilConfigService",
"grilservice",
"ImsServiceEntitlement",
"PresencePolling",
"RcsService",
):
self.assertIn(f'"{name}"', apply)
for name in ("EuiccSupportPixel", "ims", "atfwd", "qcrilmsgtunnel", "Dialer"):
self.assertIn(f'"{name}"', apply)
def test_carrier_drop_removes_package_lines_and_is_idempotent(self) -> None:
apply = (
ROOT
/ "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_carrier_google.py"
)
with tempfile.TemporaryDirectory() as tmp:
top = Path(tmp)
vendor = top / "vendor/google/sunfish"
device = top / "device/google/sunfish"
product = top / "build/make/target/product"
for directory in (vendor, device, product):
directory.mkdir(parents=True)
(vendor / "sunfish-vendor.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" CarrierServices \\\n"
" CarrierSettings \\\n"
" CarrierWifi \\\n"
" CarrierSetup \\\n"
" EuiccSupportPixel \\\n"
" EuiccSupportPixelPermissions \\\n"
" ims \\\n"
" QtiTelephonyService \\\n"
" atfwd \\\n"
" qcrilmsgtunnel \\\n"
" uceShimService \\\n"
" RilConfigService \\\n"
" grilservice \\\n"
" vendor/google/sunfish/proprietary/product/etc/CarrierSettings/vodafone_de.pb:$(TARGET_COPY_OUT_PRODUCT)/etc/CarrierSettings/vodafone_de.pb \\\n"
)
(device / "device-lineage.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" GoogleParts\n"
"\n"
"# RCS\n"
"PRODUCT_PACKAGES += \\\n"
" PresencePolling \\\n"
" RcsService\n"
"\n"
"# Touch\n"
)
(product / "telephony_product.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" Dialer \\\n"
" ImsServiceEntitlement \\\n"
" preinstalled-packages-platform-telephony-product.xml\n"
)
env = {**os.environ, "ANDROID_BUILD_TOP": str(top)}
first = subprocess.run(
["python3", str(apply)], cwd=top, env=env, check=False, text=True, capture_output=True
)
self.assertEqual(0, first.returncode, first.stderr)
self.assertIn("dropped", first.stdout)
vendor_text = (vendor / "sunfish-vendor.mk").read_text()
self.assertNotIn("CarrierServices", vendor_text)
self.assertIn("vodafone_de.pb", vendor_text)
self.assertIn(" ims \\\n", vendor_text)
lineage_text = (device / "device-lineage.mk").read_text()
self.assertNotIn("RcsService", lineage_text)
self.assertNotIn("PRODUCT_PACKAGES += \\\n\n", lineage_text)
self.assertIn("GoogleParts", lineage_text)
tele_text = (product / "telephony_product.mk").read_text()
self.assertNotIn("ImsServiceEntitlement", tele_text)
self.assertIn("Dialer", tele_text)
second = subprocess.run(
["python3", str(apply)], cwd=top, env=env, check=False, text=True, capture_output=True
)
self.assertEqual(0, second.returncode, second.stderr)
self.assertIn("already", second.stdout)
def test_lineage_tools_are_stripped_where_they_are_added(self) -> None:
ship = SHIP.read_text()
apply = (
ROOT
/ "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_lineage_tools.py"
)
text = apply.read_text()
self.assertIn("apply_009_drop_lineage_tools.py", ship)
for name in ("bash", "htop", "nano", "50-lineage.sh", "unrar"):
self.assertIn(f'"{name}"', text)
for name in ("curl", "vim", "zstd", "nano_recovery"):
self.assertIn(f'"{name}"', text)
with tempfile.TemporaryDirectory() as tmp:
top = Path(tmp)
common = top / "vendor/lineage/config"
common.mkdir(parents=True)
(common / "common.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" 50-lineage.sh\n"
"\n"
"PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \\\n"
" system/addon.d/50-lineage.sh\n"
"\n"
"PRODUCT_PACKAGES += \\\n"
" bash \\\n"
" curl \\\n"
" getcap \\\n"
" htop \\\n"
" nano \\\n"
" setcap \\\n"
" vim\n"
"\n"
"PRODUCT_PACKAGES += \\\n"
" nano_recovery\n"
)
(common / "common_mobile_full.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" unrar \\\n"
" zstd\n"
)
env = {**os.environ, "ANDROID_BUILD_TOP": str(top)}
first = subprocess.run(
["python3", str(apply)],
cwd=top,
env=env,
check=False,
text=True,
capture_output=True,
)
self.assertEqual(0, first.returncode, first.stderr)
self.assertIn("dropped", first.stdout)
common_text = (common / "common.mk").read_text()
self.assertNotIn("\n bash \\\n", common_text)
self.assertNotIn("\n htop \\\n", common_text)
self.assertNotIn("\n nano \\\n", common_text)
self.assertNotIn(" 50-lineage.sh\n", common_text)
self.assertIn("system/addon.d/50-lineage.sh", common_text)
self.assertIn(" curl \\\n", common_text)
self.assertIn(" vim\n", common_text)
self.assertIn(" nano_recovery\n", common_text)
full_text = (common / "common_mobile_full.mk").read_text()
self.assertNotIn("unrar", full_text)
self.assertIn(" zstd\n", full_text)
second = subprocess.run(
["python3", str(apply)],
cwd=top,
env=env,
check=False,
text=True,
capture_output=True,
)
self.assertEqual(0, second.returncode, second.stderr)
self.assertIn("already", second.stdout)
def test_lab_only_commands_drop_keeps_gps_and_host_pbtombstone(self) -> None:
ship = SHIP.read_text()
apply = (
ROOT
/ "vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_drop_lab_only_commands.py"
)
text = apply.read_text()
self.assertIn("apply_009_drop_lab_only_commands.py", ship) # may be commented (wave 4 deferred)
for name in (
"cnss_diag",
"diag_mdlog",
"imsrcsd",
"disable_configstore",
"bpftool",
"alloctop",
"apexd-lsof.sh",
):
self.assertIn(name, text)
for name in ("loc_launcher", "lowi-server", "xtra-daemon"):
self.assertIn(f'"{name}"', text)
with tempfile.TemporaryDirectory() as tmp:
top = Path(tmp)
vendor = top / "vendor/google/sunfish"
device = top / "device/google/sunfish"
product = top / "build/make/target/product"
bpf = top / "system/bpf/loader"
dump = top / "frameworks/native/cmds/dumpstate"
apex = top / "system/apex/apexd"
for directory in (vendor, device, product, bpf, dump, apex):
directory.mkdir(parents=True)
(vendor / "sunfish-vendor.mk").write_text(
"PRODUCT_COPY_FILES += \\\n"
" vendor/google/sunfish/proprietary/vendor/etc/init/imsrcsd.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/imsrcsd.rc \\\n"
" vendor/google/sunfish/proprietary/vendor/etc/init/imsdatadaemon.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/imsdatadaemon.rc \\\n"
"\n"
"PRODUCT_PACKAGES += \\\n"
" cnss-daemon \\\n"
" cnss_diag \\\n"
" diag_klog \\\n"
" diag_mdlog \\\n"
" diag_socket_log \\\n"
" diag_uart_log \\\n"
" ims_rtp_daemon \\\n"
" imsdatadaemon \\\n"
" imsqmidaemon \\\n"
" imsrcsd \\\n"
" loc_launcher \\\n"
" lowi-server \\\n"
" ssr_diag \\\n"
" test_diag \\\n"
" xtra-daemon\n"
)
(device / "device-lineage.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" chre_daemon_msm\n"
"\n"
"PRODUCT_PACKAGES += \\\n"
" disable_configstore\n"
)
(product / "base_system.mk").write_text(
"PRODUCT_PACKAGES += \\\n"
" logcat \\\n"
" pbtombstone \\\n"
" ping\n"
"\n"
"PRODUCT_HOST_PACKAGES += \\\n"
" fastboot \\\n"
" pbtombstone \\\n"
" sqlite3\n"
)
(bpf / "Android.bp").write_text(
'rust_binary {\n'
' name: "bpfloader",\n'
' required: [\n'
' "bpftool",\n'
' "timeInState.bpf",\n'
' ],\n'
'}\n'
)
(dump / "Android.bp").write_text(
'cc_binary {\n'
' name: "dumpstate",\n'
' required: [\n'
' "alloctop",\n'
' "atrace",\n'
' ],\n'
'}\n'
)
(apex / "Android.bp").write_text(
'cc_binary {\n'
' name: "apexd",\n'
' required: ["apexd-lsof.sh"],\n'
' init_rc: ["apexd.rc"],\n'
'}\n'
)
env = {**os.environ, "ANDROID_BUILD_TOP": str(top)}
first = subprocess.run(
["python3", str(apply)],
cwd=top,
env=env,
check=False,
text=True,
capture_output=True,
)
self.assertEqual(0, first.returncode, first.stderr)
self.assertIn("dropped", first.stdout)
vendor_text = (vendor / "sunfish-vendor.mk").read_text()
self.assertNotIn("cnss_diag", vendor_text)
self.assertNotIn("imsrcsd", vendor_text)
self.assertIn("imsdatadaemon.rc", vendor_text)
self.assertIn(" loc_launcher \\\n", vendor_text)
self.assertIn(" xtra-daemon\n", vendor_text)
self.assertIn("cnss-daemon", vendor_text)
self.assertNotIn("disable_configstore", (device / "device-lineage.mk").read_text())
base = (product / "base_system.mk").read_text()
self.assertNotIn(" pbtombstone \\\n ping\n", base)
self.assertIn("PRODUCT_HOST_PACKAGES += \\\n fastboot \\\n pbtombstone \\\n", base)
self.assertNotIn('"bpftool",', (bpf / "Android.bp").read_text())
self.assertIn("timeInState.bpf", (bpf / "Android.bp").read_text())
self.assertNotIn("alloctop", (dump / "Android.bp").read_text())
self.assertIn("atrace", (dump / "Android.bp").read_text())
self.assertNotIn("apexd-lsof.sh", (apex / "Android.bp").read_text())
self.assertIn("apexd.rc", (apex / "Android.bp").read_text())
second = subprocess.run(
["python3", str(apply)],
cwd=top,
env=env,
check=False,
text=True,
capture_output=True,
)
self.assertEqual(0, second.returncode, second.stderr)
self.assertIn("already", second.stdout)
def test_large_axp_zip_is_ignored(self) -> None:
result = subprocess.run(
["git", "check-ignore", "--quiet", "AXP.OS-22.2-20260324-PRO-sunfish.zip"],
cwd=ROOT,
check=False,
)
self.assertEqual(0, result.returncode)
if __name__ == "__main__":
unittest.main()
+34
View File
@@ -0,0 +1,34 @@
"""012 hardened_malloc port keeps the AXP/Graphene exclusions."""
from __future__ import annotations
import pathlib
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
APPLY = (
ROOT
/ "vendor/sunfish-a16/patches/012-hardened-malloc/apply_012_hardened_malloc.py"
)
WRAPPER = (
ROOT
/ "vendor/sunfish-a16/patches/012-hardened-malloc/h_malloc_wrapper.cpp"
)
class HardenedMallocPort(unittest.TestCase):
def test_pin_and_exclusions(self) -> None:
text = APPLY.read_text()
self.assertIn("1d7fc7ffe0f8068a32ab2ba33df7515e7bad3f5f", text)
self.assertIn("/system/bin/surfaceflinger", text)
self.assertIn('strstr(exe_path, "camera")', text)
self.assertIn("max_map_count 1048576", text)
self.assertIn("USE_H_MALLOC", text)
self.assertIn('REGION_39BIT = "-DCONFIG_CLASS_REGION_SIZE=2147483648"', text)
self.assertIn("text.replace(REGION_48BIT, REGION_39BIT)", text)
self.assertIn('lost noinline', text)
self.assertTrue(WRAPPER.is_file())
self.assertIn("h_malloc_info", WRAPPER.read_text())
if __name__ == "__main__":
unittest.main()
+2 -6
View File
@@ -89,9 +89,5 @@ LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/prebuilt/FossifyGallery.ap
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
include $(BUILD_PREBUILT)
# US-S4-2 — AXP PRO has no Jelly (system_ext noDefaultBrowserWebView).
include $(CLEAR_VARS)
LOCAL_MODULE := NoLineageJelly
LOCAL_MODULE_TAGS := optional
LOCAL_OVERRIDES_PACKAGES := Jelly
include $(BUILD_PHONY_PACKAGE)
# EuiccGoogle is removed by apply_009_drop_euicc_google.py from
# vendor/google/sunfish/sunfish-vendor.mk.
-5
View File
@@ -8,11 +8,6 @@
BOARD_SUPER_PARTITION_ERROR_LIMIT := 9441378304
# Product LINEAGE_VERSION id: set in vendor/extra/product.mk (via bacon
# symlink to extra-product.mk) — BoardConfig is too late for version.mk.
# Keep a duplicate here only as documentation / get_build_var fallback.
TARGET_UNOFFICIAL_BUILD_ID := AXP-Resurection-Pro
# US-S3 — project AVB *signing* key (Gate G infrastructure).
# Included after device/google/sunfish BoardConfig-common.mk → override here.
# Keep WITH_AVB unset → BoardConfigLineage adds vbmeta --flags 3
+12 -1
View File
@@ -1,8 +1,19 @@
# Bundled user apps (009) — Aurora Store, Droidify, Fossify Gallery.
# APKs live on host at /srv/rom/prebuilts/axp-apps/ (never Git).
# Symlink into vendor/sunfish-a16/prebuilt/ before bacon.
#
# OpenEUICC is the privileged LPA, same as AXP GOLDEN: system_ext priv-app,
# platform certificate, privapp whitelist. Source is not an APK prebuilt.
# Host tree: packages/apps/OpenEUICC @ upstream 89d677e07d0e (2026-09-28).
# Not the AXP fork tag. Datastore comes from LOS androidx.
# prebuilts/openeuicc-deps @ 5402167 (zxing only).
PRODUCT_PACKAGES += \
AuroraStore \
Droidify \
FossifyGallery
FossifyGallery \
OpenEUICC
# EuiccGoogle is removed by apply_009_drop_euicc_google.py. inherit-product
# merges PRODUCT_PACKAGES per makefile, so a filter-out here does not see
# the sunfish-vendor.mk entries.
+2 -2
View File
@@ -3,8 +3,8 @@
# "Pro" marks the full Magisk-resurrection track (not a future slim/minimal
# variant). GOLDEN used releasetype PRO + ro.lineage.version=…-sunfish-pro.
#
# TARGET_UNOFFICIAL_BUILD_ID is set in vendor/extra/product.mk (early Lineage
# hook, before version.mk). BoardConfig + this file are not early enough alone.
# LINEAGE_BUILDTYPE := PRO comes from apply_009_axp_identity.py (AXP Patch.sh
# method), so ro.lineage.releasetype=PRO and the version is not UNOFFICIAL.
# Ship scripts also publish AXP-Resurection-Pro-*.zip from lineage-*.zip.
# Identity props (not axp.os endpoints). Empty on GOLDEN for ro.axp.version —
+2 -4
View File
@@ -1,5 +1,3 @@
# Early Lineage hook (vendor/lineage/config/common.mk inherits this *before*
# version.mk). Required so LINEAGE_VERSION picks up the unofficial id.
# BoardConfig.mk is too late for TARGET_UNOFFICIAL_BUILD_ID.
TARGET_UNOFFICIAL_BUILD_ID := AXP-Resurection-Pro
# version.mk). Ship identity is LINEAGE_BUILDTYPE := PRO, set by
# apply_009_axp_identity.py the way AXP Patch.sh does. No unofficial build id.
+4 -3
View File
@@ -4,7 +4,9 @@
#
# Real strips:
# US-S4-1 apply_009_drop_openssh_packages.py (common.mk)
# US-S4-2 apply_009_drop_jelly_gallery2.py (Jelly + Gallery2)
# US-S4-2 Jelly stays (owner 2026-10-07). LOS 23 does not put it in
# common_mobile.mk, so it is added here. Gallery is FossifyGallery,
# not Gallery2 or Glimpse. Do not put Gallery2 back.
# PRODUCT_PACKAGES filter-out via inherit alone has failed before — keep as
# belt-and-suspenders only.
@@ -17,12 +19,11 @@ PRODUCT_PACKAGES := $(filter-out \
ssh-keygen \
start-ssh \
rsync \
Jelly \
Gallery2 \
Glimpse \
,$(PRODUCT_PACKAGES))
PRODUCT_PACKAGES += NoLineageJelly
PRODUCT_PACKAGES += Jelly
PRODUCT_PACKAGES_DEBUG := $(filter-out procmem,$(PRODUCT_PACKAGES_DEBUG))
+1 -1
View File
@@ -8,7 +8,7 @@
# US-S2 release keys (PRODUCT_DEFAULT_DEV_CERTIFICATE) — see signing.mk
$(call inherit-product-if-exists, vendor/sunfish-a16/signing.mk)
# OTA / version branding (TARGET_UNOFFICIAL_BUILD_ID=AXP-Resurection-Pro)
# OTA / version branding (releasetype PRO)
$(call inherit-product-if-exists, vendor/sunfish-a16/branding.mk)
# US-S4-1 userspace hardening (drop product OpenSSH, etc.)
@@ -38,8 +38,23 @@ def backup(path: Path) -> None:
def patch_computer_engine() -> None:
path = TREE / "frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java"
text = path.read_text()
# AXP/EXTENDROM spoofs on user images (GOLDEN ro.debuggable=0). LOS stock
# isDebuggable() would disable that on lineage_sunfish-user.
deb_gate = """ public static boolean isMicrogSigned(AndroidPackage p) {
if (!isDebuggable()) {
return false;
}
"""
user_ok = """ public static boolean isMicrogSigned(AndroidPackage p) {
// %s: no isDebuggable() gate (AXP user / GOLDEN).
""" % MARKER
if MARKER in text:
print("ComputerEngine: already patched")
if deb_gate not in text:
print("ComputerEngine: already patched, user gate ok")
return
backup(path)
path.write_text(text.replace(deb_gate, user_ok, 1))
print("ComputerEngine: dropped isDebuggable gate")
return
backup(path)
@@ -78,9 +93,7 @@ def patch_computer_engine() -> None:
}"""
new_is = """ public static boolean isMicrogSigned(AndroidPackage p) {
if (!isDebuggable()) {
return false;
}
// SUNFISH_A16_005_SIGSPOOF: no isDebuggable() gate (AXP user / GOLDEN).
// Allowlist the following apps:
// * com.android.vending - microG Companion / patched Phonesky
@@ -0,0 +1,153 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: anupritaisno1 <www.anuprita804@gmail.com>
Date: Mon, 18 Oct 2021 01:35:40 +0300
Subject: [PATCH] automatically reboot device after timeout if set
Signed-off-by: anupritaisno1 <www.anuprita804@gmail.com>
Change-Id: If891bfbcc144c9336ba013260bad2b7c7a59c054
---
core/java/android/provider/Settings.java | 7 ++++
data/etc/com.android.systemui.xml | 1 +
packages/SystemUI/AndroidManifest.xml | 3 ++
.../keyguard/KeyguardViewMediator.java | 35 +++++++++++++++++++
4 files changed, 46 insertions(+)
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 783483d8e491..48913891ac52 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19027,6 +19027,13 @@ public final class Settings {
public static final String REVIEW_PERMISSIONS_NOTIFICATION_STATE =
"review_permissions_notification_state";
+ /**
+ * Whether to automatically reboot the device after a user defined timeout
+ *
+ * @hide
+ */
+ public static final String SETTINGS_REBOOT_AFTER_TIMEOUT = "settings_reboot_after_timeout";
+
/**
* Whether repair mode is active on the device.
* <p>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ce2543a47cf5..67108d16e099 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -54,6 +54,7 @@
<permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
<permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
<permission name="android.permission.REAL_GET_TASKS"/>
+ <permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
<permission name="android.permission.SATELLITE_COMMUNICATION"/>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 351cce0929b7..00604a316019 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -356,6 +356,9 @@
<uses-permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
+ <!-- Permission to allow rebooting the device after a user configurable amount of time -->
+ <uses-permission android:name="android.permission.REBOOT" />
+
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
<uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6034e92be873..67dd84e8ae8f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -240,6 +240,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final static String TAG = "KeyguardViewMediator";
+ private static final String DELAYED_REBOOT_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_REBOOT";
public static final String DELAYED_KEYGUARD_ACTION =
"com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
private static final String DELAYED_LOCK_PROFILE_ACTION =
@@ -417,6 +419,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
*/
private int mDelayedProfileShowingSequence;
+ /**
+ * Same as {@link #mDelayedProfileShowingSequence}, but used for our reboot implementation
+ */
+ private int mDelayedRebootSequence;
+
private final DismissCallbackRegistry mDismissCallbackRegistry;
// the properties of the keyguard
@@ -1503,6 +1510,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
final IntentFilter delayedActionFilter = new IntentFilter();
delayedActionFilter.addAction(DELAYED_KEYGUARD_ACTION);
delayedActionFilter.addAction(DELAYED_LOCK_PROFILE_ACTION);
+ delayedActionFilter.addAction(DELAYED_REBOOT_ACTION);
delayedActionFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter,
SYSTEMUI_PERMISSION, null /* scheduler */,
@@ -1866,6 +1874,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
+ private void doRebootForOwnerAfterTimeoutIfEnabled(long rebootAfterTimeout) {
+ long when = mSystemClock.elapsedRealtime() + rebootAfterTimeout;
+ Intent rebootIntent = new Intent(DELAYED_REBOOT_ACTION);
+ rebootIntent.putExtra("seq", mDelayedRebootSequence);
+ rebootIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ PendingIntent sender = PendingIntent.getBroadcast(mContext,
+ 0, rebootIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
+ if (DEBUG) Log.d(TAG, "setting alarm to reboot device, timeout = "
+ + String.valueOf(rebootAfterTimeout));
+ }
+
private void doKeyguardForChildProfilesLocked() {
for (UserInfo profile : mUserTracker.getUserProfiles()) {
if (!profile.isEnabled()) continue;
@@ -1884,6 +1904,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDelayedProfileShowingSequence++;
}
+ private void cancelDoRebootForOwnerAfterTimeoutIfEnabled() {
+ mDelayedRebootSequence++;
+ }
+
/**
* It will let us know when the device is waking up.
*/
@@ -2298,6 +2322,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
showKeyguard(options);
+ final long rebootAfterTimeout = Settings.Global.getLong(mContext.getContentResolver(), Settings.Global.SETTINGS_REBOOT_AFTER_TIMEOUT, 0);
+ if (rebootAfterTimeout >= 1) {
+ doRebootForOwnerAfterTimeoutIfEnabled(rebootAfterTimeout);
+ }
}
@SuppressLint("MissingPermission")
@@ -2478,6 +2506,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
}
+ } else if (DELAYED_REBOOT_ACTION.equals(intent.getAction())) {
+ final int sequence = intent.getIntExtra("seq", 0);
+ if (sequence == mDelayedRebootSequence) {
+ PowerManager pm = mContext.getSystemService(PowerManager.class);
+ pm.reboot(null);
+ }
}
}
};
@@ -3200,6 +3234,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
+ cancelDoRebootForOwnerAfterTimeoutIfEnabled();
}
private Configuration.Builder createInteractionJankMonitorConf(int cuj) {
@@ -0,0 +1,62 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Tue, 6 Sep 2022 16:48:26 +0300
Subject: [PATCH] bugfix: Bluetooth auto turn off ignored connected BLE devices
Previous attempt at fixing this didn't work properly, because getConnectionStateLeAware() didn't
actually report BLE state.
---
.../android/server/ext/BluetoothAutoOff.java | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/services/core/java/com/android/server/ext/BluetoothAutoOff.java b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
index 4e7dbc042f37..a091b006214f 100644
--- a/services/core/java/com/android/server/ext/BluetoothAutoOff.java
+++ b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
@@ -3,6 +3,7 @@ package com.android.server.ext;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -12,12 +13,14 @@ import android.provider.Settings;
import android.util.Slog;
class BluetoothAutoOff extends DelayedConditionalAction {
+ private final BluetoothManager manager;
@Nullable
private final BluetoothAdapter adapter;
BluetoothAutoOff(SystemServerExt sse) {
super(sse, sse.bgHandler);
- adapter = sse.context.getSystemService(BluetoothManager.class).getAdapter();
+ manager = sse.context.getSystemService(BluetoothManager.class);
+ adapter = manager.getAdapter();
}
@Override
@@ -51,11 +54,18 @@ class BluetoothAutoOff extends DelayedConditionalAction {
private boolean isAdapterOnAndDisconnected() {
if (adapter != null) {
- int state = adapter.getLeStateSysApi(); // getState() converts BLE states into STATE_OFF
+ if (adapter.isLeEnabled()) {
+ if (adapter.getConnectionState() == BluetoothAdapter.STATE_DISCONNECTED) {
+ // Bluetooth GATT Profile (Bluetooth LE) connection state is ignored
+ // by getConnectionState()
+ return manager.getConnectedDevices(BluetoothProfile.GATT).size() == 0;
+ }
+ }
- if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON) {
- // getConnectionState() converts BLE states into STATE_DISCONNECTED
- return adapter.getConnectionStateLeAware() == BluetoothAdapter.STATE_DISCONNECTED;
+ // isLeEnabled() currently implies isEnabled(), but check again anyway in case
+ // this changes in the future
+ if (adapter.isEnabled()) {
+ return adapter.getConnectionState() == BluetoothAdapter.STATE_DISCONNECTED;
}
}
@@ -0,0 +1,120 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Sun, 31 Jul 2022 11:19:33 +0300
Subject: [PATCH] Bluetooth auto turn off
Co-authored-by: Pratyush <codelab@pratyush.dev>
---
core/java/android/provider/Settings.java | 6 ++
.../android/server/ext/BluetoothAutoOff.java | 69 +++++++++++++++++++
.../android/server/ext/SystemServerExt.java | 4 ++
3 files changed, 79 insertions(+)
create mode 100644 services/core/java/com/android/server/ext/BluetoothAutoOff.java
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 49f59950b9e0..58f1077af988 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19549,6 +19549,12 @@ public final class Settings {
*/
public static final String WIFI_OFF_TIMEOUT = "wifi_off_timeout";
+ /**
+ * The amount of time in milliseconds before a disconnected Bluetooth adapter is turned off
+ * @hide
+ */
+ public static final String BLUETOOTH_OFF_TIMEOUT = "bluetooth_off_timeout";
+
/**
* Whether repair mode is active on the device.
* <p>
diff --git a/services/core/java/com/android/server/ext/BluetoothAutoOff.java b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
new file mode 100644
index 000000000000..4e7dbc042f37
--- /dev/null
+++ b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
@@ -0,0 +1,69 @@
+package com.android.server.ext;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.Slog;
+
+class BluetoothAutoOff extends DelayedConditionalAction {
+ @Nullable
+ private final BluetoothAdapter adapter;
+
+ BluetoothAutoOff(SystemServerExt sse) {
+ super(sse, sse.bgHandler);
+ adapter = sse.context.getSystemService(BluetoothManager.class).getAdapter();
+ }
+
+ @Override
+ protected boolean shouldScheduleAlarm() {
+ return isAdapterOnAndDisconnected();
+ }
+
+ @Override
+ protected void alarmTriggered() {
+ if (isAdapterOnAndDisconnected()) {
+ adapter.disable();
+ }
+ }
+
+ @Override
+ protected void registerStateListener() {
+ IntentFilter f = new IntentFilter();
+ f.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ f.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+
+ sse.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context broadcastContext, Intent intent) {
+ if (Build.isDebuggable()) {
+ Slog.d("BtAutoOff", "" + intent + ", extras " + intent.getExtras().deepCopy());
+ }
+ update();
+ }
+ }, f, handler);
+ }
+
+ private boolean isAdapterOnAndDisconnected() {
+ if (adapter != null) {
+ int state = adapter.getLeStateSysApi(); // getState() converts BLE states into STATE_OFF
+
+ if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON) {
+ // getConnectionState() converts BLE states into STATE_DISCONNECTED
+ return adapter.getConnectionStateLeAware() == BluetoothAdapter.STATE_DISCONNECTED;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected String getDelayGlobalSettingsKey() {
+ return Settings.Global.BLUETOOTH_OFF_TIMEOUT;
+ }
+}
diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java
index 66350e2b7f74..3c341ed25f4e 100644
--- a/services/core/java/com/android/server/ext/SystemServerExt.java
+++ b/services/core/java/com/android/server/ext/SystemServerExt.java
@@ -53,6 +53,10 @@ public final class SystemServerExt {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)) {
new WifiAutoOff(this);
}
+
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH, 0)) {
+ new BluetoothAutoOff(this);
+ }
}
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
@@ -0,0 +1,231 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Sun, 31 Jul 2022 10:06:14 +0300
Subject: [PATCH] infrastructure for system_server extensions
---
.../server/ext/DelayedConditionalAction.java | 135 ++++++++++++++++++
.../android/server/ext/SystemServerExt.java | 58 ++++++++
.../java/com/android/server/SystemServer.java | 2 +
3 files changed, 195 insertions(+)
create mode 100644 services/core/java/com/android/server/ext/DelayedConditionalAction.java
create mode 100644 services/core/java/com/android/server/ext/SystemServerExt.java
diff --git a/services/core/java/com/android/server/ext/DelayedConditionalAction.java b/services/core/java/com/android/server/ext/DelayedConditionalAction.java
new file mode 100644
index 000000000000..d72f302e9d42
--- /dev/null
+++ b/services/core/java/com/android/server/ext/DelayedConditionalAction.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 GrapheneOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ext;
+
+import android.app.AlarmManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Slog;
+
+/**
+ * Infrastructure for actions that:
+ * - happen after a user-configurable device-wide (Settings.Global) delay
+ * - need to be taken even when the device is in deep sleep
+ * - need to be rescheduled based on some listenable event
+ */
+public abstract class DelayedConditionalAction {
+ private static final String TAG = "DelayedConditionalAction";
+
+ protected final SystemServerExt sse;
+ protected final Thread thread;
+ protected final Handler handler;
+
+ protected final ContentResolver contentResolver;
+ protected final AlarmManager alarmManager;
+ private final AlarmManager.OnAlarmListener alarmListener;
+
+ protected DelayedConditionalAction(SystemServerExt sse, Handler handler) {
+ this.sse = sse;
+
+ Looper looper = handler.getLooper();
+ thread = looper.getThread();
+ this.handler = handler;
+
+ if (Build.isDebuggable()) {
+ if (thread != Thread.currentThread()) {
+ throw new IllegalStateException("all calls should happen on the same thread");
+ }
+ }
+
+ Context ctx = sse.context;
+ contentResolver = ctx.getContentResolver();
+ alarmManager = ctx.getSystemService(AlarmManager.class);
+
+ alarmListener = () -> {
+ if (delayDurationMillis() == 0) {
+ return;
+ }
+
+ alarmTriggered();
+ };
+
+ registerStateListener();
+
+ Uri delaySettingUri = Settings.Global.getUriFor(getDelayGlobalSettingsKey());
+
+ ContentObserver delayChangeListener = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ update();
+ }
+ };
+
+ contentResolver.registerContentObserver(delaySettingUri, false, delayChangeListener);
+ }
+
+ private boolean alarmScheduled;
+
+ protected final void update() {
+ final Thread curThread = Thread.currentThread();
+ if (curThread != thread) {
+ String msg = "update() called on an unknown thread " + curThread;
+ if (Build.isDebuggable()) {
+ throw new IllegalStateException(msg);
+ } else {
+ Slog.e(TAG, msg, new Throwable());
+ return;
+ }
+ }
+
+ if (alarmScheduled) {
+ alarmManager.cancel(alarmListener);
+ alarmScheduled = false;
+ }
+
+ if (!shouldScheduleAlarm()) {
+ return;
+ }
+
+ long delayMillis = delayDurationMillis();
+
+ if (delayMillis == 0) {
+ return;
+ }
+
+ final long triggerAt = SystemClock.elapsedRealtime() + delayMillis;
+ alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt,
+ getClass().getName(), alarmListener, handler);
+ alarmScheduled = true;
+ }
+
+ private long delayDurationMillis() {
+ return Settings.Global.getLong(contentResolver, getDelayGlobalSettingsKey(), 0);
+ }
+
+ // Make sure to use the same Handler that is used for all other callbacks;
+ // call update() to reschedule / cancel the alarm
+ protected abstract void registerStateListener();
+
+ protected abstract boolean shouldScheduleAlarm();
+ protected abstract void alarmTriggered();
+
+ // android.provider.Settings.Global key
+ protected abstract String getDelayGlobalSettingsKey();
+}
diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java
new file mode 100644
index 000000000000..83d895650473
--- /dev/null
+++ b/services/core/java/com/android/server/ext/SystemServerExt.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 GrapheneOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ext;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.pm.PackageManagerService;
+
+public final class SystemServerExt {
+
+ public final Context context;
+ public final Handler bgHandler;
+ public final PackageManagerService packageManager;
+
+ private SystemServerExt(Context systemContext, PackageManagerService pm) {
+ context = systemContext;
+ bgHandler = BackgroundThread.getHandler();
+ packageManager = pm;
+ }
+
+ /*
+ Called after system server has completed its initialization,
+ but before any of the apps are started.
+
+ Call from com.android.server.SystemServer#startOtherServices(), at the end of lambda
+ that is passed into mActivityManagerService.systemReady()
+ */
+ public static void init(Context systemContext, PackageManagerService pm) {
+ SystemServerExt sse = new SystemServerExt(systemContext, pm);
+ sse.bgHandler.post(sse::initBgThread);
+ }
+
+ void initBgThread() {
+
+ }
+
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
+ context.registerReceiver(receiver, filter, null, handler);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e92ba3ed7dc3..df60c628ccdb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3454,6 +3454,8 @@ public final class SystemServer implements Dumpable {
reportWtf("Triggering OdsignStatsLogger", e);
}
t.traceEnd();
+
+ com.android.server.ext.SystemServerExt.init(mSystemContext, mPackageManagerService);
}, t);
t.traceBegin("LockSettingsThirdPartyAppsStarted");
@@ -0,0 +1,128 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Sun, 31 Jul 2022 10:06:14 +0300
Subject: [PATCH] Wi-Fi auto turn off
Co-authored-by: Pratyush <codelab@pratyush.dev>
---
core/java/android/provider/Settings.java | 6 ++
.../android/server/ext/SystemServerExt.java | 5 +-
.../com/android/server/ext/WifiAutoOff.java | 69 +++++++++++++++++++
3 files changed, 79 insertions(+), 1 deletion(-)
create mode 100644 services/core/java/com/android/server/ext/WifiAutoOff.java
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 10efaa78e435..49f59950b9e0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19543,6 +19543,12 @@ public final class Settings {
*/
public static final String SETTINGS_REBOOT_AFTER_TIMEOUT = "settings_reboot_after_timeout";
+ /**
+ * The amount of time in milliseconds before a disconnected Wi-Fi adapter is turned off
+ * @hide
+ */
+ public static final String WIFI_OFF_TIMEOUT = "wifi_off_timeout";
+
/**
* Whether repair mode is active on the device.
* <p>
diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java
index 83d895650473..66350e2b7f74 100644
--- a/services/core/java/com/android/server/ext/SystemServerExt.java
+++ b/services/core/java/com/android/server/ext/SystemServerExt.java
@@ -19,6 +19,7 @@ package com.android.server.ext;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.os.Handler;
import com.android.internal.os.BackgroundThread;
@@ -49,7 +50,9 @@ public final class SystemServerExt {
}
void initBgThread() {
-
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)) {
+ new WifiAutoOff(this);
+ }
}
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
diff --git a/services/core/java/com/android/server/ext/WifiAutoOff.java b/services/core/java/com/android/server/ext/WifiAutoOff.java
new file mode 100644
index 000000000000..c7a3c05fe766
--- /dev/null
+++ b/services/core/java/com/android/server/ext/WifiAutoOff.java
@@ -0,0 +1,69 @@
+package com.android.server.ext;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.Slog;
+
+class WifiAutoOff extends DelayedConditionalAction {
+ private final WifiManager wifiManager;
+
+ WifiAutoOff(SystemServerExt sse) {
+ super(sse, sse.bgHandler);
+ wifiManager = sse.context.getSystemService(WifiManager.class);
+ }
+
+ @Override
+ protected boolean shouldScheduleAlarm() {
+ return isWifiEnabledAndNotConnected();
+ }
+
+ @Override
+ protected void alarmTriggered() {
+ if (isWifiEnabledAndNotConnected()) {
+ wifiManager.setWifiEnabled(false);
+ }
+ }
+
+ private boolean isWifiEnabledAndNotConnected() {
+ if (wifiManager.isWifiEnabled()) {
+ WifiInfo i = wifiManager.getConnectionInfo();
+ if (i == null) {
+ return true;
+ }
+ return i.getBSSID() == null;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void registerStateListener() {
+ IntentFilter f = new IntentFilter();
+ f.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ f.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ // ConnectivityManager APIs seem unfit for listening to Wi-Fi state specifically, they look
+ // to be higher level than that, eg VPN over Wi-Fi isn't considered to be a Wi-Fi connection
+ // by ConnectivityManager
+
+ sse.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Build.isDebuggable()) {
+ Slog.d("WifiAutoOff", "" + intent + ", extras " + intent.getExtras().deepCopy());
+ }
+ update();
+ }
+ }, f, handler);
+ }
+
+ @Override
+ protected String getDelayGlobalSettingsKey() {
+ return Settings.Global.WIFI_OFF_TIMEOUT;
+ }
+}
@@ -0,0 +1,141 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Mon, 11 Mar 2024 19:40:18 +0200
Subject: [PATCH] SystemUI: require unlocking to use QS tiles by default
---
.../src/com/android/systemui/qs/QSHost.java | 1 +
.../com/android/systemui/qs/QSHostAdapter.kt | 4 +++
.../systemui/qs/tileimpl/QSTileImpl.java | 36 +++++++++++++++++--
.../systemui/qs/QSHostAdapterKosmos.kt | 2 ++
4 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 7c7f48e0d37b..87e87348d0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -51,6 +51,7 @@ public interface QSHost {
Context getUserContext();
int getUserId();
Collection<QSTile> getTiles();
+ com.android.systemui.statusbar.policy.KeyguardStateController getKeyguardStateController();
void addCallback(Callback callback);
void removeCallback(Callback callback);
void removeTile(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index dc3b58247152..08f3fc9d7bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -31,6 +31,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -53,6 +54,7 @@ constructor(
@ShadeDisplayAware private val context: Context,
private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
@Application private val scope: CoroutineScope,
+ private val keyguardStateController_: KeyguardStateController,
dumpManager: DumpManager,
) : QSHost {
@@ -72,6 +74,8 @@ constructor(
return interactor.currentQSTiles
}
+ override fun getKeyguardStateController() = keyguardStateController_
+
override fun getSpecs(): List<String> {
return interactor.currentTilesSpecs.map { it.spec }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 1d1e9911884c..e9dc57e4c47d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -71,6 +71,7 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SideLabelTileLayout;
import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.Objects;
@@ -272,6 +273,32 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
return true;
}
+ public enum Action {
+ CLICK,
+ SECONDARY_CLICK,
+ LONG_CLICK,
+ }
+
+ public boolean isAllowedWhenLocked(Action action) {
+ return false;
+ }
+
+ void handleAction(Action action, Runnable handler) {
+ if (isAllowedWhenLocked(action)) {
+ handler.run();
+ return;
+ }
+
+ KeyguardStateController ksc = mHost.getKeyguardStateController();
+ boolean hasSecureKeyguard = ksc.isMethodSecure() && ksc.isShowing();
+
+ if (hasSecureKeyguard) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(handler);
+ } else {
+ handler.run();
+ }
+ }
+
// safe to call from any thread
public void addCallback(Callback callback) {
@@ -616,16 +643,19 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
- handleClick((Expandable) msg.obj);
+ Expandable view = (Expandable) msg.obj;
+ handleAction(Action.CLICK, () -> handleClick(view));
}
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
- handleSecondaryClick((Expandable) msg.obj);
+ Expandable view = (Expandable) msg.obj;
+ handleAction(Action.SECONDARY_CLICK, () -> handleSecondaryClick(view));
} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";
mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
- handleLongClick((Expandable) msg.obj);
+ Expandable view = (Expandable) msg.obj;
+ handleAction(Action.LONG_CLICK, () -> handleLongClick(view));
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
handleRefreshState(msg.obj);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
index 0bf801b35ad1..7cb93459077c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.external.tileServiceRequestControllerBuilder
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.qsHostAdapter by
Kosmos.Fixture {
@@ -30,6 +31,7 @@ val Kosmos.qsHostAdapter by
applicationContext,
tileServiceRequestControllerBuilder,
applicationCoroutineScope,
+ keyguardStateController,
dumpManager,
)
}
@@ -0,0 +1,196 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: pratyush <codelab@pratyush.dev>
Date: Mon, 7 Jun 2021 22:15:59 +0100
Subject: [PATCH] add auto-reboot setting
---
res/values/arrays.xml | 31 +++++++
res/values/strings.xml | 3 +
res/xml/security_dashboard_settings.xml | 10 ++-
.../AutoRebootPreferenceController.java | 82 +++++++++++++++++++
.../settings/security/SecuritySettings.java | 1 +
5 files changed, 126 insertions(+), 1 deletion(-)
create mode 100644 src/com/android/settings/security/AutoRebootPreferenceController.java
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index febdb0412a4..c5df677975d 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -105,6 +105,37 @@
<item>1800000</item>
</string-array>
+ <!-- Auto reboot settings -->
+ <string-array name="auto_reboot_entries">
+ <item>Off</item>
+ <item>10 minutes</item>
+ <item>30 minutes</item>
+ <item>1 hour</item>
+ <item>2 hours</item>
+ <item>4 hours</item>
+ <item>8 hours</item>
+ <item>12 hours</item>
+ <item>24 hours</item>
+ <item>36 hours</item>
+ <item>48 hours</item>
+ <item>72 hours</item>
+ </string-array>
+
+ <string-array name="auto_reboot_values" translatable="false">
+ <item>0</item> <!-- Disabled -->
+ <item>600000</item>
+ <item>1800000</item>
+ <item>3600000</item>
+ <item>7200000</item>
+ <item>14400000</item>
+ <item>28800000</item>
+ <item>43200000</item>
+ <item>86400000</item>
+ <item>129600000</item>
+ <item>172800000</item>
+ <item>259200000</item>
+ </string-array>
+
<!-- Wi-Fi settings -->
<!-- Match this with the order of NetworkInfo.DetailedState. --> <skip />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2e44c013a96..b7105938345 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -675,6 +675,9 @@
<!-- Text shown for title of owner info setting [CHAR LIMIT=30]-->
<string name="owner_info_settings_title">Add text on lock screen</string>
+ <string name="auto_reboot_title">Auto reboot</string>
+ <string name="auto_reboot_summary">Automatically reboot the device, if the phone hasn\'t been unlocked within the selected number of hours.</string>
+
<!-- Text shown for summary of owner info setting (if none set) [CHAR LIMIT=40]-->
<string name="owner_info_settings_summary">None</string>
<!-- Hint text shown in owner info edit text [CHAR LIMIT=50] -->
diff --git a/res/xml/security_dashboard_settings.xml b/res/xml/security_dashboard_settings.xml
index 0550441d3f2..d0aeb27fe53 100644
--- a/res/xml/security_dashboard_settings.xml
+++ b/res/xml/security_dashboard_settings.xml
@@ -60,6 +60,14 @@
android:title="@string/security_settings_biometric_preference_title"
android:summary="@string/summary_placeholder"
settings:keywords="@string/keywords_biometric_settings" />
+
+ <ListPreference
+ android:key="auto_reboot"
+ android:title="@string/auto_reboot_title"
+ android:summary="@string/auto_reboot_summary"
+ android:persistent="false"
+ android:entries="@array/auto_reboot_entries"
+ android:entryValues="@array/auto_reboot_values" />
</PreferenceCategory>
<Preference
@@ -71,4 +79,4 @@
settings:controller="com.android.settings.security.SecurityAdvancedSettingsController"
settings:keywords="@string/security_advanced_settings_keywords" />
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/src/com/android/settings/security/AutoRebootPreferenceController.java b/src/com/android/settings/security/AutoRebootPreferenceController.java
new file mode 100644
index 00000000000..c7a75219a5e
--- /dev/null
+++ b/src/com/android/settings/security/AutoRebootPreferenceController.java
@@ -0,0 +1,82 @@
+package com.android.settings.security;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+public class AutoRebootPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin, OnResume,
+ Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_AUTO_REBOOT = "auto_reboot";
+ private static final String PREF_KEY_SECURITY_CATEGORY = "security_category";
+
+ private PreferenceCategory mSecurityCategory;
+ private boolean mIsAdmin;
+ private final UserManager mUm;
+
+ public AutoRebootPreferenceController(Context context) {
+ super(context);
+ mUm = UserManager.get(context);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mSecurityCategory = screen.findPreference(PREF_KEY_SECURITY_CATEGORY);
+ updatePreferenceState();
+ }
+
+ @Override
+ public boolean isAvailable() {
+ mIsAdmin = mUm.isAdminUser();
+ return mIsAdmin;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_AUTO_REBOOT;
+ }
+
+ // TODO: should we use onCreatePreferences() instead?
+ private void updatePreferenceState() {
+ if (mSecurityCategory == null) {
+ return;
+ }
+
+ if (mIsAdmin) {
+ ListPreference autoReboot =
+ (ListPreference) mSecurityCategory.findPreference(KEY_AUTO_REBOOT);
+ autoReboot.setValue(Long.toString(Settings.Global.getLong(
+ mContext.getContentResolver(), Settings.Global.SETTINGS_REBOOT_AFTER_TIMEOUT, 0)));
+ } else {
+ mSecurityCategory.removePreference(
+ mSecurityCategory.findPreference(KEY_AUTO_REBOOT));
+ }
+ }
+
+ @Override
+ public void onResume() {
+ updatePreferenceState();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ final String key = preference.getKey();
+ if (KEY_AUTO_REBOOT.equals(key) && mIsAdmin) {
+ long timeout = Long.parseLong((String) value);
+ Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.SETTINGS_REBOOT_AFTER_TIMEOUT, timeout);
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java
index 797d3ec621e..3f0645f7dd8 100644
--- a/src/com/android/settings/security/SecuritySettings.java
+++ b/src/com/android/settings/security/SecuritySettings.java
@@ -114,6 +114,7 @@ public class SecuritySettings extends DashboardFragment {
securityPreferenceControllers.add(new CombinedBiometricStatusPreferenceController(
context, lifecycle));
securityPreferenceControllers.add(new ChangeScreenLockPreferenceController(context, host));
+ securityPreferenceControllers.add(new AutoRebootPreferenceController(context));
controllers.add(new PreferenceCategoryController(context, SECURITY_CATEGORY)
.setChildren(securityPreferenceControllers));
controllers.addAll(securityPreferenceControllers);
@@ -0,0 +1,238 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Oliver Scott <olivercscott@gmail.com>
Date: Fri, 2 Oct 2020 12:37:30 -0400
Subject: [PATCH] add bluetooth auto-turn-off setting
---
res/values/arrays.xml | 44 +++++++
res/values/strings.xml | 19 +++
res/xml/connected_devices.xml | 8 ++
.../BluetoothTimeoutPreferenceController.java | 115 ++++++++++++++++++
4 files changed, 186 insertions(+)
create mode 100644 src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index c5df677975d..1a2796ad276 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -18,6 +18,50 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Bluetooth settings. The delay in inactivity before bluetooth is turned off. These are shown in a list dialog. -->
+ <string-array name="bluetooth_timeout_entries">
+ <item>@string/bluetooth_timeout_summary_never</item>
+ <item>@string/bluetooth_timeout_summary_15secs</item>
+ <item>@string/bluetooth_timeout_summary_30secs</item>
+ <item>@string/bluetooth_timeout_summary_1min</item>
+ <item>@string/bluetooth_timeout_summary_2mins</item>
+ <item>@string/bluetooth_timeout_summary_5mins</item>
+ <item>@string/bluetooth_timeout_summary_10mins</item>
+ <item>@string/bluetooth_timeout_summary_30mins</item>
+ <item>@string/bluetooth_timeout_summary_1hour</item>
+ <item>@string/bluetooth_timeout_summary_2hours</item>
+ <item>@string/bluetooth_timeout_summary_4hours</item>
+ <item>@string/bluetooth_timeout_summary_8hours</item>
+ </string-array>
+
+ <!-- Do not translate. -->
+ <string-array name="bluetooth_timeout_values" translatable="false">
+ <!-- Do not translate. -->
+ <item>0</item>
+ <!-- Do not translate. -->
+ <item>15000</item>
+ <!-- Do not translate. -->
+ <item>30000</item>
+ <!-- Do not translate. -->
+ <item>60000</item>
+ <!-- Do not translate. -->
+ <item>120000</item>
+ <!-- Do not translate. -->
+ <item>300000</item>
+ <!-- Do not translate. -->
+ <item>600000</item>
+ <!-- Do not translate. -->
+ <item>1800000</item>
+ <!-- Do not translate. -->
+ <item>3600000</item>
+ <!-- Do not translate. -->
+ <item>7200000</item>
+ <!-- Do not translate. -->
+ <item>14400000</item>
+ <!-- Do not translate. -->
+ <item>28800000</item>
+ </string-array>
+
<!-- Display settings. The delay in inactivity before the screen is turned off. These are shown in a list dialog. -->
<string-array name="screen_timeout_entries">
<item>15 seconds</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b7105938345..cf41793fd42 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -27,6 +27,25 @@
<!-- Used in confirmation dialogs as the action that the user will tap to turn on the feature. [CHAR LIMIT=40]-->
<string name="confirmation_turn_on">Turn on</string>
+ <!-- Connected devices screen, setting option name to change bluetooth timeout -->
+ <string name="bluetooth_timeout">Bluetooth timeout</string>
+
+ <!-- Connected devices screen, setting option summary to change bluetooth timeout -->
+ <string name="bluetooth_timeout_summary">Bluetooth will turn off after <xliff:g id="timeout_description">%1$s</xliff:g> if no devices connected</string>
+ <string name="bluetooth_timeout_summary2">Do not automatically turn off Bluetooth</string>
+ <string name="bluetooth_timeout_summary_never">Never</string>
+ <string name="bluetooth_timeout_summary_15secs">15 seconds</string>
+ <string name="bluetooth_timeout_summary_30secs">30 seconds</string>
+ <string name="bluetooth_timeout_summary_1min">1 minute</string>
+ <string name="bluetooth_timeout_summary_2mins">2 minutes</string>
+ <string name="bluetooth_timeout_summary_5mins">5 minutes</string>
+ <string name="bluetooth_timeout_summary_10mins">10 minutes</string>
+ <string name="bluetooth_timeout_summary_30mins">30 minutes</string>
+ <string name="bluetooth_timeout_summary_1hour">1 hour</string>
+ <string name="bluetooth_timeout_summary_2hours">2 hours</string>
+ <string name="bluetooth_timeout_summary_4hours">4 hours</string>
+ <string name="bluetooth_timeout_summary_8hours">8 hours</string>
+
<!-- Device Info screen. Used for a status item's value when the proper value is not known -->
<string name="device_info_default">Unknown</string>
<!-- String for removal of sensitive info on about, depending on tap -->
diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml
index 95aa8773c68..d66915369e4 100644
--- a/res/xml/connected_devices.xml
+++ b/res/xml/connected_devices.xml
@@ -60,6 +60,14 @@
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_config_bluetooth" />
+ <androidx.preference.ListPreference
+ android:key="bluetooth_timeout"
+ android:title="@string/bluetooth_timeout"
+ android:summary="@string/summary_placeholder"
+ android:entries="@array/bluetooth_timeout_entries"
+ android:entryValues="@array/bluetooth_timeout_values"
+ settings:controller="com.android.settings.bluetooth.BluetoothTimeoutPreferenceController"/>
+
<PreferenceCategory
android:key="previously_connected_devices"
android:title="@string/connected_device_saved_title"
diff --git a/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
new file mode 100644
index 00000000000..244147948aa
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Calyx Institute
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+public class BluetoothTimeoutPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BluetoothTimeoutPrefCtrl";
+
+ public static final int FALLBACK_BLUETOOTH_TIMEOUT_VALUE = 0;
+
+ private final String mBluetoothTimeoutKey;
+
+ protected BluetoothAdapter mBluetoothAdapter;
+
+ public BluetoothTimeoutPreferenceController(Context context, String key) {
+ super(context, key);
+ mBluetoothTimeoutKey = key;
+
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mBluetoothAdapter != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mBluetoothTimeoutKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference timeoutListPreference = (ListPreference) preference;
+ final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.BLUETOOTH_OFF_TIMEOUT, FALLBACK_BLUETOOTH_TIMEOUT_VALUE);
+ timeoutListPreference.setValue(String.valueOf(currentTimeout));
+ updateTimeoutPreferenceDescription(timeoutListPreference,
+ Long.parseLong(timeoutListPreference.getValue()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ try {
+ long value = Long.parseLong((String) newValue);
+ Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.BLUETOOTH_OFF_TIMEOUT, value);
+ updateTimeoutPreferenceDescription((ListPreference) preference, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist bluetooth timeout setting", e);
+ }
+ return true;
+ }
+
+ public static CharSequence getTimeoutDescription(
+ long currentTimeout, CharSequence[] entries, CharSequence[] values) {
+ if (currentTimeout < 0 || entries == null || values == null
+ || values.length != entries.length) {
+ return null;
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout == timeout) {
+ return entries[i];
+ }
+ }
+ return null;
+ }
+
+ private void updateTimeoutPreferenceDescription(ListPreference preference,
+ long currentTimeout) {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ final CharSequence timeoutDescription = getTimeoutDescription(
+ currentTimeout, entries, values);
+ String summary = "";
+ if (timeoutDescription != null) {
+ if (currentTimeout != 0)
+ summary = mContext.getString(R.string.bluetooth_timeout_summary, timeoutDescription);
+ else
+ summary = mContext.getString(R.string.bluetooth_timeout_summary2);
+ }
+ preference.setSummary(summary);
+ }
+}
@@ -0,0 +1,238 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Oliver Scott <olivercscott@gmail.com>
Date: Thu, 25 Feb 2021 11:31:14 -0500
Subject: [PATCH] add Wi-Fi timeout feature
---
res/values/arrays.xml | 44 +++++++
res/values/strings.xml | 19 +++
res/xml/wifi_configure_settings.xml | 8 ++
.../wifi/WifiTimeoutPreferenceController.java | 115 ++++++++++++++++++
4 files changed, 186 insertions(+)
create mode 100644 src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 1a2796ad276..9344280a905 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -62,6 +62,50 @@
<item>28800000</item>
</string-array>
+ <!-- Wifi settings. The delay in inactivity before wifi is turned off. These are shown in a list dialog. -->
+ <string-array name="wifi_timeout_entries">
+ <item>@string/wifi_timeout_summary_never</item>
+ <item>@string/wifi_timeout_summary_15secs</item>
+ <item>@string/wifi_timeout_summary_30secs</item>
+ <item>@string/wifi_timeout_summary_1min</item>
+ <item>@string/wifi_timeout_summary_2mins</item>
+ <item>@string/wifi_timeout_summary_5mins</item>
+ <item>@string/wifi_timeout_summary_10mins</item>
+ <item>@string/wifi_timeout_summary_30mins</item>
+ <item>@string/wifi_timeout_summary_1hour</item>
+ <item>@string/wifi_timeout_summary_2hours</item>
+ <item>@string/wifi_timeout_summary_4hours</item>
+ <item>@string/wifi_timeout_summary_8hours</item>
+ </string-array>
+
+ <!-- Do not translate. -->
+ <string-array name="wifi_timeout_values" translatable="false">
+ <!-- Do not translate. -->
+ <item>0</item>
+ <!-- Do not translate. -->
+ <item>15000</item>
+ <!-- Do not translate. -->
+ <item>30000</item>
+ <!-- Do not translate. -->
+ <item>60000</item>
+ <!-- Do not translate. -->
+ <item>120000</item>
+ <!-- Do not translate. -->
+ <item>300000</item>
+ <!-- Do not translate. -->
+ <item>600000</item>
+ <!-- Do not translate. -->
+ <item>1800000</item>
+ <!-- Do not translate. -->
+ <item>3600000</item>
+ <!-- Do not translate. -->
+ <item>7200000</item>
+ <!-- Do not translate. -->
+ <item>14400000</item>
+ <!-- Do not translate. -->
+ <item>28800000</item>
+ </string-array>
+
<!-- Display settings. The delay in inactivity before the screen is turned off. These are shown in a list dialog. -->
<string-array name="screen_timeout_entries">
<item>15 seconds</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf41793fd42..1c3f864c956 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -46,6 +46,25 @@
<string name="bluetooth_timeout_summary_4hours">4 hours</string>
<string name="bluetooth_timeout_summary_8hours">8 hours</string>
+ <!-- screen, setting option name to change wifi timeout -->
+ <string name="wifi_timeout">Turn off Wi-Fi automatically</string>
+
+ <!-- screen, setting option summary to change wifi timeout -->
+ <string name="wifi_timeout_summary">Wi-Fi will turn off after <xliff:g id="timeout_description">%1$s</xliff:g> if no network connected</string>
+ <string name="wifi_timeout_summary2">Disabled</string>
+ <string name="wifi_timeout_summary_never">Never</string>
+ <string name="wifi_timeout_summary_15secs">15 seconds</string>
+ <string name="wifi_timeout_summary_30secs">30 seconds</string>
+ <string name="wifi_timeout_summary_1min">1 minute</string>
+ <string name="wifi_timeout_summary_2mins">2 minutes</string>
+ <string name="wifi_timeout_summary_5mins">5 minutes</string>
+ <string name="wifi_timeout_summary_10mins">10 minutes</string>
+ <string name="wifi_timeout_summary_30mins">30 minutes</string>
+ <string name="wifi_timeout_summary_1hour">1 hour</string>
+ <string name="wifi_timeout_summary_2hours">2 hours</string>
+ <string name="wifi_timeout_summary_4hours">4 hours</string>
+ <string name="wifi_timeout_summary_8hours">8 hours</string>
+
<!-- Device Info screen. Used for a status item's value when the proper value is not known -->
<string name="device_info_default">Unknown</string>
<!-- String for removal of sensitive info on about, depending on tap -->
diff --git a/res/xml/wifi_configure_settings.xml b/res/xml/wifi_configure_settings.xml
index 5866c5898d9..663e3109d76 100644
--- a/res/xml/wifi_configure_settings.xml
+++ b/res/xml/wifi_configure_settings.xml
@@ -26,6 +26,14 @@
android:summary="@string/wifi_wakeup_summary"
settings:controller="com.android.settings.wifi.WifiWakeupPreferenceController"/>
+ <ListPreference
+ android:key="wifi_timeout"
+ android:title="@string/wifi_timeout"
+ android:summary="@string/wifi_timeout_summary"
+ android:entries="@array/wifi_timeout_entries"
+ android:entryValues="@array/wifi_timeout_values"
+ settings:controller="com.android.settings.wifi.WifiTimeoutPreferenceController"/>
+
<SwitchPreferenceCompat
android:key="notify_open_networks"
android:title="@string/wifi_notify_open_networks"
diff --git a/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
new file mode 100644
index 00000000000..7116c905190
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Calyx Institute
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+public class WifiTimeoutPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "WifiTimeoutPrefCtrl";
+
+ public static final int FALLBACK_WIFI_TIMEOUT_VALUE = 0;
+
+ private final String mWifiTimeoutKey;
+
+ protected WifiManager mWifiManager;
+
+ public WifiTimeoutPreferenceController(Context context, String key) {
+ super(context, key);
+ mWifiTimeoutKey = key;
+
+ mWifiManager = context.getSystemService(WifiManager.class);
+ if (mWifiManager == null) {
+ Log.e(TAG, "Wifi is not supported on this device");
+ return;
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mWifiManager != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mWifiTimeoutKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference timeoutListPreference = (ListPreference) preference;
+ final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_OFF_TIMEOUT, FALLBACK_WIFI_TIMEOUT_VALUE);
+ timeoutListPreference.setValue(String.valueOf(currentTimeout));
+ updateTimeoutPreferenceDescription(timeoutListPreference,
+ Long.parseLong(timeoutListPreference.getValue()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ try {
+ long value = Long.parseLong((String) newValue);
+ Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.WIFI_OFF_TIMEOUT, value);
+ updateTimeoutPreferenceDescription((ListPreference) preference, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist wifi timeout setting", e);
+ }
+ return true;
+ }
+
+ public static CharSequence getTimeoutDescription(
+ long currentTimeout, CharSequence[] entries, CharSequence[] values) {
+ if (currentTimeout < 0 || entries == null || values == null
+ || values.length != entries.length) {
+ return null;
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout == timeout) {
+ return entries[i];
+ }
+ }
+ return null;
+ }
+
+ private void updateTimeoutPreferenceDescription(ListPreference preference,
+ long currentTimeout) {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ final CharSequence timeoutDescription = getTimeoutDescription(
+ currentTimeout, entries, values);
+ String summary = "";
+ if (timeoutDescription != null) {
+ if (currentTimeout != 0)
+ summary = mContext.getString(R.string.wifi_timeout_summary, timeoutDescription);
+ else
+ summary = mContext.getString(R.string.wifi_timeout_summary2);
+ }
+ preference.setSummary(summary);
+ }
+}
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""Port AXP auto-reboot, Wi-Fi/Bluetooth timeouts, and sensitive QS tiles.
AXP Patch.sh order @ 7b735844, applied with --fuzz=0:
0014-Automatic_Reboot.patch
0015-System_Server_Extensions.patch
0015-WiFi_Timeout.patch
0015-Bluetooth_Timeout.patch
0015-Bluetooth_Timeout-Fix.patch
0041-tile_restrictions.patch
A16 adaptations after the exact hunks:
* privapp-permissions gained READ_SYSTEM_GRAMMATICAL_GENDER, so the REBOOT
line is inserted between REAL_GET_TASKS and REQUEST_NETWORK_SCORES.
* onKeyguardExitFinished() now dismisses the keyguard interactor before the
closing brace. The AXP cancel stays immediately after sendUserPresentBroadcast().
* QSTileImpl no longer imports QsInCompose, so KeyguardStateController is
imported after QSLogger.
"""
from __future__ import annotations
import os
import pathlib
import subprocess
import sys
TREE = pathlib.Path(os.environ.get("SUNFISH_A16_TREE", "/srv/rom/src/lineage"))
PATCH_DIR = pathlib.Path(__file__).resolve().parent / "a15-src" / "frameworks_base"
BASE = TREE / "frameworks/base"
XML = BASE / "data/etc/com.android.systemui.xml"
KEYGUARD = (
BASE / "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
)
QSTILE = BASE / "packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java"
XML_OLD = """ <permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
"""
XML_NEW = """ <permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
"""
KEYGUARD_OLD = """ adjustStatusBarLocked();
sendUserPresentBroadcast();
if (!KeyguardWmStateRefactor.isEnabled()) {
mKeyguardInteractor.dismissKeyguard();
}
"""
KEYGUARD_NEW = """ adjustStatusBarLocked();
sendUserPresentBroadcast();
cancelDoRebootForOwnerAfterTimeoutIfEnabled();
if (!KeyguardWmStateRefactor.isEnabled()) {
mKeyguardInteractor.dismissKeyguard();
}
"""
IMPORT_OLD = "import com.android.systemui.qs.logging.QSLogger;\n"
IMPORT_NEW = (
"import com.android.systemui.qs.logging.QSLogger;\n"
"import com.android.systemui.statusbar.policy.KeyguardStateController;\n"
)
def replace_once(path: pathlib.Path, old: str, new: str) -> None:
text = path.read_text()
if new in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def apply_patch(name: str) -> None:
patch = PATCH_DIR / name
if not patch.is_file():
sys.exit(f"missing {patch}")
result = subprocess.run(
[
"patch",
"-p1",
"--forward",
"--fuzz=0",
"--batch",
"--no-backup-if-mismatch",
"-d",
str(BASE),
"-i",
str(patch),
],
check=False,
text=True,
capture_output=True,
)
sys.stdout.write(result.stdout)
sys.stdout.write(result.stderr)
if result.returncode != 0 and "FAILED" in result.stderr + result.stdout:
print(f"patch_incomplete: {name}")
elif result.returncode != 0:
print(f"patch_exit_{result.returncode}: {name}")
def clean_rejects() -> None:
for path in (XML, KEYGUARD, QSTILE):
for suffix in (".rej", ".orig"):
extra = path.with_name(path.name + suffix)
if extra.exists():
extra.unlink()
def main() -> None:
apply_patch("0014-Automatic_Reboot.patch")
replace_once(XML, XML_OLD, XML_NEW)
replace_once(KEYGUARD, KEYGUARD_OLD, KEYGUARD_NEW)
for name in (
"0015-System_Server_Extensions.patch",
"0015-WiFi_Timeout.patch",
"0015-Bluetooth_Timeout.patch",
"0015-Bluetooth_Timeout-Fix.patch",
"0041-tile_restrictions.patch",
):
apply_patch(name)
replace_once(QSTILE, IMPORT_OLD, IMPORT_NEW)
clean_rejects()
required = [
"settings_reboot_after_timeout",
"android.permission.REBOOT",
"cancelDoRebootForOwnerAfterTimeoutIfEnabled();",
"class SystemServerExt",
"class WifiAutoOff",
"class BluetoothAutoOff",
"import android.bluetooth.BluetoothProfile;",
"WIFI_OFF_TIMEOUT",
"BLUETOOTH_OFF_TIMEOUT",
"isAllowedWhenLocked",
"KeyguardStateController",
]
blob = "\n".join(
path.read_text()
for path in (
BASE / "core/java/android/provider/Settings.java",
XML,
KEYGUARD,
BASE / "services/core/java/com/android/server/ext/SystemServerExt.java",
BASE / "services/core/java/com/android/server/ext/WifiAutoOff.java",
BASE / "services/core/java/com/android/server/ext/BluetoothAutoOff.java",
QSTILE,
)
)
missing = [item for item in required if item not in blob]
if missing:
sys.exit("missing after apply: " + ", ".join(missing))
print("done: SUNFISH_A16_009_A15_REBOOT_TIMEOUTS")
if __name__ == "__main__":
main()
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Settings UI for the AXP reboot and radio timeouts.
Apply after apply_009_a15_reboot_timeouts.py. Order matches AXP:
bluetooth arrays land before the Wi-Fi arrays, which insert after them.
0006 needs fuzz 2. A16's strings.xml has an empty line where the A15
patch has a single space, and connected_devices.xml shifted by a few
lines. The preference still lands on the connected-devices screen.
"""
from __future__ import annotations
import os
import pathlib
import subprocess
import sys
TREE = pathlib.Path(os.environ.get("SUNFISH_A16_TREE", "/srv/rom/src/lineage"))
PATCH_DIR = pathlib.Path(__file__).resolve().parent / "a15-src" / "settings"
SETTINGS = TREE / "packages/apps/Settings"
MARKERS = (
"res/xml/security_dashboard_settings.xml",
"res/xml/connected_devices.xml",
"res/xml/wifi_configure_settings.xml",
"res/values/arrays.xml",
"res/values/strings.xml",
)
def already_applied() -> bool:
try:
blob = "\n".join((SETTINGS / name).read_text() for name in MARKERS)
except OSError:
return False
needles = (
'android:key="auto_reboot"',
'android:key="bluetooth_timeout"',
'android:key="wifi_timeout"',
'name="auto_reboot_entries"',
'name="bluetooth_timeout_entries"',
'name="wifi_timeout_entries"',
)
return all(needle in blob for needle in needles)
def apply_patch(name: str, fuzz: str) -> None:
result = subprocess.run(
[
"patch",
"-p1",
"--forward",
f"--fuzz={fuzz}",
"--batch",
"--no-backup-if-mismatch",
"-d",
str(SETTINGS),
"-i",
str(PATCH_DIR / name),
],
check=False,
text=True,
capture_output=True,
)
sys.stdout.write(result.stdout)
sys.stdout.write(result.stderr)
for path in SETTINGS.rglob("*.rej"):
path.unlink()
if "FAILED" in result.stdout + result.stderr and not already_applied():
sys.exit(f"failed: {name}")
def main() -> None:
if already_applied():
print("done: SUNFISH_A16_009_A15_SETTINGS_TIMEOUTS")
return
apply_patch("0005-Automatic_Reboot.patch", "0")
apply_patch("0006-Bluetooth_Timeout.patch", "2")
apply_patch("0007-WiFi_Timeout.patch", "0")
if not already_applied():
sys.exit("missing settings timeout markers")
print("done: SUNFISH_A16_009_A15_SETTINGS_TIMEOUTS")
if __name__ == "__main__":
main()
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Port four AXP LOS 22.2 userspace changes onto LOS 23 / Android 16.
Sources, Patch.sh @ 7b735844 (AXP A15):
android_frameworks_base/0004-Fingerprint_Lockout.patch
android_frameworks_base/0007-Always_Restict_Serial.patch
android_frameworks_base/0042-minimal_screenshot_exif.patch
android_bootable_recovery/0001-No_SerialNum_Restrictions.patch
Sunfish fingerprint HAL is HIDL 2.2, and HidlToAidlSensorAdapter still uses
LockoutFrameworkImpl, so the AXP constant change is the live path.
A16 adaptations, behavior unchanged:
* CheckPackageMetadata returns bool, so a serial constraint returns false.
The A15 patch returned INSTALL_ERROR, which does not compile here.
* Screenshot EXIF drops the now-unused Build and DateTimeFormatter imports.
A16 ErrorProne treats unused imports as errors.
Already satisfied on this tree, so this script does not touch them:
* 0028-Remove_Legacy_Package_Query: A16 already removed the
MATCH_UNINSTALLED_PACKAGES / MATCH_ANY_USER branch.
* system/core 0002-ptrace_scope: apply_009_yama_ptrace.py already writes
persist.native_debug.
"""
from __future__ import annotations
import os
import pathlib
import sys
TREE = pathlib.Path(os.environ.get("SUNFISH_A16_TREE", "/srv/rom/src/lineage"))
LOCKOUT = (
TREE
/ "frameworks/base/services/core/java/com/android/server/biometrics"
/ "sensors/fingerprint/hidl/LockoutFrameworkImpl.java"
)
AMS = (
TREE
/ "frameworks/base/services/core/java/com/android/server/am"
/ "ActivityManagerService.java"
)
EXIF = (
TREE
/ "frameworks/base/packages/SystemUI/src/com/android/systemui"
/ "screenshot/ImageExporter.java"
)
RECOVERY = TREE / "bootable/recovery/install/install.cpp"
LOCKOUT_OLD = "private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;"
LOCKOUT_NEW = "private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 5;"
SERIAL_OLD = """ // We deprecated Build.SERIAL and it is not accessible to
// Instant Apps and target APIs higher than O MR1. Since access to the serial
// is now behind a permission we push down the value.
final String buildSerial = (!appInfo.isInstantApp()
&& appInfo.targetSdkVersion < Build.VERSION_CODES.P)
? sTheRealBuildSerial : Build.UNKNOWN;
"""
SERIAL_NEW = """ // AXP/GrapheneOS 0007: deprecated Build.SERIAL stays UNKNOWN.
// Callers use Build.getSerial(), which requires READ_PHONE_STATE.
final String buildSerial = Build.UNKNOWN;
"""
EXIF_OLD = """ exif.setAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID, uniqueId.toString());
exif.setAttribute(ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY);
exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width));
exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height));
String dateTime = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(captureTime);
String subSec = DateTimeFormatter.ofPattern("SSS").format(captureTime);
String timeZone = DateTimeFormatter.ofPattern("xxx").format(captureTime);
exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTime);
exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subSec);
exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, timeZone);
"""
EXIF_NEW = """ // AXP/Calyx 0042: width and height only. No OS name, unique id, or time.
exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width));
exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height));
"""
RECOVERY_OLD = """ // We allow the package to not have any serialno; and we also allow it to carry multiple serial
// numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
// verification if the device's serialno doesn't match any of these carried numbers.
auto pkg_serial_no = get_value(metadata, "serialno");
if (!pkg_serial_no.empty()) {
auto device_serial_no = android::base::GetProperty("ro.serialno", "");
bool serial_number_match = false;
for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
if (device_serial_no == android::base::Trim(number)) {
serial_number_match = true;
}
}
if (!serial_number_match) {
LOG(ERROR) << "Package is for serial " << pkg_serial_no;
return false;
}
} else if (ota_type == OtaType::BRICK) {
"""
RECOVERY_NEW = """ // AXP/GrapheneOS 0001: any serialno constraint is rejected.
// A16 CheckPackageMetadata returns bool; A15 returned INSTALL_ERROR.
auto pkg_serial_no = get_value(metadata, "serialno");
if (!pkg_serial_no.empty()) {
LOG(ERROR) << "Serial number constraint not permitted: " << pkg_serial_no;
return false;
} else if (ota_type == OtaType::BRICK) {
"""
def replace_once(path: pathlib.Path, old: str, new: str) -> None:
text = path.read_text()
if new in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def drop_import(text: str, import_line: str, still_used: str) -> str:
if import_line not in text:
return text
body = text.replace(import_line + "\n", "", 1)
if still_used in body:
return text
return body
def apply_exif() -> None:
text = EXIF.read_text()
if EXIF_NEW in text:
updated = text
elif EXIF_OLD not in text:
sys.exit(f"anchor missing in {EXIF}")
else:
updated = text.replace(EXIF_OLD, EXIF_NEW, 1)
updated = drop_import(updated, "import android.os.Build;", "Build.")
updated = drop_import(
updated, "import java.time.format.DateTimeFormatter;", "DateTimeFormatter"
)
EXIF.write_text(updated)
def main() -> None:
replace_once(LOCKOUT, LOCKOUT_OLD, LOCKOUT_NEW)
replace_once(AMS, SERIAL_OLD, SERIAL_NEW)
apply_exif()
replace_once(RECOVERY, RECOVERY_OLD, RECOVERY_NEW)
print("done: SUNFISH_A16_009_A15_SMALL")
if __name__ == "__main__":
main()
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""009: AXP ship identity on LOS 23.2.
- AXP Patch.sh (LineageOS-22.2:405) replaces LINEAGE_BUILDTYPE := UNOFFICIAL
with its own build type in vendor/lineage/config/*.mk. Same here, with PRO
(GOLDEN ro.lineage.releasetype=PRO).
- Drops the old TARGET_UNOFFICIAL_BUILD_ID insert from lineage_sunfish.mk.
- Vendor SPL stays the LOS 23.2 2023-09-05 (AXP GOLDEN ships 2023-08-05).
- build/make/core/config.mk only tags release-keys when the default cert is
under vendor/lineage-priv/. Copies the project certs there (same keys, so
an existing install updates without a wipe).
"""
from __future__ import annotations
import pathlib
import shutil
import sys
TREE = pathlib.Path("/srv/rom/src/lineage")
SRC_CERTS = TREE / "vendor/sunfish-a16/certs"
DST_CERTS = TREE / "vendor/lineage-priv/keys"
OLD_ID = (
"# sunfish-a16: must precede vendor/lineage version.mk (via inherit below)\n"
"TARGET_UNOFFICIAL_BUILD_ID := AXP-Resurection-Pro\n\n"
)
# LOS 23.2 writes 2023-09-05. AXP GOLDEN uses 2023-08-05, but keymaster key
# blobs on an existing install are bound to the vendor patch level and refuse
# a rollback: vdc cryptfs init_user0 fails and recovery asks for a wipe
# (LAB 2026-10-07 17:15 build). Never lower the vendor SPL on an upgrade.
VENDOR_SPL_LOS = (
"# Set Vendor SPL to match platform\n"
"VENDOR_SECURITY_PATCH = 2023-09-05\n"
)
VENDOR_SPL_ROLLBACK = (
"# Stock sunfish vendor SPL (TQ3A.230805), same as AXP GOLDEN.\n"
"# System PLATFORM_SECURITY_PATCH stays the LOS 23.2 ASB.\n"
"VENDOR_SECURITY_PATCH = 2023-08-05\n"
)
def main() -> None:
version = TREE / "vendor/lineage/config/version.mk"
text = version.read_text()
if "LINEAGE_BUILDTYPE := PRO\n" not in text:
old = " LINEAGE_BUILDTYPE := UNOFFICIAL\n"
if old not in text:
sys.exit(f"anchor missing in {version}")
version.write_text(text.replace(old, " LINEAGE_BUILDTYPE := PRO\n", 1))
device_mk = TREE / "device/google/sunfish/lineage_sunfish.mk"
text = device_mk.read_text()
if OLD_ID in text:
device_mk.write_text(text.replace(OLD_ID, "", 1))
if "TARGET_UNOFFICIAL_BUILD_ID" in device_mk.read_text():
sys.exit(f"TARGET_UNOFFICIAL_BUILD_ID still set in {device_mk}")
common = TREE / "device/google/sunfish/device-common.mk"
text = common.read_text()
if VENDOR_SPL_ROLLBACK in text:
text = text.replace(VENDOR_SPL_ROLLBACK, VENDOR_SPL_LOS, 1)
common.write_text(text)
if VENDOR_SPL_LOS not in text:
sys.exit(f"vendor SPL is not 2023-09-05 in {common}")
if not (SRC_CERTS / "releasekey.pk8").is_file():
sys.exit(f"missing {SRC_CERTS}/releasekey.pk8")
DST_CERTS.mkdir(parents=True, exist_ok=True)
for src in SRC_CERTS.iterdir():
if src.name.startswith("avb"):
continue
dst = DST_CERTS / src.name
if dst.is_symlink() or dst.exists():
dst.unlink()
if src.is_symlink():
dst.symlink_to(src.readlink())
else:
shutil.copy2(src, dst)
print("axp identity applied")
if __name__ == "__main__":
main()
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Hide /proc/cmdline from unprivileged processes.
AOSP and GOLDEN's init do this in first-stage init, immediately after
mounting proc:
CHECKCALL(chmod("/proc/cmdline", 0440));
The failure text is exactly `chmod("/proc/cmdline", 0440) failed`, which
is present in GOLDEN's /system/bin/init and absent from the LOS 23.2 init
that left the file mode 0444. LOS 23.2 already chmod's /proc/bootconfig
the same way. init.rc only chowns cmdline to root:radio.
"""
from __future__ import annotations
import os
import pathlib
import sys
TREE = pathlib.Path(os.environ.get("ANDROID_BUILD_TOP", "/srv/rom/src/lineage"))
INIT = TREE / "system/core/init/first_stage_init.cpp"
OLD = """#undef MAKE_STR
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
"""
NEW = """#undef MAKE_STR
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
"""
def main() -> None:
text = INIT.read_text()
if NEW in text:
print(f"already applied: {INIT}")
return
if OLD not in text:
sys.exit(f"cmdline chmod anchor missing in {INIT}")
INIT.write_text(text.replace(OLD, NEW, 1))
print(f"patched cmdline mode: {INIT}")
if __name__ == "__main__":
main()
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Drop Google carrier apps and the AOSP IMS entitlement/RCS packages.
inherit-product merges PRODUCT_PACKAGES per makefile, so a filter-out never
sees names added by sunfish-vendor.mk. Remove the package lines where they
are added. CarrierSettings/*.pb copy rules stay: their tokens are paths, not
the CarrierSettings module. Qualcomm radio packages stay (ims, atfwd,
qcrilmsgtunnel, uceShimService, EuiccSupportPixel).
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
TARGETS: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
(
"vendor/google/sunfish/sunfish-vendor.mk",
(
"CarrierServices",
"CarrierSettings",
"CarrierWifi",
"CarrierSetup",
"RilConfigService",
"grilservice",
),
(
"EuiccSupportPixel",
"EuiccSupportPixelPermissions",
"ims",
"QtiTelephonyService",
"atfwd",
"qcrilmsgtunnel",
"uceShimService",
),
),
(
"device/google/sunfish/device-lineage.mk",
("PresencePolling", "RcsService"),
("GoogleParts",),
),
(
"build/make/target/product/telephony_product.mk",
("ImsServiceEntitlement",),
("Dialer",),
),
)
_RCS_STUB = "# RCS\nPRODUCT_PACKAGES += \\\n\n"
def _token(line: str) -> str:
return line.strip().rstrip("\\").strip()
def _has(text: str, name: str) -> bool:
return any(_token(line) == name for line in text.splitlines())
def apply_file(path: Path, packages: tuple[str, ...], keep: tuple[str, ...]) -> str:
text = path.read_text()
for name in keep:
if not _has(text, name):
raise SystemExit(f"FAIL: {path} lost {name}")
removed = {name: 0 for name in packages}
kept: list[str] = []
for line in text.splitlines(keepends=True):
token = _token(line)
if token in removed:
removed[token] += 1
continue
kept.append(line)
if all(count == 0 for count in removed.values()):
return "already"
if any(count != 1 for count in removed.values()):
raise SystemExit(f"FAIL: unexpected removals in {path}: {removed}")
new = "".join(kept)
if path.name == "device-lineage.mk":
new = new.replace(_RCS_STUB, "", 1)
for name in keep:
if not _has(new, name):
raise SystemExit(f"FAIL: edit would drop {name} from {path}")
for name in packages:
if _has(new, name):
raise SystemExit(f"FAIL: {name} still listed in {path}")
path.write_text(new)
return "dropped"
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
results: list[str] = []
for rel, packages, keep in TARGETS:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
state = apply_file(path, packages, keep)
results.append(f"{state} {rel}")
print("OK: " + "; ".join(results))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Drop the Google eUICC LPA from the sunfish vendor makefile.
inherit-product merges PRODUCT_PACKAGES per makefile, so a filter-out in
apps.mk never sees the names added by sunfish-vendor.mk. A Soong phony
does not set PACKAGES.*.OVERRIDES. The 20261009T073753Z and 20261009T090831Z
zips still installed both APKs. Remove the two entries where they are added.
EuiccSupportPixel and EuiccSupportPixelPermissions stay.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
PACKAGES = ("EuiccGoogle", "EuiccGoogleOverlay")
KEEP = ("EuiccSupportPixel", "EuiccSupportPixelPermissions")
def _package_line(line: str) -> str | None:
stripped = line.strip().rstrip("\\").strip()
if stripped in PACKAGES:
return stripped
return None
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
mk = top / "vendor/google/sunfish/sunfish-vendor.mk"
if not mk.is_file():
print(f"FAIL: missing {mk}", file=sys.stderr)
return 1
text = mk.read_text()
for name in KEEP:
if name not in text:
print(f"FAIL: {mk} lost {name}", file=sys.stderr)
return 1
removed = {name: 0 for name in PACKAGES}
kept: list[str] = []
for line in text.splitlines(keepends=True):
hit = _package_line(line)
if hit is None:
kept.append(line)
continue
removed[hit] += 1
if all(count == 0 for count in removed.values()):
print(f"OK: already applied ({mk})")
return 0
if any(count != 1 for count in removed.values()):
print(f"FAIL: unexpected removals {removed}", file=sys.stderr)
return 1
new = "".join(kept)
for name in KEEP:
if name not in new:
print(f"FAIL: edit would drop {name}", file=sys.stderr)
return 1
for name in PACKAGES:
if f" {name} \\" in new or f" {name}\n" in new:
print(f"FAIL: {name} still listed", file=sys.stderr)
return 1
mk.write_text(new)
print(f"OK: dropped {', '.join(PACKAGES)} from {mk}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""Drop commands present on LAB and absent on GOLDEN.
Keeps the GPS binaries loc_launcher, lowi-server and xtra-daemon.
bpftool, alloctop and apexd-lsof.sh are not product packages: bpfloader,
dumpstate and apexd require them. Remove only those required entries.
pbtombstone stays in PRODUCT_HOST_PACKAGES; only the system copy goes.
Qualcomm diag binaries and imsrcsd go. imsdatadaemon and the other IMS
call daemons stay.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
MAKE_DROPS: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
(
"vendor/google/sunfish/sunfish-vendor.mk",
(
"cnss_diag",
"diag_klog",
"diag_mdlog",
"diag_socket_log",
"diag_uart_log",
"ssr_diag",
"test_diag",
"imsrcsd",
),
(
"loc_launcher",
"lowi-server",
"xtra-daemon",
"cnss-daemon",
"imsdatadaemon",
"imsqmidaemon",
"ims_rtp_daemon",
),
),
(
"device/google/sunfish/device-lineage.mk",
("disable_configstore",),
("chre_daemon_msm",),
),
)
HOST_SAFE = {"pbtombstone"}
BP_LINES: tuple[tuple[str, str], ...] = (
("system/bpf/loader/Android.bp", '"bpftool",'),
("frameworks/native/cmds/dumpstate/Android.bp", '"alloctop",'),
("system/apex/apexd/Android.bp", 'required: ["apexd-lsof.sh"],'),
)
def _token(line: str) -> str:
return line.strip().rstrip("\\").strip()
def _has(text: str, name: str) -> bool:
return any(_token(line) == name for line in text.splitlines())
def _assignment(line: str, current: str | None) -> str | None:
stripped = line.strip()
if stripped.startswith("PRODUCT_HOST_PACKAGES") and "+=" in stripped:
return "PRODUCT_HOST_PACKAGES"
if stripped.startswith("PRODUCT_PACKAGES") and "+=" in stripped:
return "PRODUCT_PACKAGES"
return current
def apply_make(path: Path, packages: tuple[str, ...], keep: tuple[str, ...]) -> str:
text = path.read_text()
for name in keep:
if not _has(text, name):
raise SystemExit(f"FAIL: {path} lost {name}")
removed = {name: 0 for name in packages}
rc_removed = 0
kept: list[str] = []
assignment: str | None = None
for line in text.splitlines(keepends=True):
assignment = _assignment(line, assignment)
token = _token(line)
if token in removed and not (token in HOST_SAFE and assignment == "PRODUCT_HOST_PACKAGES"):
removed[token] += 1
continue
if "imsrcsd.rc:" in line:
rc_removed += 1
continue
kept.append(line)
pending = [name for name, count in removed.items() if count == 0]
if pending and any(count for count in removed.values()):
raise SystemExit(f"FAIL: partial removal in {path}: {removed}")
if not any(count for count in removed.values()) and rc_removed == 0:
return "already"
if any(count != 1 for count in removed.values()):
raise SystemExit(f"FAIL: unexpected removals in {path}: {removed}")
if "imsrcsd" in packages and rc_removed != 1:
raise SystemExit(f"FAIL: imsrcsd.rc removals={rc_removed} in {path}")
new = "".join(kept)
for name in keep:
if not _has(new, name):
raise SystemExit(f"FAIL: edit would drop {name} from {path}")
for name in packages:
if _has(new, name):
raise SystemExit(f"FAIL: {name} still listed in {path}")
if "imsrcsd.rc:" in new:
raise SystemExit(f"FAIL: imsrcsd.rc still copied from {path}")
path.write_text(new)
return "dropped"
def apply_pbtombstone(path: Path) -> str:
text = path.read_text()
if not _has(text, "logcat") or not _has(text, "fastboot"):
raise SystemExit(f"FAIL: {path} lost logcat or fastboot")
removed = 0
kept: list[str] = []
assignment: str | None = None
for line in text.splitlines(keepends=True):
assignment = _assignment(line, assignment)
token = _token(line)
if token == "pbtombstone" and assignment == "PRODUCT_PACKAGES":
removed += 1
continue
kept.append(line)
if removed == 0:
if not any(
_token(line) == "pbtombstone" and True
for line in text.splitlines()
):
raise SystemExit(f"FAIL: {path} has no pbtombstone at all")
return "already"
if removed != 1:
raise SystemExit(f"FAIL: pbtombstone system removals={removed}")
new = "".join(kept)
host_hits = 0
assignment = None
for line in new.splitlines():
assignment = _assignment(line, assignment)
if _token(line) == "pbtombstone" and assignment == "PRODUCT_HOST_PACKAGES":
host_hits += 1
if host_hits != 1:
raise SystemExit(f"FAIL: host pbtombstone count={host_hits}")
path.write_text(new)
return "dropped"
def apply_bp(path: Path, needle: str) -> str:
text = path.read_text()
hits = [line for line in text.splitlines() if line.strip() == needle]
if not hits:
return "already"
if len(hits) != 1:
raise SystemExit(f"FAIL: {needle} appears {len(hits)} times in {path}")
new = "".join(
line for line in text.splitlines(keepends=True) if line.strip() != needle
)
if needle in new:
raise SystemExit(f"FAIL: {needle} still present in {path}")
path.write_text(new)
return "dropped"
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
results: list[str] = []
for rel, packages, keep in MAKE_DROPS:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
results.append(f"{apply_make(path, packages, keep)} {rel}")
pb = top / "build/make/target/product/base_system.mk"
if not pb.is_file():
print(f"FAIL: missing {pb}", file=sys.stderr)
return 1
results.append(f"{apply_pbtombstone(pb)} {pb.relative_to(top)}")
for rel, needle in BP_LINES:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
results.append(f"{apply_bp(path, needle)} {rel}")
print("OK: " + "; ".join(results))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Drop Lineage tools that AXP.OS PRO sunfish does not ship.
GOLDEN has no /system_ext/bin/{bash,htop,nano,unrar} and no
/system/addon.d/50-lineage.sh. It does ship curl and vim, so those tokens
stay. Qualcomm diag binaries are a separate change.
inherit-product merges PRODUCT_PACKAGES per makefile, so filter-out cannot
remove names added here. Delete the exact package-token lines.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
TARGETS: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
(
"vendor/lineage/config/common.mk",
("bash", "htop", "nano", "50-lineage.sh"),
("curl", "getcap", "setcap", "vim", "nano_recovery"),
),
(
"vendor/lineage/config/common_mobile_full.mk",
("unrar",),
("zstd",),
),
)
def _token(line: str) -> str:
return line.strip().rstrip("\\").strip()
def _has(text: str, name: str) -> bool:
return any(_token(line) == name for line in text.splitlines())
def apply_file(path: Path, packages: tuple[str, ...], keep: tuple[str, ...]) -> str:
text = path.read_text()
for name in keep:
if not _has(text, name):
raise SystemExit(f"FAIL: {path} lost {name}")
removed = {name: 0 for name in packages}
kept: list[str] = []
for line in text.splitlines(keepends=True):
token = _token(line)
if token in removed:
removed[token] += 1
continue
kept.append(line)
if all(count == 0 for count in removed.values()):
return "already"
if any(count != 1 for count in removed.values()):
raise SystemExit(f"FAIL: unexpected removals in {path}: {removed}")
new = "".join(kept)
for name in keep:
if not _has(new, name):
raise SystemExit(f"FAIL: edit would drop {name} from {path}")
for name in packages:
if _has(new, name):
raise SystemExit(f"FAIL: {name} still listed in {path}")
path.write_text(new)
return "dropped"
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
results: list[str] = []
for rel, packages, keep in TARGETS:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
results.append(f"{apply_file(path, packages, keep)} {rel}")
print("OK: " + "; ".join(results))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,576 @@
#!/usr/bin/env python3
"""009: SUPL privacy, plus an optional stop for LPP / XTRA / IZat.
- Never send IMSI or MSISDN to a SUPL server (AXP/Divest), including emergency.
LOS 23 already skips that outside an emergency call.
- Settings toggle "Force disable SUPL", default off. On forces SUPL_MODE=0.
- GNSS NTP in sunfish gps.conf: 2.android.pool.ntp.org.
- Settings toggle "Stop extra location assistance", default off.
Off keeps Lineage LPP, XTRA and IZat. On sets LPP_PROFILE=0 (LPPE is
carried by that LPP session), skips the framework XTRA download, and
stops loc_launcher. The vendor binaries stay on the image.
"""
from __future__ import annotations
import pathlib
import sys
MARKER = "sunfish-a16-009-supl-privacy"
TREE = pathlib.Path("/srv/rom/src/lineage")
def must(path: pathlib.Path, old: str, new: str, already: str) -> None:
text = path.read_text()
if already in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def main() -> None:
settings = TREE / "frameworks/base/core/java/android/provider/Settings.java"
gnss_cfg = (
TREE
/ "frameworks/base/services/core/java/com/android/server/location/gnss/GnssConfiguration.java"
)
gnss_prov = (
TREE
/ "frameworks/base/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java"
)
loc_xml = TREE / "packages/apps/Settings/res/xml/location_settings.xml"
strings = TREE / "packages/apps/Settings/res/values/strings.xml"
gps = TREE / "device/google/sunfish/gps.conf"
init_rc = TREE / "device/google/sunfish/init.hardware.rc"
controller = (
TREE
/ "packages/apps/Settings/src/com/android/settings/location/ForceDisableSuplPrefController.java"
)
must(
settings,
""" public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";
""",
""" public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";
/**
* Force disable Secure User Plane Location (SUPL), 0 or 1.
* Default 0 keeps carrier SUPL. AXP/Graphene toggle, """
+ MARKER
+ """.
* @hide
*/
@Readable
public static final String FORCE_DISABLE_SUPL = "force_disable_supl";
/** @hide */
public static final int FORCE_DISABLE_SUPL_DEFAULT = 0;
""",
already="FORCE_DISABLE_SUPL",
)
must(
settings,
""" /** @hide */
public static final int FORCE_DISABLE_SUPL_DEFAULT = 0;
""",
""" /** @hide */
public static final int FORCE_DISABLE_SUPL_DEFAULT = 0;
/**
* Stop LPP, the framework XTRA download, and loc_launcher
* (xtra-daemon, lowi, IZat). 0 or 1. Default 0 keeps Lineage
* assistance. Binaries stay installed. """
+ MARKER
+ """.
* @hide
*/
@Readable
public static final String FORCE_DISABLE_LOCATION_ASSIST =
"force_disable_location_assist";
/** @hide */
public static final int FORCE_DISABLE_LOCATION_ASSIST_DEFAULT = 0;
""",
already="FORCE_DISABLE_LOCATION_ASSIST",
)
must(
gnss_cfg,
"import android.os.SystemProperties;\n",
"import android.os.SystemProperties;\nimport android.provider.Settings;\n",
already="import android.provider.Settings;\n",
)
must(
gnss_cfg,
"import android.util.Log;\n",
"import android.util.Log;\nimport android.util.Slog;\n",
already="import android.util.Slog;\n",
)
must(
gnss_cfg,
""" loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
""",
""" loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
applyConfigOverrides(mContext, mProperties); /* """
+ MARKER
+ """ */
""",
already="applyConfigOverrides(mContext, mProperties)",
)
if "void applyConfigOverrides(" not in gnss_cfg.read_text():
text = gnss_cfg.read_text()
needle = " private static native boolean native_set_es_extension_sec(int emergencyExtensionSeconds);\n"
insert = needle + """
/** """ + MARKER + """: optional SUPL off. Default leaves carrier SUPL_MODE. */
private static void applyConfigOverrides(Context ctx, Properties props) {
String key = Settings.Global.FORCE_DISABLE_SUPL;
int def = Settings.Global.FORCE_DISABLE_SUPL_DEFAULT;
if (Settings.Global.getInt(ctx.getContentResolver(), key, def) == 1) {
props.setProperty(CONFIG_SUPL_MODE, "0");
Slog.d(TAG, "SUPL is force disabled");
}
}
"""
if needle not in text:
sys.exit("native_set_es_extension_sec missing")
gnss_cfg.write_text(text.replace(needle, insert, 1))
must(
gnss_cfg,
""" if (Settings.Global.getInt(ctx.getContentResolver(), key, def) == 1) {
props.setProperty(CONFIG_SUPL_MODE, "0");
Slog.d(TAG, "SUPL is force disabled");
}
}
""",
""" if (Settings.Global.getInt(ctx.getContentResolver(), key, def) == 1) {
props.setProperty(CONFIG_SUPL_MODE, "0");
Slog.d(TAG, "SUPL is force disabled");
}
String assist = Settings.Global.FORCE_DISABLE_LOCATION_ASSIST;
int assistDef = Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT;
if (Settings.Global.getInt(ctx.getContentResolver(), assist, assistDef) == 1) {
// LPPE is carried by the LPP session. This HAL only has a setter
// for LPP_PROFILE, and this override runs after carrier config.
props.setProperty(CONFIG_LPP_PROFILE, "0");
Slog.d(TAG, "LPP assistance stopped");
}
}
""",
already="LPP assistance stopped",
)
must(
gnss_prov,
""" mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ASSISTED_GPS_ENABLED),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
toggleXtraDaemon();
}
}, UserHandle.USER_ALL);
""",
""" mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ASSISTED_GPS_ENABLED),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
toggleXtraDaemon();
}
}, UserHandle.USER_ALL);
/* """
+ MARKER
+ """ */
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_SUPL),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
""",
already="Settings.Global.FORCE_DISABLE_SUPL",
)
must(
gnss_prov,
""" Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_SUPL),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
""",
""" Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_SUPL),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_LOCATION_ASSIST),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
""",
already="Settings.Global.FORCE_DISABLE_LOCATION_ASSIST",
)
must(
gnss_prov,
""" toggleXtraDaemon();
}
public GnssLocationProvider(Context context, GnssNative gnssNative,
""",
""" applyLocationAssistPrivacy();
toggleXtraDaemon();
}
public GnssLocationProvider(Context context, GnssNative gnssNative,
""",
already="applyLocationAssistPrivacy();",
)
must(
gnss_prov,
""" if (!isAssistedGpsEnabled()) {
// PSDS download disabled by system setting, don't try
Log.d(TAG, "handleDownloadPsdsData() called when PSDS disabled by system setting");
return;
}
""",
""" if (!isAssistedGpsEnabled()) {
// PSDS download disabled by system setting, don't try
Log.d(TAG, "handleDownloadPsdsData() called when PSDS disabled by system setting");
return;
}
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT) == 1) {
Log.d(TAG, "handleDownloadPsdsData() skipped: location assistance stopped");
return;
}
""",
already="location assistance stopped",
)
must(
gnss_prov,
""" private void toggleXtraDaemon() {
Log.i(TAG, "Toggling xtra-daemon via property");
SystemProperties.set("persist.sys.xtra-daemon.enabled",
Boolean.toString(isAssistedGpsEnabled()));
}
""",
""" /** """
+ MARKER
+ """ Stop loc_launcher. Does not delete xtra-daemon, lowi or IZat. */
private void applyLocationAssistPrivacy() {
boolean stop = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT) == 1;
SystemProperties.set("persist.sys.axp.loc_privacy", stop ? "1" : "0");
}
private void toggleXtraDaemon() {
Log.i(TAG, "Toggling xtra-daemon via property");
SystemProperties.set("persist.sys.xtra-daemon.enabled",
Boolean.toString(isAssistedGpsEnabled()));
}
""",
already="persist.sys.axp.loc_privacy",
)
old_setid = """ TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
int type = AGPS_SETID_TYPE_NONE;
String setId = null;
final Boolean isEmergency = mNIHandler.getInEmergency();
// Unless we are in an emergency, do not provide sensitive subscriber information
// to SUPL servers.
if (!isEmergency) {
mGnssNative.setAgpsSetId(type, "");
return;
}
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (mGnssConfiguration.isActiveSimEmergencySuplEnabled() && isEmergency
&& mNetworkConnectivityHandler.getActiveSubId() >= 0) {
subId = mNetworkConnectivityHandler.getActiveSubId();
}
if (SubscriptionManager.isValidSubscriptionId(subId)) {
phone = phone.createForSubscriptionId(subId);
}
if ((flags & AGPS_REQUEST_SETID_IMSI) == AGPS_REQUEST_SETID_IMSI) {
setId = phone.getSubscriberId();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_IMSI;
}
} else if ((flags & AGPS_REQUEST_SETID_MSISDN) == AGPS_REQUEST_SETID_MSISDN) {
setId = phone.getLine1Number();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_MSISDN;
}
}
mGnssNative.setAgpsSetId(type, (setId == null) ? "" : setId);
"""
new_setid = """ // """ + MARKER + """: AXP/Divest. Never send IMSI or the phone number
// to a SUPL server, including during an emergency call. LOS 23 already
// skips this outside emergency. Control-plane A-GNSS is unchanged.
// flags is read so the unused-parameter check stays quiet.
if (flags != 0 && Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "ignoring SUPL set-id request flags=" + flags);
}
mGnssNative.setAgpsSetId(AGPS_SETID_TYPE_NONE, "");
"""
prov = gnss_prov.read_text()
if old_setid in prov:
gnss_prov.write_text(prov.replace(old_setid, new_setid, 1))
elif "ignoring SUPL set-id request flags" not in prov:
short = """ // """ + MARKER + """: AXP/Divest. Never send IMSI or the phone number
// to a SUPL server, including during an emergency call. LOS 23 already
// skips this outside emergency. Control-plane A-GNSS is unchanged.
mGnssNative.setAgpsSetId(AGPS_SETID_TYPE_NONE, "");
"""
if short not in prov:
sys.exit("setAgpsSetId block missing")
gnss_prov.write_text(prov.replace(short, new_setid, 1))
must(
loc_xml,
""" settings:controller="com.android.settings.location.LocationServicesPreferenceController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
""" settings:controller="com.android.settings.location.LocationServicesPreferenceController"/>
<!-- """
+ MARKER
+ """: default off, so carrier SUPL stays -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="force_disable_supl"
android:title="@string/force_disable_supl_title"
android:summary="@string/force_disable_supl_summary"
settings:controller="com.android.settings.location.ForceDisableSuplPrefController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
already='android:key="force_disable_supl"',
)
must(
loc_xml,
""" settings:controller="com.android.settings.location.ForceDisableSuplPrefController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
""" settings:controller="com.android.settings.location.ForceDisableSuplPrefController"/>
<!-- """
+ MARKER
+ """: default off. On stops LPP, XTRA and IZat. Binaries stay. -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="force_disable_location_assist"
android:title="@string/force_disable_location_assist_title"
android:summary="@string/force_disable_location_assist_summary"
settings:controller="com.android.settings.location.ForceDisableLocationAssistPrefController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
already='android:key="force_disable_location_assist"',
)
must(
strings,
""" <string name="location_services_preference_title">Location services</string>
""",
""" <string name="location_services_preference_title">Location services</string>
<!-- """
+ MARKER
+ """ -->
<string name="force_disable_supl_title">Force disable Secure User Plane Location (SUPL)</string>
<string name="force_disable_supl_summary">Always disable SUPL assisted location support regardless of carrier configuration or emergency call status. Does not disable control-plane A-GNSS. IMSI and the phone number are never sent to a SUPL server.</string>
""",
already="force_disable_supl_title",
)
must(
strings,
""" <string name="force_disable_supl_summary">Always disable SUPL assisted location support regardless of carrier configuration or emergency call status. Does not disable control-plane A-GNSS. IMSI and the phone number are never sent to a SUPL server.</string>
""",
""" <string name="force_disable_supl_summary">Always disable SUPL assisted location support regardless of carrier configuration or emergency call status. Does not disable control-plane A-GNSS. IMSI and the phone number are never sent to a SUPL server.</string>
<string name="force_disable_location_assist_title">Stop extra location assistance</string>
<string name="force_disable_location_assist_summary">Stops LTE positioning (LPP/LPPE), XTRA orbit downloads, and Qualcomm IZat. Satellite fixes keep working. The programs stay installed. SUPL has its own switch.</string>
""",
already="force_disable_location_assist_title",
)
if not controller.exists():
controller.write_text(
"""package com.android.settings.location;
import android.content.Context;
import android.os.Process;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
/** """
+ MARKER
+ """ AXP/Graphene toggle. Default off. */
public class ForceDisableSuplPrefController extends TogglePreferenceController {
public ForceDisableSuplPrefController(Context ctx, String key) {
super(ctx, key);
}
@Override
public int getAvailabilityStatus() {
if (!Process.myUserHandle().isSystem()) {
return DISABLED_FOR_USER;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_SUPL,
Settings.Global.FORCE_DISABLE_SUPL_DEFAULT) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Global.putInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_SUPL,
isChecked ? 1 : 0);
}
@Override
public int getSliceHighlightMenuRes() {
return NO_RES;
}
}
"""
)
assist_controller = (
TREE
/ "packages/apps/Settings/src/com/android/settings/location/ForceDisableLocationAssistPrefController.java"
)
if not assist_controller.exists():
assist_controller.write_text(
"""package com.android.settings.location;
import android.content.Context;
import android.os.Process;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
/** """
+ MARKER
+ """ Default off. Stops assistance; does not delete blobs. */
public class ForceDisableLocationAssistPrefController extends TogglePreferenceController {
public ForceDisableLocationAssistPrefController(Context ctx, String key) {
super(ctx, key);
}
@Override
public int getAvailabilityStatus() {
if (!Process.myUserHandle().isSystem()) {
return DISABLED_FOR_USER;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Global.putInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
isChecked ? 1 : 0);
}
@Override
public int getSliceHighlightMenuRes() {
return NO_RES;
}
}
"""
)
must(
init_rc,
"""on property:persist.sys.xtra-daemon.enabled=*
restart loc_launcher
""",
"""# """
+ MARKER
+ """
# 1 stops loc_launcher (xtra-daemon, lowi, IZat). Binaries stay.
# The xtra property must not restart the service while that stop is on.
on property:persist.sys.axp.loc_privacy=1
stop loc_launcher
exec_background -- /system/bin/toybox killall xtra-daemon lowi-server
on property:persist.sys.axp.loc_privacy=0
start loc_launcher
on property:sys.boot_completed=1 && property:persist.sys.axp.loc_privacy=1
stop loc_launcher
exec_background -- /system/bin/toybox killall xtra-daemon lowi-server
on property:persist.sys.xtra-daemon.enabled=* && property:persist.sys.axp.loc_privacy=0
restart loc_launcher
""",
already="persist.sys.axp.loc_privacy",
)
raw = gps.read_bytes()
old_ntp = b"NTP_SERVER = time.google.com"
new_ntp = b"NTP_SERVER = 2.android.pool.ntp.org"
if new_ntp not in raw:
if old_ntp not in raw:
sys.exit("NTP_SERVER line missing")
raw = raw.replace(old_ntp, new_ntp, 1)
gps.write_bytes(raw)
print("supl privacy applied")
if __name__ == "__main__":
main()
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""009: AXP Yama ptrace_scope, active at 2.
AXP LOS 22 init.rc sets /proc/sys/kernel/yama/ptrace_scope from
persist.native_debug (true -> 0, false -> 2). GOLDEN's kernel has Yama
off, so those lines do nothing there. This tree builds Yama, so init
writes 2 at boot. persist.native_debug=true still opens ptrace.
Writing that sysctl needs CAP_SYS_PTRACE. AOSP neverallows it for init;
the exception is the one in AXP's ptrace_scope sepolicy patch. The node
is labeled proc_security, which init may already read and write.
"""
from __future__ import annotations
import pathlib
import sys
TREE = pathlib.Path("/srv/rom/src/lineage")
MARKER = "sunfish-a16-009-yama-ptrace"
RC_BOOT_OLD = " write /proc/sys/kernel/randomize_va_space 2\n"
RC_BOOT_NEW = (
" write /proc/sys/kernel/randomize_va_space 2\n"
" # sunfish-a16-009-yama-ptrace: AXP scope 2 unless native debug is on.\n"
" write /proc/sys/kernel/yama/ptrace_scope 2\n"
)
RC_PROP_OLD = (
" write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd}\n"
)
RC_PROP_NEW = (
" write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd}\n"
"\n"
"on property:persist.native_debug=true\n"
" write /proc/sys/kernel/yama/ptrace_scope 0\n"
"\n"
"on property:persist.native_debug=false\n"
" write /proc/sys/kernel/yama/ptrace_scope 2\n"
)
GENFS_OLD = "genfscon proc /sys/kernel/usermodehelper u:object_r:usermodehelper:s0\n"
GENFS_NEW = (
"genfscon proc /sys/kernel/usermodehelper u:object_r:usermodehelper:s0\n"
"genfscon proc /sys/kernel/yama/ptrace_scope u:object_r:proc_security:s0\n"
)
PROP_OLD = "persist.debug. u:object_r:persist_debug_prop:s0\n"
PROP_NEW = (
"persist.debug. u:object_r:persist_debug_prop:s0\n"
"persist.native_debug u:object_r:system_prop:s0\n"
)
INIT_OLD = """allow init {
proc_security
}:file rw_file_perms;
"""
INIT_NEW = """allow init {
proc_security
}:file rw_file_perms;
# sunfish-a16-009-yama-ptrace: CAP_SYS_PTRACE to set ptrace_scope.
allow init self:global_capability_class_set sys_ptrace;
"""
NEVER_END = "} self:global_capability_class_set sys_ptrace;"
def must(path: pathlib.Path, old: str, new: str, already: str) -> None:
text = path.read_text()
if already in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def loosen_sys_ptrace(path: pathlib.Path) -> None:
text = path.read_text()
idx = text.find(NEVER_END)
if idx < 0:
return
start = text.rfind("neverallow {", 0, idx)
if start < 0:
sys.exit(f"neverallow block missing in {path}")
block = text[start:idx]
if "\n -init\n" in block:
return
if "\n domain\n" not in block:
sys.exit(f"domain line missing in sys_ptrace neverallow {path}")
new_block = block.replace("\n domain\n", "\n domain\n -init\n", 1)
path.write_text(text[:start] + new_block + text[idx:])
def main() -> None:
rc = TREE / "system/core/rootdir/init.rc"
must(rc, RC_BOOT_OLD, RC_BOOT_NEW, MARKER)
must(rc, RC_PROP_OLD, RC_PROP_NEW, "on property:persist.native_debug=true")
se = TREE / "system/sepolicy"
must(
se / "private/init.te",
INIT_OLD,
INIT_NEW,
"allow init self:global_capability_class_set sys_ptrace;",
)
loosen_sys_ptrace(se / "private/domain.te")
must(se / "private/genfs_contexts", GENFS_OLD, GENFS_NEW, "yama/ptrace_scope")
must(se / "private/property_contexts", PROP_OLD, PROP_NEW, "persist.native_debug")
api = se / "prebuilts/api"
for domain in api.glob("*/private/domain.te"):
loosen_sys_ptrace(domain)
for genfs in api.glob("*/private/genfs_contexts"):
text = genfs.read_text()
if "yama/ptrace_scope" in text or GENFS_OLD not in text:
continue
genfs.write_text(text.replace(GENFS_OLD, GENFS_NEW, 1))
for props in api.glob("*/private/property_contexts"):
text = props.read_text()
if "persist.native_debug" in text or PROP_OLD not in text:
continue
props.write_text(text.replace(PROP_OLD, PROP_NEW, 1))
# Current policy must still exclude init, or the write is neverallowed.
live = (se / "private/domain.te").read_text()
end = live.find(NEVER_END)
start = live.rfind("neverallow {", 0, end)
if "\n -init\n" not in live[start:end]:
sys.exit("init still neverallowed from sys_ptrace")
print("yama ptrace_scope applied")
if __name__ == "__main__":
main()
@@ -69,12 +69,12 @@ guarantee). Do not relock the bootloader on this Magisk track.
## Branding
`TARGET_UNOFFICIAL_BUILD_ID=AXP-Resurection-Pro` is set at the **top** of
`device/google/sunfish/lineage_sunfish.mk` (before inheriting
`vendor/lineage` → `version.mk`). Helper:
`apply_009_unofficial_build_id.py`. **Pro** = full Magisk resurrection track
(not a future slim/minimal variant). Mirrors GOLDEN’s `…-sunfish-pro` /
releasetype `PRO` intent without patching `vendor/lineage/config/version.mk`.
Ship identity follows AXP/GOLDEN, not Lineage's unofficial channel.
GOLDEN (`22.2-2026.003-sunfish-pro`) reports `ro.lineage.releasetype=PRO`,
`ro.build.tags=release-keys`, and a version ending in `sunfish-pro`.
`TARGET_UNOFFICIAL_BUILD_ID` forces Lineage `UNOFFICIAL` and is not that
scheme. The next bacon sets `PRO` + `release-keys` + `…-sunfish-pro`.
**Pro** remains the full Magisk track (AXP PRO, not SLIM).
`branding.mk` adds `ro.axp.resurection.track=pro`. Ship scripts also publish
`AXP-Resurection-Pro-<ver>-<date>-sunfish.zip` as a hardlink/copy of the
@@ -30,7 +30,9 @@ while IFS= read -r rel || [[ -n "$rel" ]]; do
fail=$((fail + 1))
continue
fi
args=(-p1 --batch -d "$KERNEL_DIR")
# Never let GNU patch auto-answer "Assuming -R": the LOS base can already
# contain a patch's intended result. Reversing it is a regression, not OK.
args=(-p1 --batch --forward -d "$KERNEL_DIR")
if [[ "$DRY_RUN" -eq 1 ]]; then
args+=(--dry-run)
fi
@@ -6,8 +6,13 @@
# off; CONFIG_LSM already lists yama but SECURITY_YAMA was unset so the LSM
# never built.
#
# Does NOT apply high-offset CVE leftovers (nf_tables deliberately skipped) or
# CVE-2022-49980 (denylist until lock-scope port).
# Does NOT apply high-offset CVE leftovers (nf_tables deliberately skipped).
# CVE-2022-49980's one-liner stays on the denylist. The bacon applies
# ports/cve-2022-49980-lock-scope.patch after the CVE lists.
#
# USERFAULTFD: GOLDEN's running 4.14.355 config leaves it unset. LOS 23
# sunfish_defconfig turns it on. This kernel has no UFFDIO_MOVE (that ioctl
# is 6.1+), so ART only opens a fd and times out. Match GOLDEN.
#
# Usage: apply_uss5_1_defconfig.sh <kernel-tree>
set -euo pipefail
@@ -17,15 +22,14 @@ DEF="$K/arch/arm64/configs/sunfish_defconfig"
test -f "$DEF"
test -f "$K/security/yama/Kconfig"
for k in SECURITY_YAMA SLAB_FREELIST_RANDOM; do
for k in SECURITY_YAMA SLAB_FREELIST_RANDOM USERFAULTFD HAVE_ARCH_USERFAULTFD_MINOR; do
sed -i "/CONFIG_${k}/d" "$DEF"
# drop "# CONFIG_FOO is not set" lines too (sed above only matches CONFIG_FOO=)
sed -i "/# CONFIG_${k} is not set/d" "$DEF"
done
{
echo "CONFIG_SECURITY_YAMA=y"
echo "CONFIG_SLAB_FREELIST_RANDOM=y"
echo "# CONFIG_USERFAULTFD is not set"
} >> "$DEF"
echo "OK: US-S5-1 defconfig (YAMA + SLAB_FREELIST_RANDOM) on $K"
echo "OK: US-S5-1 defconfig (YAMA + SLAB_FREELIST_RANDOM, USERFAULTFD off) on $K"
@@ -1,6 +1,7 @@
# relative to AXP kernel_patches
# LAB 2026-10-01: alone ⇒ Google-logo hang (udc_lock recursive deadlock on
# msm-4.14; needs lock-scope companion, not one-liner). Do not re-apply.
# msm-4.14). Companion is ports/cve-2022-49980-lock-scope.patch (UAF fix
# plus uevents outside the lock). Do not apply the one-liner by itself.
CVE-2022-49980/ANY/0001.patch
CVE-2017-15832/qcacld-3.0/0001.patch
CVE-2021-29648/^5.11/0001.patch
@@ -0,0 +1,49 @@
From: Daniel Micay <danielmicay@gmail.com>
Subject: [PATCH] add vmalloc alloc_size attributes
Port of AXP/Graphene
0008-Graphene-Kernel_Hardening-allocsize/4.14/0007.patch onto the
Lineage msm-4.14 vmalloc.h. That header gained vmalloc_user_node_flags
and dropped the pgprot argument of __vmalloc, so the upstream hunk no
longer applies. The attribute is the same one AXP adds: alloc_size(1)
on the size argument.
Signed-off-by: Daniel Micay <danielmicay@gmail.com>
---
include/linux/vmalloc.h | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
--- a/include/linux/vmalloc.h
+++ b/include/linux/vmalloc.h
@@ -76,20 +76,20 @@
static inline unsigned long vmalloc_nr_pages(void) { return 0; }
#endif
-extern void *vmalloc(unsigned long size);
-extern void *vzalloc(unsigned long size);
-extern void *vmalloc_user(unsigned long size);
-extern void *vmalloc_node(unsigned long size, int node);
-extern void *vzalloc_node(unsigned long size, int node);
-extern void *vmalloc_user_node_flags(unsigned long size, int node, gfp_t flags);
-extern void *vmalloc_exec(unsigned long size);
-extern void *vmalloc_32(unsigned long size);
-extern void *vmalloc_32_user(unsigned long size);
-extern void *__vmalloc(unsigned long size, gfp_t gfp_mask);
+extern void *vmalloc(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vzalloc(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_user(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_node(unsigned long size, int node) __attribute__((alloc_size(1)));
+extern void *vzalloc_node(unsigned long size, int node) __attribute__((alloc_size(1)));
+extern void *vmalloc_user_node_flags(unsigned long size, int node, gfp_t flags) __attribute__((alloc_size(1)));
+extern void *vmalloc_exec(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_32(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_32_user(unsigned long size) __attribute__((alloc_size(1)));
+extern void *__vmalloc(unsigned long size, gfp_t gfp_mask) __attribute__((alloc_size(1)));
extern void *__vmalloc_node_range(unsigned long size, unsigned long align,
unsigned long start, unsigned long end, gfp_t gfp_mask,
pgprot_t prot, unsigned long vm_flags, int node,
- const void *caller);
+ const void *caller) __attribute__((alloc_size(1)));
#ifndef CONFIG_MMU
extern void *__vmalloc_node_flags(unsigned long size, int node, gfp_t flags);
static inline void *__vmalloc_node_flags_caller(unsigned long size, int node,
@@ -0,0 +1,95 @@
From: Alan Stern <stern@rowland.harvard.edu>
Subject: [PATCH] USB: gadget: fix udc_lock scope around usb_udc_uevent
CVE-2022-49980 (2191c00855b0) takes udc_lock inside usb_udc_uevent().
On msm-4.14 that callback still runs synchronously from device_add()
and kobject_uevent() while the same thread already holds udc_lock, so
the mutex deadlocks and the phone hangs on the Google logo.
Linux 6.1 usb_add_gadget() / gadget_bind_driver() only hold udc_lock
around the list and the driver pointer. This keeps the 4.14 call
shape and moves those uevents outside the lock, then applies the
UAF fix. Do not apply 2191c00855b0 without this.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
---
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -1229,11 +1229,18 @@
mutex_lock(&udc_lock);
list_add_tail(&udc->list, &udc_list);
+ mutex_unlock(&udc_lock);
+ /*
+ * device_add() emits a uevent that calls usb_udc_uevent(), which
+ * takes udc_lock. Same for device_del() on the error path. v6.1
+ * usb_add_gadget() only holds the mutex around the list update.
+ */
ret = device_add(&udc->dev);
if (ret)
goto err_unlist_udc;
+ mutex_lock(&udc_lock);
usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
udc->vbus = true;
@@ -1247,9 +1254,11 @@
return 0;
err_del_udc:
+ mutex_unlock(&udc_lock);
device_del(&udc->dev);
err_unlist_udc:
+ mutex_lock(&udc_lock);
list_del(&udc->list);
mutex_unlock(&udc_lock);
@@ -1313,7 +1322,14 @@
dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n",
udc->driver->function);
+ /*
+ * Caller holds udc_lock. usb_udc_uevent() takes it too, so the
+ * CHANGE uevent has to run outside the lock. Driver is still bound;
+ * the callback re-locks to read it.
+ */
+ mutex_unlock(&udc_lock);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
+ mutex_lock(&udc_lock);
usb_gadget_disconnect(udc->gadget);
udc->driver->disconnect(udc->gadget);
@@ -1383,7 +1399,10 @@
}
usb_udc_connect_control(udc);
+ /* Same deadlock as remove: uevent re-enters udc_lock. */
+ mutex_unlock(&udc_lock);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
+ mutex_lock(&udc_lock);
return 0;
err1:
if (ret != -EISNAM)
@@ -1611,13 +1630,14 @@
return ret;
}
- if (udc->driver) {
+ mutex_lock(&udc_lock);
+ if (udc->driver)
ret = add_uevent_var(env, "USB_UDC_DRIVER=%s",
udc->driver->function);
- if (ret) {
- dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n");
- return ret;
- }
+ mutex_unlock(&udc_lock);
+ if (ret) {
+ dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n");
+ return ret;
}
return 0;
@@ -0,0 +1,424 @@
From: Daniel Micay <danielmicay@gmail.com>
Subject: [PATCH] make sysctl constants read-only
Port of AXP/Graphene ro/4.14/0007.patch and ro/4.14/0034.patch.
Declarations still match; hunk context does not, and this tree has no
net/decnet/sysctl_net_decnet.c. Same __read_only (__ro_after_init)
annotations AXP applies, on the files that exist here.
Signed-off-by: Daniel Micay <danielmicay@gmail.com>
---
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -53,7 +53,7 @@
/*
* Minimum pipe size, as required by POSIX
*/
-unsigned int pipe_min_size = PAGE_SIZE;
+unsigned int pipe_min_size __read_only = PAGE_SIZE;
/* Maximum allocatable pages per user. Hard limit is unset by default, soft
* matches default values.
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -121,48 +121,48 @@
/* Constants used for minimum and maximum */
#ifdef CONFIG_LOCKUP_DETECTOR
-static int sixty = 60;
+static int sixty __read_only = 60;
#endif
-static int __maybe_unused neg_one = -1;
-static int __maybe_unused neg_three = -3;
+static int __maybe_unused neg_one __read_only = -1;
+static int __maybe_unused neg_three __read_only = -3;
-static int zero;
-static int __maybe_unused one = 1;
-static int __maybe_unused two = 2;
-static int __maybe_unused three = 3;
-static int __maybe_unused four = 4;
+static int zero __read_only;
+static int __maybe_unused one __read_only = 1;
+static int __maybe_unused two __read_only = 2;
+static int __maybe_unused three __read_only = 3;
+static int __maybe_unused four __read_only = 4;
static int int_max = INT_MAX;
-static unsigned long zero_ul;
-static unsigned long one_ul = 1;
-static unsigned long long_max = LONG_MAX;
-static int one_hundred = 100;
-static int one_thousand = 1000;
+static unsigned long zero_ul __read_only;
+static unsigned long one_ul __read_only = 1;
+static unsigned long long_max __read_only = LONG_MAX;
+static int one_hundred __read_only = 100;
+static int one_thousand __read_only = 1000;
#ifdef CONFIG_SCHED_WALT
-static int two_million = 2000000;
+static int two_million __read_only = 2000000;
#endif
#ifdef CONFIG_PRINTK
-static int ten_thousand = 10000;
+static int ten_thousand __read_only = 10000;
#endif
#ifdef CONFIG_PERF_EVENTS
-static int six_hundred_forty_kb = 640 * 1024;
+static int six_hundred_forty_kb __read_only = 640 * 1024;
#endif
static int __maybe_unused two_hundred_million __read_only = 200000000;
static int two_hundred_fifty_five __read_only = 255;
/* this is needed for the proc_doulongvec_minmax of vm_dirty_bytes */
-static unsigned long dirty_bytes_min = 2 * PAGE_SIZE;
+static unsigned long dirty_bytes_min __read_only = 2 * PAGE_SIZE;
/* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */
-static int maxolduid = 65535;
-static int minolduid;
+static int maxolduid __read_only = 65535;
+static int minolduid __read_only;
-static int ngroups_max = NGROUPS_MAX;
+static int ngroups_max __read_only = NGROUPS_MAX;
static const int cap_last_cap = CAP_LAST_CAP;
/*this is needed for proc_doulongvec_minmax of sysctl_hung_task_timeout_secs */
#ifdef CONFIG_DETECT_HUNG_TASK
-static unsigned long hung_task_timeout_max = (LONG_MAX/HZ);
+static unsigned long hung_task_timeout_max __read_only = (LONG_MAX/HZ);
#endif
#ifdef CONFIG_INOTIFY_USER
@@ -332,19 +332,19 @@
};
#ifdef CONFIG_SCHED_DEBUG
-static int min_sched_granularity_ns = 100000; /* 100 usecs */
-static int max_sched_granularity_ns = NSEC_PER_SEC; /* 1 second */
-static int min_wakeup_granularity_ns; /* 0 usecs */
-static int max_wakeup_granularity_ns = NSEC_PER_SEC; /* 1 second */
+static int min_sched_granularity_ns __read_only = 100000; /* 100 usecs */
+static int max_sched_granularity_ns __read_only = NSEC_PER_SEC; /* 1 second */
+static int min_wakeup_granularity_ns __read_only; /* 0 usecs */
+static int max_wakeup_granularity_ns __read_only = NSEC_PER_SEC; /* 1 second */
#ifdef CONFIG_SMP
-static int min_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE;
-static int max_sched_tunable_scaling = SCHED_TUNABLESCALING_END-1;
+static int min_sched_tunable_scaling __read_only = SCHED_TUNABLESCALING_NONE;
+static int max_sched_tunable_scaling __read_only = SCHED_TUNABLESCALING_END-1;
#endif /* CONFIG_SMP */
#endif /* CONFIG_SCHED_DEBUG */
#ifdef CONFIG_COMPACTION
-static int min_extfrag_threshold;
-static int max_extfrag_threshold = 1000;
+static int min_extfrag_threshold __read_only;
+static int max_extfrag_threshold __read_only = 1000;
#endif
#ifdef CONFIG_BPF_SYSCALL
--- a/ipc/mq_sysctl.c
+++ b/ipc/mq_sysctl.c
@@ -47,11 +47,11 @@
#define proc_mq_dointvec_minmax NULL
#endif
-static int msg_max_limit_min = MIN_MSGMAX;
-static int msg_max_limit_max = HARD_MSGMAX;
+static int msg_max_limit_min __read_only = MIN_MSGMAX;
+static int msg_max_limit_max __read_only = HARD_MSGMAX;
-static int msg_maxsize_limit_min = MIN_MSGSIZEMAX;
-static int msg_maxsize_limit_max = HARD_MSGSIZEMAX;
+static int msg_maxsize_limit_min __read_only = MIN_MSGSIZEMAX;
+static int msg_maxsize_limit_max __read_only = HARD_MSGSIZEMAX;
static struct ctl_table mq_sysctls[] = {
{
--- a/lib/test_sysctl.c
+++ b/lib/test_sysctl.c
@@ -36,8 +36,8 @@
#include <linux/delay.h>
#include <linux/vmalloc.h>
-static int i_zero;
-static int i_one_hundred = 100;
+static int i_zero __read_only;
+static int i_one_hundred __read_only = 100;
struct test_sysctl_data {
int int_0001;
--- a/net/ax25/sysctl_net_ax25.c
+++ b/net/ax25/sysctl_net_ax25.c
@@ -12,21 +12,21 @@
#include <linux/spinlock.h>
#include <net/ax25.h>
-static int min_ipdefmode[1], max_ipdefmode[] = {1};
-static int min_axdefmode[1], max_axdefmode[] = {1};
-static int min_backoff[1], max_backoff[] = {2};
-static int min_conmode[1], max_conmode[] = {2};
-static int min_window[] = {1}, max_window[] = {7};
-static int min_ewindow[] = {1}, max_ewindow[] = {63};
-static int min_t1[] = {1}, max_t1[] = {30000};
-static int min_t2[] = {1}, max_t2[] = {20000};
-static int min_t3[1], max_t3[] = {3600000};
-static int min_idle[1], max_idle[] = {65535000};
-static int min_n2[] = {1}, max_n2[] = {31};
-static int min_paclen[] = {1}, max_paclen[] = {512};
-static int min_proto[1], max_proto[] = { AX25_PROTO_MAX };
+static int min_ipdefmode[1], max_ipdefmode[] __read_only = {1};
+static int min_axdefmode[1], max_axdefmode[] __read_only = {1};
+static int min_backoff[1], max_backoff[] __read_only = {2};
+static int min_conmode[1], max_conmode[] __read_only = {2};
+static int min_window[] __read_only = {1}, max_window[] __read_only = {7};
+static int min_ewindow[] __read_only = {1}, max_ewindow[] __read_only = {63};
+static int min_t1[] __read_only = {1}, max_t1[] __read_only = {30000};
+static int min_t2[] __read_only = {1}, max_t2[] __read_only = {20000};
+static int min_t3[1], max_t3[] __read_only = {3600000};
+static int min_idle[1], max_idle[] __read_only = {65535000};
+static int min_n2[] __read_only = {1}, max_n2[] __read_only = {31};
+static int min_paclen[] __read_only = {1}, max_paclen[] __read_only = {512};
+static int min_proto[1], max_proto[] __read_only = { AX25_PROTO_MAX };
#ifdef CONFIG_AX25_DAMA_SLAVE
-static int min_ds_timeout[1], max_ds_timeout[] = {65535000};
+static int min_ds_timeout[1], max_ds_timeout[] __read_only = {65535000};
#endif
static const struct ctl_table ax25_param_table[] = {
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -23,14 +23,14 @@
#include <net/busy_poll.h>
#include <net/pkt_sched.h>
-static int zero = 0;
-static int one = 1;
-static int two __maybe_unused = 2;
-static int min_sndbuf = SOCK_MIN_SNDBUF;
-static int min_rcvbuf = SOCK_MIN_RCVBUF;
-static int max_skb_frags = MAX_SKB_FRAGS;
-static long long_one __maybe_unused = 1;
-static long long_max __maybe_unused = LONG_MAX;
+static int zero __read_only = 0;
+static int one __read_only = 1;
+static int two __maybe_unused __read_only = 2;
+static int min_sndbuf __read_only = SOCK_MIN_SNDBUF;
+static int min_rcvbuf __read_only = SOCK_MIN_RCVBUF;
+static int max_skb_frags __read_only = MAX_SKB_FRAGS;
+static long long_one __maybe_unused __read_only = 1;
+static long long_max __maybe_unused __read_only = LONG_MAX;
static int net_msg_warn; /* Unused, but still a sysctl */
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -28,33 +28,33 @@
#include <net/protocol.h>
#include <net/netevent.h>
-static int zero;
-static int one = 1;
-static int three = 3;
-static int four = 4;
-static int hundred = 100;
-static int thousand = 1000;
-static int gso_max_segs = GSO_MAX_SEGS;
-static int tcp_retr1_max = 255;
-static int ip_local_port_range_min[] = { 1, 1 };
-static int ip_local_port_range_max[] = { 65535, 65535 };
-static int tcp_adv_win_scale_min = -31;
-static int tcp_adv_win_scale_max = 31;
-static int tcp_min_snd_mss_min = TCP_MIN_SND_MSS;
-static int tcp_min_snd_mss_max = 65535;
+static int zero __read_only;
+static int one __read_only = 1;
+static int three __read_only = 3;
+static int four __read_only = 4;
+static int hundred __read_only = 100;
+static int thousand __read_only = 1000;
+static int gso_max_segs __read_only = GSO_MAX_SEGS;
+static int tcp_retr1_max __read_only = 255;
+static int ip_local_port_range_min[] __read_only = { 1, 1 };
+static int ip_local_port_range_max[] __read_only = { 65535, 65535 };
+static int tcp_adv_win_scale_min __read_only = -31;
+static int tcp_adv_win_scale_max __read_only = 31;
+static int tcp_min_snd_mss_min __read_only = TCP_MIN_SND_MSS;
+static int tcp_min_snd_mss_max __read_only = 65535;
static int ip_privileged_port_min;
-static int ip_privileged_port_max = 65535;
-static int ip_ttl_min = 1;
-static int ip_ttl_max = 255;
-static int tcp_syn_retries_min = 1;
-static int tcp_syn_retries_max = MAX_TCP_SYNCNT;
-static int ip_ping_group_range_min[] = { 0, 0 };
-static int ip_ping_group_range_max[] = { GID_T_MAX, GID_T_MAX };
-static int one_day_secs = 24 * 3600;
-static int tcp_delack_seg_min = TCP_DELACK_MIN;
-static int tcp_delack_seg_max = 60;
+static int ip_privileged_port_max __read_only = 65535;
+static int ip_ttl_min __read_only = 1;
+static int ip_ttl_max __read_only = 255;
+static int tcp_syn_retries_min __read_only = 1;
+static int tcp_syn_retries_max __read_only = MAX_TCP_SYNCNT;
+static int ip_ping_group_range_min[] __read_only = { 0, 0 };
+static int ip_ping_group_range_max[] __read_only = { GID_T_MAX, GID_T_MAX };
+static int one_day_secs __read_only = 24 * 3600;
+static int tcp_delack_seg_min __read_only = TCP_DELACK_MIN;
+static int tcp_delack_seg_max __read_only = 60;
static int tcp_use_userconfig_min;
-static int tcp_use_userconfig_max = 1;
+static int tcp_use_userconfig_max __read_only = 1;
/* obsolete */
static int sysctl_tcp_low_latency __read_mostly;
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -22,9 +22,9 @@
#endif
static int zero;
-static int one = 1;
-static int auto_flowlabels_min;
-static int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX;
+static int one __read_only = 1;
+static int auto_flowlabels_min __read_only;
+static int auto_flowlabels_max __read_only = IP6_AUTO_FLOW_LABEL_MAX;
static int proc_rt6_multipath_hash_policy(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
--- a/net/netrom/sysctl_net_netrom.c
+++ b/net/netrom/sysctl_net_netrom.c
@@ -15,22 +15,22 @@
/*
* Values taken from NET/ROM documentation.
*/
-static int min_quality[] = {0}, max_quality[] = {255};
-static int min_obs[] = {0}, max_obs[] = {255};
-static int min_ttl[] = {0}, max_ttl[] = {255};
-static int min_t1[] = {5 * HZ};
-static int max_t1[] = {600 * HZ};
-static int min_n2[] = {2}, max_n2[] = {127};
-static int min_t2[] = {1 * HZ};
-static int max_t2[] = {60 * HZ};
-static int min_t4[] = {1 * HZ};
-static int max_t4[] = {1000 * HZ};
-static int min_window[] = {1}, max_window[] = {127};
-static int min_idle[] = {0 * HZ};
-static int max_idle[] = {65535 * HZ};
-static int min_route[] = {0}, max_route[] = {1};
-static int min_fails[] = {1}, max_fails[] = {10};
-static int min_reset[] = {0}, max_reset[] = {1};
+static int min_quality[] __read_only = {0}, max_quality[] __read_only = {255};
+static int min_obs[] __read_only = {0}, max_obs[] __read_only = {255};
+static int min_ttl[] __read_only = {0}, max_ttl[] __read_only = {255};
+static int min_t1[] __read_only = {5 * HZ};
+static int max_t1[] __read_only = {600 * HZ};
+static int min_n2[] __read_only = {2}, max_n2[] __read_only = {127};
+static int min_t2[] __read_only = {1 * HZ};
+static int max_t2[] __read_only = {60 * HZ};
+static int min_t4[] __read_only = {1 * HZ};
+static int max_t4[] __read_only = {1000 * HZ};
+static int min_window[] __read_only = {1}, max_window[] __read_only = {127};
+static int min_idle[] __read_only = {0 * HZ};
+static int max_idle[] __read_only = {65535 * HZ};
+static int min_route[] __read_only = {0}, max_route[] __read_only = {1};
+static int min_fails[] __read_only = {1}, max_fails[] __read_only = {10};
+static int min_reset[] __read_only = {0}, max_reset[] __read_only = {1};
static struct ctl_table_header *nr_table_header;
--- a/net/phonet/sysctl.c
+++ b/net/phonet/sysctl.c
@@ -35,8 +35,8 @@
#define DYNAMIC_PORT_MAX 0x7f
static DEFINE_SEQLOCK(local_port_range_lock);
-static int local_port_range_min[2] = {0, 0};
-static int local_port_range_max[2] = {1023, 1023};
+static int local_port_range_min[2] __read_only = {0, 0};
+static int local_port_range_max[2] __read_only = {1023, 1023};
static int local_port_range[2] = {DYNAMIC_PORT_MIN, DYNAMIC_PORT_MAX};
static struct ctl_table_header *phonet_table_hrd;
--- a/net/rose/sysctl_net_rose.c
+++ b/net/rose/sysctl_net_rose.c
@@ -12,15 +12,15 @@
#include <net/ax25.h>
#include <net/rose.h>
-static int min_timer[] = {1 * HZ};
-static int max_timer[] = {300 * HZ};
-static int min_idle[] = {0 * HZ};
-static int max_idle[] = {65535 * HZ};
-static int min_route[1], max_route[] = {1};
-static int min_ftimer[] = {60 * HZ};
-static int max_ftimer[] = {600 * HZ};
-static int min_maxvcs[] = {1}, max_maxvcs[] = {254};
-static int min_window[] = {1}, max_window[] = {7};
+static int min_timer[] __read_only = {1 * HZ};
+static int max_timer[] __read_only = {300 * HZ};
+static int min_idle[] __read_only = {0 * HZ};
+static int max_idle[] __read_only = {65535 * HZ};
+static int min_route[1], max_route[] __read_only = {1};
+static int min_ftimer[] __read_only = {60 * HZ};
+static int max_ftimer[] __read_only = {600 * HZ};
+static int min_maxvcs[] __read_only = {1}, max_maxvcs[] = {254};
+static int min_window[] __read_only = {1}, max_window[] = {7};
static struct ctl_table_header *rose_table_header;
--- a/net/sctp/sysctl.c
+++ b/net/sctp/sysctl.c
@@ -40,21 +40,21 @@
#include <net/sctp/sctp.h>
#include <linux/sysctl.h>
-static int zero = 0;
-static int one = 1;
-static int timer_max = 86400000; /* ms in one day */
-static int int_max = INT_MAX;
-static int sack_timer_min = 1;
-static int sack_timer_max = 500;
-static int addr_scope_max = SCTP_SCOPE_POLICY_MAX;
-static int rwnd_scale_max = 16;
-static int rto_alpha_min = 0;
-static int rto_beta_min = 0;
-static int rto_alpha_max = 1000;
-static int rto_beta_max = 1000;
+static int zero __read_only = 0;
+static int one __read_only = 1;
+static int timer_max __read_only = 86400000; /* ms in one day */
+static int int_max __read_only = INT_MAX;
+static int sack_timer_min __read_only = 1;
+static int sack_timer_max __read_only = 500;
+static int addr_scope_max __read_only = SCTP_SCOPE_POLICY_MAX;
+static int rwnd_scale_max __read_only = 16;
+static int rto_alpha_min __read_only = 0;
+static int rto_beta_min __read_only = 0;
+static int rto_alpha_max __read_only = 1000;
+static int rto_beta_max __read_only = 1000;
-static unsigned long max_autoclose_min = 0;
-static unsigned long max_autoclose_max =
+static unsigned long max_autoclose_min __read_only = 0;
+static unsigned long max_autoclose_max __read_only =
(MAX_SCHEDULE_TIMEOUT / HZ > UINT_MAX)
? UINT_MAX : MAX_SCHEDULE_TIMEOUT / HZ;
--- a/net/x25/sysctl_net_x25.c
+++ b/net/x25/sysctl_net_x25.c
@@ -13,8 +13,8 @@
#include <linux/init.h>
#include <net/x25.h>
-static int min_timer[] = { 1 * HZ };
-static int max_timer[] = { 300 * HZ };
+static int min_timer[] __read_only = { 1 * HZ };
+static int max_timer[] __read_only = { 300 * HZ };
static struct ctl_table_header *x25_table_header;
@@ -12,6 +12,9 @@
0008-Graphene-Kernel_Hardening-misc/4.14/0005.patch
0008-Graphene-Kernel_Hardening-misc/4.14/0010.patch
0008-Graphene-Kernel_Hardening-misc/4.14/0018.patch
# misc/0021 is already present in LOS @ 28f9290ae067 (KSYM_NAME_LEN=192).
# Applying it with GNU patch's reverse detection silently shrinks it to 128
# under CONFIG_CFI_CLANG, so it must remain omitted.
0008-Graphene-Kernel_Hardening-random/4.14/0002.patch
0008-Graphene-Kernel_Hardening-random/4.14/0007.patch
0008-Graphene-Kernel_Hardening-random/4.14/0012.patch
@@ -30,13 +30,19 @@ Stage1+combo4+CVE apply.
```text
zcat /proc/config.gz | grep CONFIG_SECURITY_YAMA=y
zcat /proc/config.gz | grep CONFIG_SLAB_FREELIST_RANDOM=y
cat /proc/sys/kernel/yama/ptrace_scope # expect 1 (default)
cat /proc/sys/kernel/yama/ptrace_scope # expect 2 (ship policy)
pidof magiskd # still up
```
The ship script now applies `apply_009_yama_ptrace.py`, so the current LAB
policy is `ptrace_scope=2`, not the kernel default `1`. Matching AXP's
`persist.native_debug` 0/2 toggle is a separate userspace change and must not
be mixed into the CVE-2022-49980 boot test.
## Out of scope here
- `CVE-2022-49980` lock-scope port (still denylist)
- The original `CVE-2022-49980/ANY/0001.patch` remains denylisted. The next
isolated bacon applies `ports/cve-2022-49980-lock-scope.patch` instead.
- nf_tables high-offset CVEs
- Stage1 slab FAIL patches (`0010`/`0018` still reject on this tree)
- `hardened_malloc` / bootloader lock
@@ -0,0 +1,414 @@
#!/usr/bin/env python3
"""012: Graphene hardened_malloc on LOS 23.2, the AXP GRAPHENE_MALLOC set.
Source of the integration is GrapheneOS platform_bionic branch 16:
64-bit libc uses hardened_malloc, 32-bit stays Scudo, and SurfaceFlinger
plus camera executables stay on Scudo. AXP documents that camera exception
as applying to every camera executable, which on sunfish is
/vendor/bin/hw/android.hardware.camera.provider@2.7-service-google rather
than the newer Pixel apex path.
Library pin: GrapheneOS/hardened_malloc 1d7fc7ffe0f8 (branch 14, 2025-04-03),
the newest branch that is not the Android 17 line. device_has_arm_mte is
removed because LOS soong has no such product variable and sunfish has no MTE.
Class region size is 2GiB, as DivestOS/AXP set it, because the sunfish
kernel has a 39-bit address space (GOLDEN: VA_BITS=39 with hardened_malloc).
system/core init.rc raises vm.max_map_count to 1048576, same as GrapheneOS 16.
frameworks/av no longer sets RLIMIT_AS on this tree, so that AXP/Pie hunk
does not apply.
"""
from __future__ import annotations
import os
import pathlib
import subprocess
import sys
MARKER = "SUNFISH_A16_012_HARDENED_MALLOC"
PIN = "1d7fc7ffe0f8068a32ab2ba33df7515e7bad3f5f"
REPO = "https://github.com/GrapheneOS/hardened_malloc.git"
REGION_48BIT = "-DCONFIG_CLASS_REGION_SIZE=34359738368"
REGION_39BIT = "-DCONFIG_CLASS_REGION_SIZE=2147483648"
HERE = pathlib.Path(__file__).resolve().parent
TREE = pathlib.Path(os.environ.get("ANDROID_BUILD_TOP", "/srv/rom/src/lineage"))
def must_replace(path: pathlib.Path, old: str, new: str, label: str) -> None:
text = path.read_text()
if new in text:
print(f"already applied: {path} ({label})")
return
if old not in text:
sys.exit(f"anchor missing for {label} in {path}")
path.write_text(text.replace(old, new, 1))
print(f"patched {label}: {path}")
def rewrite_region_comment(bp: pathlib.Path) -> None:
text = bp.read_text()
stale = f'{REGION_39BIT}", // 32GiB'
fresh = f'{REGION_39BIT}", // 2GiB, 39-bit VA (DivestOS/AXP)'
if stale not in text:
return
bp.write_text(text.replace(stale, fresh))
print(f"region comment: {bp}")
def ensure_library() -> None:
dest = TREE / "external/hardened_malloc"
if (dest / ".git").is_dir():
head = subprocess.check_output(
["git", "-C", dest, "rev-parse", "HEAD"], text=True
).strip()
bp_text = (dest / "Android.bp").read_text()
if head == PIN and MARKER in bp_text and REGION_39BIT in bp_text:
rewrite_region_comment(dest / "Android.bp")
print(f"hardened_malloc already at {PIN}")
return
subprocess.check_call(["git", "-C", dest, "fetch", "--depth", "1", "origin", PIN])
subprocess.check_call(["git", "-C", dest, "checkout", "--detach", PIN])
else:
if dest.exists():
sys.exit(f"{dest} exists and is not a git checkout")
dest.parent.mkdir(parents=True, exist_ok=True)
subprocess.check_call(
["git", "clone", "--depth", "1", REPO, str(dest)]
)
subprocess.check_call(["git", "-C", dest, "fetch", "--depth", "1", "origin", PIN])
subprocess.check_call(["git", "-C", dest, "checkout", "--detach", PIN])
bp = dest / "Android.bp"
text = bp.read_text()
mte = """ device_has_arm_mte: {
cflags: ["-DHAS_ARM_MTE", "-march=armv8-a+dotprod+memtag"]
},
"""
if mte in text:
text = text.replace(mte, "")
# sunfish msm-4.14 is CONFIG_ARM64_VA_BITS=39. The 32GiB default class
# region needs 48-bit VA and aborts every 64-bit process at boot.
# DivestOS/AXP Patch.sh: sed 's/34359738368/2147483648/' Android.bp.
text = text.replace(REGION_48BIT, REGION_39BIT)
text = text.replace(
f'{REGION_39BIT}", // 32GiB',
f'{REGION_39BIT}", // 2GiB, 39-bit VA (DivestOS/AXP)',
)
bp.write_text(text)
text = bp.read_text()
if REGION_39BIT not in text:
sys.exit(f"CONFIG_CLASS_REGION_SIZE is not 2GiB in {bp}")
if MARKER not in text:
text = text.replace(
"cc_library {\n",
f"cc_library {{\n // {MARKER}: sunfish has no MTE; LOS soong has no device_has_arm_mte.\n",
1,
)
bp.write_text(text)
head = subprocess.check_output(
["git", "-C", dest, "rev-parse", "HEAD"], text=True
).strip()
if head != PIN:
sys.exit(f"hardened_malloc HEAD {head} != {PIN}")
print(f"hardened_malloc {PIN}")
def patch_bionic() -> None:
wrapper = TREE / "bionic/libc/bionic/h_malloc_wrapper.cpp"
src = HERE / "h_malloc_wrapper.cpp"
if not wrapper.exists() or wrapper.read_text() != src.read_text():
wrapper.write_text(src.read_text())
print(f"installed {wrapper}")
must_replace(
TREE / "bionic/libc/Android.bp",
"""cc_defaults {
name: "libc_native_allocator_defaults",
whole_static_libs: [
"libscudo",
],
cflags: [
"-DUSE_SCUDO",
],
header_libs: ["gwp_asan_headers"],
product_variables: {
malloc_low_memory: {
cflags: ["-UUSE_SCUDO"],
whole_static_libs: [
"libjemalloc5",
"libc_jemalloc_wrapper",
],
exclude_static_libs: [
"libscudo",
],
},
},
}
""",
f"""cc_defaults {{
// {MARKER}
name: "libc_native_allocator_defaults",
header_libs: ["gwp_asan_headers"],
whole_static_libs: ["libscudo"],
// GrapheneOS 16: hardened_malloc on 64-bit, Scudo on 32-bit.
multilib: {{
lib32: {{
cflags: ["-DUSE_SCUDO"],
}},
lib64: {{
srcs: ["bionic/h_malloc_wrapper.cpp"],
cflags: [
"-DH_MALLOC_PREFIX",
"-DUSE_H_MALLOC",
"-DUSE_SCUDO",
],
whole_static_libs: [
"libhardened_malloc",
],
}},
}},
}}
""",
"allocator-defaults",
)
must_replace(
TREE / "bionic/libc/bionic/malloc_common.h",
"""#else // __has_feature(hwaddress_sanitizer)
#if defined(USE_SCUDO)
""",
f"""#else // __has_feature(hwaddress_sanitizer)
// {MARKER}
#ifdef __LP64__
#ifndef USE_H_MALLOC
#error missing USE_H_MALLOC
#endif
#include "h_malloc.h"
#define Malloc(function) h_ ## function
__BEGIN_DECLS
int h_malloc_info(int options, FILE* fp);
__END_DECLS
#if defined(USE_SCUDO)
#include "scudo.h"
void InitNativeAllocatorDispatch(libc_globals* globals);
#endif
#define BOTH_H_MALLOC_AND_SCUDO
#else // 32-bit
#if defined(USE_SCUDO)
""",
"malloc-header",
)
header = TREE / "bionic/libc/bionic/malloc_common.h"
text = header.read_text()
tail = """#define Malloc(function) je_ ## function
#endif
#endif
const MallocDispatch* NativeAllocatorDispatch();
"""
tail_new = """#define Malloc(function) je_ ## function
#endif
#endif // __LP64__
#endif // __has_feature(hwaddress_sanitizer)
const MallocDispatch* NativeAllocatorDispatch();
"""
if tail_new not in text:
if tail not in text:
sys.exit(f"header close anchor missing in {header}")
header.write_text(text.replace(tail, tail_new, 1))
must_replace(
TREE / "bionic/libc/bionic/malloc_common.cpp",
"""const MallocDispatch* NativeAllocatorDispatch() {
return &__libc_malloc_default_dispatch;
}
""",
f"""#if defined(BOTH_H_MALLOC_AND_SCUDO)
// {MARKER}: camera executables and surfaceflinger stay on Scudo.
#define ScudoMalloc(function) scudo_ ## function
static constexpr MallocDispatch __scudo_malloc_dispatch __attribute__((unused)) = {{
ScudoMalloc(calloc),
ScudoMalloc(free),
ScudoMalloc(mallinfo),
ScudoMalloc(malloc),
ScudoMalloc(malloc_usable_size),
ScudoMalloc(memalign),
ScudoMalloc(posix_memalign),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
ScudoMalloc(pvalloc),
#endif
ScudoMalloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
ScudoMalloc(valloc),
#endif
ScudoMalloc(malloc_iterate),
ScudoMalloc(malloc_disable),
ScudoMalloc(malloc_enable),
ScudoMalloc(mallopt),
ScudoMalloc(aligned_alloc),
ScudoMalloc(malloc_info),
}};
static const MallocDispatch* native_allocator_dispatch;
void InitNativeAllocatorDispatch(libc_globals* globals) {{
bool hardened_impl = true;
if (globals->flags & GLOBAL_FLAG_DISABLE_HARDENED_MALLOC) {{
hardened_impl = false;
}} else if (getenv("DISABLE_HARDENED_MALLOC") != nullptr) {{
hardened_impl = false;
}}
const MallocDispatch* table = hardened_impl ?
&__libc_malloc_default_dispatch :
&__scudo_malloc_dispatch;
if (!hardened_impl) {{
globals->malloc_dispatch_table = __scudo_malloc_dispatch;
globals->current_dispatch_table = &globals->malloc_dispatch_table;
globals->default_dispatch_table = &globals->malloc_dispatch_table;
}}
native_allocator_dispatch = table;
}}
const MallocDispatch* NativeAllocatorDispatch() {{
return native_allocator_dispatch;
}}
#else
const MallocDispatch* NativeAllocatorDispatch() {{
return &__libc_malloc_default_dispatch;
}}
#endif
""",
"dispatch",
)
must_replace(
TREE / "bionic/libc/bionic/malloc_common_dynamic.cpp",
"""void MallocInitImpl(libc_globals* globals) {
char prop[PROP_VALUE_MAX];
""",
f"""void MallocInitImpl(libc_globals* globals) {{
#if defined(BOTH_H_MALLOC_AND_SCUDO)
// {MARKER}
InitNativeAllocatorDispatch(globals);
#endif
char prop[PROP_VALUE_MAX];
""",
"malloc-init",
)
must_replace(
TREE / "bionic/libc/private/bionic_globals.h",
""" MallocDispatch malloc_dispatch_table;
};
""",
f""" MallocDispatch malloc_dispatch_table;
// {MARKER}
int flags;
}};
#define GLOBAL_FLAG_DISABLE_HARDENED_MALLOC 1
""",
"globals",
)
init = TREE / "bionic/libc/bionic/libc_init_dynamic.cpp"
text = init.read_text()
if '#include <string.h>' not in text:
if '#include <stdlib.h>' not in text:
sys.exit(f"stdlib include missing in {init}")
text = text.replace(
"#include <stdlib.h>\n",
"#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n",
1,
)
helper = f"""
// {MARKER}: GrapheneOS disables hardened_malloc for surfaceflinger.
// AXP expands the camera exception to every camera executable. sunfish runs
// /vendor/bin/hw/android.hardware.camera.provider@2.7-service-google.
static void init_hardened_malloc_exclusions(libc_globals* globals) {{
char exe_path[500];
ssize_t n = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (n <= 0) {{
globals->flags = 0;
return;
}}
exe_path[n] = '\\0';
if (strcmp(exe_path, "/system/bin/surfaceflinger") == 0 ||
strstr(exe_path, "camera") != nullptr) {{
globals->flags = GLOBAL_FLAG_DISABLE_HARDENED_MALLOC;
}} else {{
globals->flags = 0;
}}
}}
"""
# __libc_preinit_impl must stay noinline (stack protector guard setup).
noinline = "__attribute__((noinline))\n"
anchor = noinline + "static void __libc_preinit_impl() {"
misplaced = noinline + helper + "static void __libc_preinit_impl() {"
if misplaced in text:
text = text.replace(misplaced, helper + anchor, 1)
if "init_hardened_malloc_exclusions" not in text:
if anchor not in text:
sys.exit(f"preinit anchor missing in {init}")
text = text.replace(anchor, helper + anchor, 1)
if anchor not in text:
sys.exit(f"__libc_preinit_impl lost noinline in {init}")
old_mutate = " __libc_globals.mutate(__libc_init_malloc);\n"
new_mutate = """ __libc_globals.mutate([](libc_globals* globals) {
init_hardened_malloc_exclusions(globals);
__libc_init_malloc(globals);
});
"""
if "init_hardened_malloc_exclusions(globals)" not in text:
if old_mutate not in text:
sys.exit(f"mutate anchor missing in {init}")
text = text.replace(old_mutate, new_mutate, 1)
init.write_text(text)
print(f"patched exclusions: {init}")
def patch_init_rc() -> None:
must_replace(
TREE / "system/core/rootdir/init.rc",
" symlink /sys/class/block/${dev.mnt.rootdisk.data} /dev/sys/block/by-name/rootdisk\n",
" symlink /sys/class/block/${dev.mnt.rootdisk.data} /dev/sys/block/by-name/rootdisk\n\n"
f" # {MARKER}: GrapheneOS 16 vm.max_map_count for hardened_malloc\n"
" write /proc/sys/vm/max_map_count 1048576\n",
"max-map-count",
)
def main() -> None:
if not (TREE / "bionic/libc/Android.bp").is_file():
sys.exit(f"not an Android tree: {TREE}")
ensure_library()
patch_bionic()
patch_init_rc()
print(f"done: {MARKER}")
if __name__ == "__main__":
main()
@@ -0,0 +1,51 @@
#include <errno.h>
#include <malloc.h>
#include <sys/param.h>
#include <unistd.h>
#include <private/MallocXmlElem.h>
#include "h_malloc.h"
__BEGIN_DECLS
int h_malloc_info(int options, FILE* fp);
__END_DECLS
int h_malloc_info(int options, FILE* fp) {
if (options != 0) {
errno = EINVAL;
return -1;
}
fflush(fp);
int fd = fileno(fp);
MallocXmlElem root(fd, "malloc", "version=\"jemalloc-1\"");
// Dump all of the large allocations in the arenas.
for (size_t i = 0; i < h_mallinfo_narenas(); i++) {
struct mallinfo mi = h_mallinfo_arena_info(i);
if (mi.hblkhd != 0) {
MallocXmlElem arena_elem(fd, "heap", "nr=\"%d\"", i);
{
MallocXmlElem(fd, "allocated-large").Contents("%zu", mi.ordblks);
MallocXmlElem(fd, "allocated-huge").Contents("%zu", mi.uordblks);
MallocXmlElem(fd, "allocated-bins").Contents("%zu", mi.fsmblks);
size_t total = 0;
for (size_t j = 0; j < h_mallinfo_nbins(); j++) {
struct mallinfo mi = h_mallinfo_bin_info(i, j);
if (mi.ordblks != 0) {
MallocXmlElem bin_elem(fd, "bin", "nr=\"%d\"", j);
MallocXmlElem(fd, "allocated").Contents("%zu", mi.ordblks);
MallocXmlElem(fd, "nmalloc").Contents("%zu", mi.uordblks);
MallocXmlElem(fd, "ndalloc").Contents("%zu", mi.fordblks);
total += mi.ordblks;
}
}
MallocXmlElem(fd, "bins-total").Contents("%zu", total);
}
}
}
return 0;
}
+5
View File
@@ -1,8 +1,13 @@
# US-S2 — project release keys (Gate G).
# Soong requires paths inside ANDROID_BUILD_TOP (absolute $HOME/.android-certs panics).
# Host: copy keys into vendor/sunfish-a16/certs/ (gitignored). See docs/009-hardening-signing-checklist.md
# apply_009_axp_identity.py copies them to vendor/lineage-priv/keys, the only
# path for which build/make/core/config.mk sets BUILD_KEYS := release-keys.
_AXP_CERT_DIR := vendor/sunfish-a16/certs
ifneq ($(wildcard vendor/lineage-priv/keys/releasekey.pk8),)
_AXP_CERT_DIR := vendor/lineage-priv/keys
endif
ifneq ($(wildcard $(_AXP_CERT_DIR)/releasekey.pk8),)
PRODUCT_DEFAULT_DEV_CERTIFICATE := $(_AXP_CERT_DIR)/releasekey