Dokumentiere Philips-TV-Speicherdiagnose und Patch-Engineering
This commit is contained in:
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# Never publish private/raw device artifacts.
|
||||
*.apk
|
||||
*.jar
|
||||
*.odex
|
||||
*.vdex
|
||||
*.art
|
||||
*.db
|
||||
*.db-*
|
||||
*.ab
|
||||
*.img
|
||||
*.bin
|
||||
*.ko
|
||||
*.so
|
||||
*.dll
|
||||
*.exe
|
||||
*.whl
|
||||
*.pyc
|
||||
*.zip
|
||||
*.7z
|
||||
*.tar
|
||||
*.gz
|
||||
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
diagnostics/raw/
|
||||
diagnostics/bugreport/
|
||||
diagnostics/packages/
|
||||
diagnostics/privilege-escalation/
|
||||
diagnostics/selinux/
|
||||
diagnostics/kernel/
|
||||
diagnostics/deep-forensics/kernel/
|
||||
diagnostics/deep-forensics/selinux/
|
||||
diagnostics/deep-forensics/sqlite/
|
||||
diagnostics/deep-forensics/storage/
|
||||
diagnostics/deep-forensics/philips/*.txt
|
||||
|
||||
*SECURITY-001*
|
||||
*private-key*
|
||||
*.key
|
||||
*.p12
|
||||
*.pfx
|
||||
31
PUBLICATION-MANIFEST.md
Normal file
31
PUBLICATION-MANIFEST.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Publication manifest
|
||||
|
||||
Stand: 2026-08-26. Diese Git-Arbeitskopie wurde aus dem lokalen Diagnoseprojekt
|
||||
für ein privates Gitea-Repository zusammengestellt.
|
||||
|
||||
## Enthalten
|
||||
|
||||
- finale Speicher-, EPG-, eMMC-, System- und Bootberichte;
|
||||
- funktionale Philips-Bugreports BUG-001 bis BUG-003 und DESIGN-001;
|
||||
- Phase-5-Reparaturzusammenfassungen ohne Datenbankkopien;
|
||||
- vollständige Phase-6/7-Patch-, Signing-, OTA-, AVB- und Deploymentdokumente;
|
||||
- Quelltext der Offline-Testmodelle und des read-only Health-Checks;
|
||||
- Recovery-/Rollback-Runbooks;
|
||||
- öffentliche Zertifikatfingerprints und unvertrauliche Baseline-Hashes.
|
||||
|
||||
## Bewusst ausgeschlossen
|
||||
|
||||
- vertrauliche Sicherheits- und Privilege-Escalation-/SELinux-Berichte;
|
||||
- rohe Bugreports, Logs, Tombstones, Prozess-/Netzwerk-/Nutzerdaten;
|
||||
- EPG- und andere private SQLite-Datenbanken;
|
||||
- APK, JAR, ODEX, VDEX, ELF, Kernelmodule und Firmware-/Partitionsabbilder;
|
||||
- ADB-Backups, lokale USB-/App-Daten und Recovery-Artefakte;
|
||||
- alte Delta-/Diagnose-ZIPs, Python-Caches und extrahierte Toolchains;
|
||||
- Zugangsdaten, Tokens, private Schlüssel und absolute Benutzerpfade.
|
||||
|
||||
## Integrität
|
||||
|
||||
Die dokumentierten SHA-256-Werte identifizieren lokale Stock-Artefakte, die
|
||||
Binärdateien selbst sind nicht Teil des Repositorys. Die Veröffentlichung darf
|
||||
nicht als flashbares Firmwarepaket oder vollständige Wiederherstellungskopie
|
||||
missverstanden werden.
|
||||
50
README.md
Normal file
50
README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Philips 43PUS7394/12 – Speicherdiagnose und Patch Engineering
|
||||
|
||||
Kuratierte technische Ergebnisse zur Diagnose der wiederkehrenden Meldung
|
||||
„interner Speicher fast voll“ auf einem Philips 43PUS7394/12 mit
|
||||
TPM191E-Plattform und Android TV 12.
|
||||
|
||||
## Ergebnis
|
||||
|
||||
Die Warnung wurde durch eine auf rund 1,62 GiB angewachsene EPG-Datenbank
|
||||
verursacht. Der Philips-Cleanup-Task protokollierte eine Bereinigung, führte
|
||||
aber keine Löschung aus. Eine kontrollierte Reparatur reduzierte die DB auf
|
||||
wenige MiB und gewann rund 1,62 GiB auf `/data`; Guide, Now/Next, Sender,
|
||||
Favoriten, Aufnahmen, Timer und Reminder wurden anschließend validiert.
|
||||
|
||||
Weitere bestätigte Befunde:
|
||||
|
||||
- unbeschränkt wachsende GEL-WorkManager-Abhängigkeitskette;
|
||||
- falscher USB-Freier-Speicher-Wert durch fehlenden `StatFs`-Fallback;
|
||||
- Philips-Speicherwarnung vermischt Kapazität und eMMC-Lebensdauer;
|
||||
- dauerhafte APK-Patches benötigen den Philips-Platform-Signer;
|
||||
- das Gerät verwendet locked/green AVB und einen statischen A/B-OTA-Pfad;
|
||||
- der sichere erste Deploymentkandidat bleibt ohne OEM-Signatur **NO-GO**.
|
||||
|
||||
## Inhalt
|
||||
|
||||
- `diagnostics/` – bereinigte Analyse- und Herstellerberichte;
|
||||
- `patches/` – Patchpläne, Referenz-Diffs, Tests und Deploymentanalyse;
|
||||
- `recovery/` – Notfall- und Rollback-Runbooks;
|
||||
- `tools/epg-health-check/` – rein lesender EPG-Größen-/Snapshot-Check.
|
||||
|
||||
Der Health-Check enthält keine automatische Reparaturfunktion:
|
||||
|
||||
```powershell
|
||||
python .\tools\epg-health-check\health_check.py --serial <TV_IP>:5555
|
||||
```
|
||||
|
||||
## Sicherheits- und Datenschutzgrenze
|
||||
|
||||
Dieses Repository ist eine kuratierte Veröffentlichung und kein vollständiges
|
||||
Gerätebackup. Private Datenbanken, Bugreports, APK/JAR-/Kernel-Binaries,
|
||||
Firmwareabbilder, Zugangsdaten und die vertrauliche Sicherheitsanalyse wurden
|
||||
absichtlich nicht aufgenommen. Siehe `PUBLICATION-MANIFEST.md`.
|
||||
|
||||
## Wichtige Entscheidungen
|
||||
|
||||
- `patches/PHASE7-DECISION.md`
|
||||
- `patches/phase7/DEPLOYMENT-MATRIX.md`
|
||||
- `diagnostics/vendor-bugs/PHILIPS-PATCH-SUBMISSION.md`
|
||||
- `recovery/README.md`
|
||||
|
||||
526
diagnostics/boot/boot-inventory-agent.md
Normal file
526
diagnostics/boot/boot-inventory-agent.md
Normal file
@@ -0,0 +1,526 @@
|
||||
# Lokale System-, Boot- und Partitionsinventur
|
||||
|
||||
Stand: 2026-08-25. Diese Auswertung wurde **ausschließlich lokal** aus dem
|
||||
vorhandenen Bugreport und bereits gesicherten Textartefakten erstellt. Es gab
|
||||
keine Live-ADB-Abfrage, keinen Reboot, keinen Wechsel in Recovery/Fastboot und
|
||||
keine Änderung am Fernseher.
|
||||
|
||||
## Bewertungsmaßstab
|
||||
|
||||
- **CONFIRMED** – unmittelbar im Bugreport, in Properties, Kernel-Commandline
|
||||
oder Mountinfo beobachtet.
|
||||
- **LIKELY** – mehrere lokale Indizien passen zusammen, aber der entscheidende
|
||||
Rohwert beziehungsweise die Implementierung fehlt.
|
||||
- **POSSIBLE** – technisch mit dem Befund vereinbar, aber ohne positiven
|
||||
lokalen Beleg.
|
||||
- **DISPROVED** – durch die lokale Momentaufnahme direkt widerlegt. Das gilt
|
||||
nur für die untersuchte Firmware und den aufgezeichneten Boot.
|
||||
|
||||
## Quellenkürzel
|
||||
|
||||
- **BR**:
|
||||
`diagnostics/bugreport/extracted/files/01790__bugreport-PH9M_EA_5599-STT2.230526.001-2026-08-25-13-26-44.txt`
|
||||
- **MI**: PID-1-Mountnamespace,
|
||||
`diagnostics/bugreport/extracted/files/00018__mountinfo`
|
||||
- **EA**: Zugriffsprotokoll,
|
||||
`diagnostics/raw/emmc/emmc-filesystem-health-access.txt`
|
||||
- **EI**: eMMC-Identitäts-/Zugriffsprotokoll,
|
||||
`diagnostics/raw/emmc/emmc-identity-and-access.txt`
|
||||
- **FS**: `diagnostics/boot/fstab.mt5887`
|
||||
- **FL**: `diagnostics/boot/fstab.late.mt5887`
|
||||
- **IA**: `diagnostics/boot/vendor-init/hw/init.abupdate.rc`
|
||||
- **KI**: `diagnostics/boot/vendor-init/hw/init.mt5887.rc`
|
||||
- **KC**: lokal gesichertes `/proc/config.gz`,
|
||||
`diagnostics/kernel/proc-config.gz` (SHA-256
|
||||
`2C8DC79DBA98F4555684D0DED59C39232B4A4ADEAA4D92E5625FBB0C24D8F8C6`)
|
||||
|
||||
Zeilenangaben hinter `BR` und `MI` beziehen sich auf diese unveränderten
|
||||
lokalen Dateien.
|
||||
|
||||
## Kurzfazit
|
||||
|
||||
| Aussage | Bewertung | Beleg |
|
||||
|---|---|---|
|
||||
| Android TV 12, API 31, Patchlevel 2025-11-01 | **CONFIRMED** | BR:248208-248211 |
|
||||
| Philips/TPV-Firmware `TPM191E_R.211.000.103.000` | **CONFIRMED** | BR:248246-248247 |
|
||||
| Produktionsbuild `user`, `release-keys`, nicht debuggable | **CONFIRMED** | BR:248190-248200, 248219 |
|
||||
| MediaTek-MT53xx-Plattform, Kernel-Machine `MT5887` | **CONFIRMED** | BR:248049, 248053, 248221-248222; KC: `CONFIG_MACH_MT5887=y`, `CONFIG_SOC_MEDIATEK_MT53XX=y` |
|
||||
| OEM-Chipkennung `MT5599` | **CONFIRMED** als TPV-Property/Produktkennung, nicht als ausgelesene Silizium-ID | BR:247943; zusätzlich BR:10563-10569 |
|
||||
| TPVs Kennung `MT5599` bezeichnet hier dieselbe physische Plattform/Familie wie `mt5887` | **POSSIBLE** | beide Kennungen treten auf demselben Gerät auf; eine physische Aliasbeziehung ist mangels Chip-ID/Herstellerunterlage nicht bewiesen |
|
||||
| Android-Userspace ist ausschließlich 32-Bit ARM (`armeabi-v7a`, `armeabi`) | **CONFIRMED** | BR:248255-248259; Tombstones melden `ABI: 'arm'`, z. B. BR:168856 |
|
||||
| Kernel/CPU-Architektur ist ARM64; Android-Userspace bleibt 32-Bit | **CONFIRMED** | KC: `CONFIG_ARM64=y`, `CONFIG_64BIT=y`, `CONFIG_COMPAT=y`; ABI-Properties in BR:248255-248259 |
|
||||
| Kernel `4.9.310-ab4215`, SMP/PREEMPT, GCC 5.5/Linaro | **CONFIRMED** | BR:12 |
|
||||
| Aktiver Bootslot ist A | **CONFIRMED** | BR:13, 248056 |
|
||||
| Legacy/statisch partitioniertes A/B-System, keine Android-Dynamic-Partitions | **CONFIRMED** für diesen Boot | vollständige `blkdevparts=`-Tabelle in BR:13; kein `super`; direkte physische System-/Vendor-Slots |
|
||||
| AVB/Verified Boot ist gesperrt, Zustand grün | **CONFIRMED** | BR:248050, 248057-248063 |
|
||||
| dm-verity läuft im Enforcing-Modus | **CONFIRMED** | BR:13 und 248064 |
|
||||
| System-as-root über `/dev/dm-0` | **CONFIRMED** | BR:13, 248197; MI:1 |
|
||||
| Öffentliches Bootloader-Unlock / Unlock-Fähigkeit | **UNBEKANNT / nicht belegt** | kein Fastboot-Test, keine Unlock-Ability-Ausgabe und keine modellspezifische Herstellerfreigabe vorhanden |
|
||||
|
||||
## Phase 1 – System- und Buildinventur
|
||||
|
||||
### Geräte- und Firmwaredaten
|
||||
|
||||
| Feld | Wert | Status / Fundstelle |
|
||||
|---|---|---|
|
||||
| Hersteller / Marke | Philips | **CONFIRMED**, BR:248254, 248263 |
|
||||
| Modellproperty | `TPM191E` | **CONFIRMED**, BR:248264 |
|
||||
| konkretes TV-Modell | `43PUS7394/12` | **CONFIRMED**, BR:13 (`device_name`) und BR:247942 |
|
||||
| Produkt / Device / Board | `PH9M_EA_5599` | **CONFIRMED**, BR:248196, 248253, 248260, 248265 |
|
||||
| TPV-Produkt-ID | `TPM191E_R` | **CONFIRMED**, BR:248246 |
|
||||
| Firmware | `TPM191E_R.211.000.103.000` | **CONFIRMED**, BR:248247 |
|
||||
| Android | 12 / `REL` | **CONFIRMED**, BR:248201-248209 |
|
||||
| SDK | 31 | **CONFIRMED**, BR:248210 |
|
||||
| First API level | 28 | **CONFIRMED**, BR:248261; dies belegt die ursprüngliche Plattformgeneration, nicht zwingend das ursprüngliche Verkaufs-ROM |
|
||||
| Security Patch Level | `2025-11-01` | **CONFIRMED**, BR:248211 |
|
||||
| Build-ID | `STT2.230526.001` | **CONFIRMED**, BR:248191, 248195 |
|
||||
| Incremental | `4215` | **CONFIRMED**, BR:248204 |
|
||||
| Buildzeit | `Tue Dec 9 11:16:50 CST 2025` | **CONFIRMED**, BR:248188 |
|
||||
| Buildhost / User | `TWTPSA398-ub1404` / `jenkins` | **CONFIRMED**, BR:248194, 248200 |
|
||||
| Fingerprint | `Philips/PH9M_EA_5599/PH9M_EA_5599:12/STT2.230526.001/4215:user/release-keys` | **CONFIRMED**, BR:6 und 248192 |
|
||||
|
||||
Der vollständige Build-String lautet (BR:248190):
|
||||
|
||||
```text
|
||||
PH9M_EA_5599-user 12 STT2.230526.001 4215 release-keys
|
||||
```
|
||||
|
||||
### Produktions- und ADB-Sicherheitsstatus
|
||||
|
||||
| Property / Beobachtung | Wert | Bewertung |
|
||||
|---|---:|---|
|
||||
| `ro.build.type` | `user` | **CONFIRMED** |
|
||||
| `ro.build.tags` | `release-keys` | **CONFIRMED** |
|
||||
| `ro.debuggable` | `0` | **CONFIRMED** |
|
||||
| `ro.secure` | `1` | **CONFIRMED** |
|
||||
| `ro.adb.secure` | `1` | **CONFIRMED** |
|
||||
| `init.svc.adbd` | `running` | **CONFIRMED**; BR:247638 |
|
||||
| `service.adb.root` | nicht in der vollständigen Propertyliste publiziert | **CONFIRMED** als Nichtfund, nicht gleichbedeutend mit einem Wert `0`; Vendor-Init besitzt nur einen Trigger, der bei Wert `1` den USB-Gadget toggelt und `adbd` neu startet (`init.mtk_common.rc`:74-84) |
|
||||
| SELinux Runtime | `Enforcing`; Shell `uid=2000`, Domain `u:r:shell:s0` | **CONFIRMED** durch `deep-forensics/selinux/access-summary.txt` und Auditzeilen mit `permissive=0`, z. B. BR:68587 |
|
||||
| Build ist `eng` oder `userdebug` | – | **DISPROVED** durch `user`, `release-keys`, `ro.debuggable=0` |
|
||||
|
||||
Fundstellen: BR:248041, 248198-248200, 248219, 248294. Die Kombination ist
|
||||
typisch für einen Production-ADB-Kontext. Ob der Vendor dennoch einen speziell
|
||||
geschützten Engineeringpfad besitzt, folgt daraus nicht.
|
||||
|
||||
### Vendor-/ODM-/Treble-Merkmale
|
||||
|
||||
| Merkmal | Wert | Bewertung |
|
||||
|---|---|---|
|
||||
| `ro.treble.enabled` | `true` | **CONFIRMED**, BR:248325 |
|
||||
| Framework VNDK | 31 | **CONFIRMED**, BR:248380 |
|
||||
| Vendor VNDK | 26 | **CONFIRMED**, BR:248369; älterer Vendor-Unterbau trotz Android-12-Framework |
|
||||
| ODM-Build | Android 12 / SDK 31 / Incremental 4215 / release-keys | **CONFIRMED**, BR:248233-248245 |
|
||||
| Vendor-Build | Android 12 / SDK 31 / Incremental 4215 / release-keys | **CONFIRMED**, BR:248327-248337 |
|
||||
| Vendor SPL | leer | **CONFIRMED**, BR:248331; daraus darf kein Kernel-/Vendor-Patchlevel abgeleitet werden |
|
||||
| `ro.vendor.mtk.oem.lock` | `1` | **CONFIRMED**, BR:248342; Semantik vendorintern, daher nicht allein als Bootloaderbeweis verwenden |
|
||||
| `ro.crypto.state` | `unsupported` | **CONFIRMED**, BR:248217; `/data` erscheint direkt als ext4 auf `mmcblk0p37` |
|
||||
|
||||
## SoC, CPU und ABI
|
||||
|
||||
### Direkt beobachtete Kennungen
|
||||
|
||||
```text
|
||||
ro.board.platform=mt5887
|
||||
ro.boot.hardware=mt5887
|
||||
ro.hardware=mt5887
|
||||
ro.hardware.bootctrl=mt5887
|
||||
persist.odm.tpv.sys.ACR_OemChip=MT5599
|
||||
ro.product.board=PH9M_EA_5599
|
||||
```
|
||||
|
||||
Fundstellen: BR:247943, 248049, 248053, 248221-248222, 248253.
|
||||
|
||||
Die Firmware enthält zudem MediaTek-TV-HALs und -Dienste; der HbbTV-User-Agent
|
||||
nennt `_TV_MT5599_2019` (BR:10563-10569). Damit ist **CONFIRMED**, dass es sich
|
||||
um eine MediaTek/TPV-Plattform handelt und dass TPV sie als `MT5599` ausweist.
|
||||
Gleichzeitig ist **CONFIRMED**, dass Androids Board-/HAL-Plattform `mt5887`
|
||||
heißt. Die Kernelkonfiguration verstärkt diesen Befund wesentlich:
|
||||
`CONFIG_MACH_MT5887=y` und `CONFIG_SOC_MEDIATEK_MT53XX=y`; die alternativen
|
||||
aufgeführten TV-Machines `MT5886`, `MT5893`, `MT5895` usw. sind nicht gesetzt.
|
||||
Die spätere `/proc/cpuinfo`-Abfrage nennt `Hardware: MT5887` und vier
|
||||
Cortex-A53-Kerne, liefert aber ebenfalls keine ausgelesene Silizium-ID. Ohne
|
||||
Device-Tree-`compatible`, Chip-ID-Register oder passende Herstellerunterlage
|
||||
ist die Aussage „MT5887, MT5690 und MT5599 sind exakt dasselbe Silizium“ daher
|
||||
weiterhin **nicht bestätigt**. Die beste lokale Einordnung lautet:
|
||||
|
||||
> **CONFIRMED:** Kernel und Android sind konkret für die MediaTek-Machine
|
||||
> `MT5887` gebaut. **CONFIRMED:** TPV bezeichnet den OEM-Chip/die Produktfamilie
|
||||
> gleichzeitig als `MT5599`. **POSSIBLE:** Beide Kennungen können Ebenen
|
||||
> derselben Plattformfamilie bezeichnen. Die exakte Alias-/Siliziumbeziehung
|
||||
> bleibt offen und darf nicht aus der Namenskorrelation abgeleitet werden.
|
||||
|
||||
### Architektur
|
||||
|
||||
```text
|
||||
ro.product.cpu.abi=armeabi-v7a
|
||||
ro.product.cpu.abi2=armeabi
|
||||
ro.product.cpu.abilist=armeabi-v7a,armeabi
|
||||
ro.product.cpu.abilist32=armeabi-v7a,armeabi
|
||||
ro.product.cpu.abilist64=
|
||||
```
|
||||
|
||||
Dies belegt einen ausschließlich 32-Bit-fähigen Android-Userspace. Native
|
||||
Crashdumps melden `ABI: 'arm'`. Das inzwischen lokal gesicherte
|
||||
`/proc/config.gz` bestätigt jedoch ausdrücklich `Linux/arm64 4.9.310`,
|
||||
`CONFIG_ARM64=y`, `CONFIG_64BIT=y` und `CONFIG_COMPAT=y`. Damit sind ein
|
||||
64-Bit-ARM-Kernel und die 32-Bit-Kompatibilitätsschicht **CONFIRMED**. Der
|
||||
Fernseher verwendet also einen 64-Bit-Kernel mit reinem 32-Bit-Android-
|
||||
Userspace. `CONFIG_NR_CPUS=4` bestätigt, dass dieser Kernel für maximal vier
|
||||
CPUs gebaut ist. Eine spätere rein lesende `/proc/cpuinfo`-Abfrage hat vier
|
||||
Prozessoren mit `CPU part 0xd03` direkt gezählt und damit vier Cortex-A53-Kerne
|
||||
**CONFIRMED**. Das ist eine CPU-Kernidentifikation, keine Silizium-ID für die
|
||||
offene MT5887/MT5690/MT5599-Aliasfrage.
|
||||
|
||||
## Kernelinventur
|
||||
|
||||
Direkte Bugreport-Ausgabe (BR:12):
|
||||
|
||||
```text
|
||||
Linux version 4.9.310-ab4215 (jenkins@TWTPSA398-ub1404)
|
||||
(gcc version 5.5.0 (Linaro GCC 5.5-2017.10))
|
||||
#1 SMP PREEMPT Tue Dec 9 11:04:39 CST 2025
|
||||
```
|
||||
|
||||
| Merkmal | Bewertung |
|
||||
|---|---|
|
||||
| Kernelrelease `4.9.310-ab4215` | **CONFIRMED** |
|
||||
| SMP und PREEMPT | **CONFIRMED** |
|
||||
| Compiler GCC 5.5.0 / Linaro 2017.10 | **CONFIRMED** |
|
||||
| Kernelbuild vom 2025-12-09 | **CONFIRMED** |
|
||||
| `ro.kernel.version=4.9` | **CONFIRMED**, BR:248228 |
|
||||
| exakte Kernelkonfiguration | **CONFIRMED verfügbar** als KC; Header: `Linux/arm64 4.9.310 Kernel Configuration` |
|
||||
| Android-SPL belegt alle Kernel-CVE-Backports | **DISPROVED als Schlussfolgerung**; SPL und Kernelpatchstand sind getrennte Evidenz |
|
||||
|
||||
Der zugängliche Bugreport-Kernelabschnitt war leer beziehungsweise durch
|
||||
`klogctl` beschränkt. `dmesg`, `/proc/cmdline` und `/proc/partitions` waren der
|
||||
ADB-Shell direkt nicht zugänglich (EA:18-26). Dumpstate hat die Kernel-
|
||||
Commandline dennoch im Bugreportkopf gesichert.
|
||||
|
||||
### Relevante Kernelkonfiguration
|
||||
|
||||
| Option | Wert | Bedeutung für diese Inventur |
|
||||
|---|---:|---|
|
||||
| `CONFIG_ARM64`, `CONFIG_64BIT`, `CONFIG_COMPAT` | `y` | **CONFIRMED** ARM64-Kernel mit 32-Bit-Kompatibilität |
|
||||
| `CONFIG_PREEMPT`, `CONFIG_SMP` | `y` | entspricht der Versionszeile |
|
||||
| `CONFIG_IKCONFIG`, `CONFIG_IKCONFIG_PROC` | `y` | erklärt den jetzt gesicherten `/proc/config.gz`-Export |
|
||||
| `CONFIG_CMDLINE_PARTITION` | `y` | Kernel unterstützt das beobachtete `blkdevparts=`-Layout |
|
||||
| `CONFIG_CMDLINE="loop.max_part=7"`, `CONFIG_CMDLINE_EXTEND=y` | gesetzt | eingebaute Minimal-Commandline wird um die vom Bootpfad gelieferte Commandline erweitert |
|
||||
| `CONFIG_EFI_PARTITION` | `y` | GPT-Unterstützung ist einkompiliert; das beweist nicht, dass dieses konkrete Layout von GPT stammt |
|
||||
| `CONFIG_DM_VERITY`, `CONFIG_DM_VERITY_FEC` | `y` | bestätigt dm-verity/FEC-Unterstützung |
|
||||
| `CONFIG_DM_CRYPT` | `y` | erklärt unter anderem Device-Mapper-Verschlüsselung für adopted storage; `/data` ist trotzdem direkt ext4 gemountet |
|
||||
| `CONFIG_EXT4_FS`, `CONFIG_SQUASHFS` | `y` | deckt die beobachteten Dateisysteme ab |
|
||||
| `CONFIG_MMC`, `CONFIG_MMC_BLOCK`, `CONFIG_MMC_MTK` | `y` | MediaTek-MMC-Treiberpfad bestätigt |
|
||||
| `CONFIG_MMC_BLOCK_MINORS` | `64` | passt exakt zur beobachteten Hauptnode plus p1–p63 |
|
||||
| `CONFIG_MACH_MT5887`, `CONFIG_SOC_MEDIATEK_MT53XX` | `y` | konkrete Kernel-Machine und SoC-Familie bestätigt |
|
||||
| `CONFIG_NR_CPUS` | `4` | Kernelobergrenze vier logische CPUs; spätere `/proc/cpuinfo`-Abfrage zählt vier Cortex-A53-Kerne direkt |
|
||||
| `CONFIG_SECURITY_SELINUX`, `CONFIG_DEFAULT_SECURITY_SELINUX` | `y` | SELinux ist Standard-LSM |
|
||||
| `CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE` | `1` | Default ist enforcing; Runtime-Audit meldet ebenfalls `permissive=0` |
|
||||
| `CONFIG_SECCOMP`, `CONFIG_SECCOMP_FILTER` | `y` | Seccomp-Filter verfügbar |
|
||||
| `CONFIG_RANDOMIZE_BASE`, `CONFIG_CC_STACKPROTECTOR_STRONG`, `CONFIG_HARDENED_USERCOPY` | `y` | relevante Kernelhärtungen aktiv |
|
||||
| `CONFIG_USER_NS` | nicht gesetzt | unprivilegierte User-Namespaces nicht verfügbar |
|
||||
| `CONFIG_KEXEC` | nicht gesetzt | kein einkompilierter kexec-Bootpfad |
|
||||
| `CONFIG_DEVMEM`, `CONFIG_DEVKMEM` | nicht gesetzt | keine klassischen `/dev/mem`-/`/dev/kmem`-Interfaces |
|
||||
| `CONFIG_MODULES`, `CONFIG_MODULE_UNLOAD` | `y` | Kernelmodule sind unterstützt |
|
||||
| `CONFIG_MODULE_SIG` | nicht gesetzt | keine Kernel-Modulsignaturprüfung durch diese Option; dies verleiht der Shell **keine** Ladeberechtigung |
|
||||
|
||||
Die Konfiguration erlaubt außerdem SELinux-Development-/Disable-Codepfade
|
||||
(`CONFIG_SECURITY_SELINUX_DEVELOP=y`, `..._DISABLE=y`). Im **tatsächlichen**
|
||||
Boot wurde aber kein Disable-/Permissive-Argument beobachtet; AVB ist locked,
|
||||
die Runtime ist enforcing. Aus der einkompilierten Option folgt daher kein
|
||||
nutzbarer Shell-Schalter.
|
||||
|
||||
## Phase 2 – Boot- und Verified-Boot-Architektur
|
||||
|
||||
### Relevante `ro.boot.*`-Momentaufnahme
|
||||
|
||||
| Property | Wert | Status |
|
||||
|---|---|---|
|
||||
| `ro.boot.bootreason` | `reboot` | **CONFIRMED**, BR:248051; OEM-Properties führen daneben `Cold Boot`/`CrashBoot`, deren Semantik ist nicht identisch |
|
||||
| `ro.boot.hardware` | `mt5887` | **CONFIRMED**, BR:248053 |
|
||||
| `ro.boot.slot_suffix` | `_a` | **CONFIRMED**, BR:248056 |
|
||||
| `ro.boot.dtbo_idx` | `0` | **CONFIRMED**, BR:248052 |
|
||||
| `ro.boot.serialno` | `000070af24c031e7` | **CONFIRMED**, BR:248055 |
|
||||
| `ro.boot.phandle` | leer | **CONFIRMED**, BR:248054 |
|
||||
| `ro.boot.vbmeta.device_state` | `locked` | **CONFIRMED**, BR:248059 |
|
||||
| `ro.boot.verifiedbootstate` | `green` | **CONFIRMED**, BR:248063 |
|
||||
| `ro.boot.veritymode` | `enforcing` | **CONFIRMED**, BR:248064 |
|
||||
|
||||
`ro.boot.flash.locked`, eine Unlock-Ability und ein Rollback-Index werden in
|
||||
den vorhandenen Properties nicht ausgegeben und bleiben **UNBEKANNT**.
|
||||
|
||||
### Rekonstruierter Normalboot
|
||||
|
||||
```text
|
||||
eMMC / mmcblk0
|
||||
-> U-Boot-artige Bootloaderpartitionen (uboot/ubootB + Umgebungen) [LIKELY]
|
||||
-> aktiver Slot _a [CONFIRMED]
|
||||
-> vbmeta_a = mmcblk0p8 [CONFIRMED]
|
||||
-> boot_a = mmcblk0p14 [CONFIRMED als Partitionszuordnung]
|
||||
-> system_a = mmcblk0p15 -> dm-verity -> dm-0 -> / [CONFIRMED]
|
||||
-> vendor_a = mmcblk0p35 -> vendor-verity -> dm-1 -> /vendor [LIKELY für p35-Ziel,
|
||||
CONFIRMED für dm-1/verity]
|
||||
-> linux_rootfs_a = p17 -> dm-2 -> squashfs unter /mnt/vendor/linux_rootfs [LIKELY für p17-Ziel]
|
||||
-> 3rd_a = p19 -> dm-3 -> squashfs unter /mnt/vendor/3rd [LIKELY für p19-Ziel]
|
||||
-> userdata = p37 -> ext4 -> /data [CONFIRMED]
|
||||
```
|
||||
|
||||
Die Bootloaderart ist **LIKELY U-Boot-basiert**, weil die Partitionen wörtlich
|
||||
`uboot`, `ubootB`, `uboot_env` und `uboot_envB` heißen. Eine ausführbare
|
||||
Bootloader-Versionsausgabe fehlt; `ro.bootloader=unknown` ist **CONFIRMED**
|
||||
(BR:7 und 248065). Daher ist weder eine konkrete U-Boot-Version noch deren
|
||||
Kommando-/Unlockoberfläche bestätigt.
|
||||
|
||||
Die lokal gesicherte Fstab bestätigt die Slot- und AVB-Zuordnung unabhängig
|
||||
von der Commandline: `system` und `vendor` sind `avb,slotselect,recoveryonly`
|
||||
(FS:6-7); `linux_rootfs`, `3rd` und `factory` sind ebenfalls `avb,slotselect`
|
||||
(FS:12-14). `boot`, `vbmeta`, `dtbo`, `misc` und `tzbp` werden als rohe
|
||||
`emmc`-Bereiche mit `slotselect` beschrieben (FS:16-20). Die separate
|
||||
`recovery`-Zeile ist auskommentiert (FS:15).
|
||||
|
||||
### System-as-root
|
||||
|
||||
Die Commandline enthält (BR:13):
|
||||
|
||||
```text
|
||||
skip_initramfs root=/dev/dm-0 androidboot.slot_suffix=_a
|
||||
```
|
||||
|
||||
Zusätzlich gilt `ro.build.system_root_image=true` (BR:248197). PID 1 sieht `/`
|
||||
read-only als ext4 auf Major/Minor `254:0`, `/dev/root` (MI:1). Die dm-Tabelle
|
||||
in der Commandline nennt als Daten- und Hashgerät `179:15`, also
|
||||
`mmcblk0p15 = system_a`. Damit sind System-as-root und die Zuordnung von
|
||||
`dm-0` zu `system_a` **CONFIRMED**. `/system` ist in diesem Namespace kein
|
||||
separater Mount, sondern Teil dieses Root-Dateisystems.
|
||||
|
||||
### AVB und dm-verity
|
||||
|
||||
| Feld | Wert | Bewertung |
|
||||
|---|---|---|
|
||||
| `ro.boot.avb_version` | `1.2` | **CONFIRMED**, BR:248050 |
|
||||
| `ro.boot.vbmeta.avb_version` | `1.0` | **CONFIRMED**, BR:248057 |
|
||||
| vbmeta block device | `179:8` = `mmcblk0p8` = `vbmeta_a` | **CONFIRMED**, BR:13, 248058 |
|
||||
| Device state | `locked` | **CONFIRMED**, BR:248059 |
|
||||
| Verified boot state | `green` | **CONFIRMED**, BR:248063 |
|
||||
| Verity mode | `enforcing` | **CONFIRMED**, BR:248064 |
|
||||
| vbmeta hash algorithm | SHA-256 | **CONFIRMED**, BR:248061 |
|
||||
| vbmeta digest | `66eae984496730f2339e5e5531f513645ce32263ac24b95cd80d4f787d701dc6` | **CONFIRMED**, BR:248060 |
|
||||
| vbmeta size | 17,280 Byte | **CONFIRMED**, BR:248062 |
|
||||
| System verity hash tree | SHA-1, 4096-Byte-Blöcke, FEC | **CONFIRMED**, BR:13 |
|
||||
| Fehlerverhalten | `restart_on_corruption`, `ignore_zero_blocks`, `use_fec_from_device` | **CONFIRMED**, BR:13 |
|
||||
| Vendor-Verity | dm-Gerät `vendor-verity` vorhanden | **CONFIRMED**, BR:7325; `/vendor` ist dm-1 ro (MI:8) |
|
||||
|
||||
`locked + green + enforcing` widerlegt für diesen Boot einen bereits
|
||||
entsperrten/orangen Verified-Boot-Zustand. Ein beliebig gepatchtes `boot_a`
|
||||
würde im gegenwärtigen Zustand deshalb **LIKELY** an Signatur-/Verified-Boot-
|
||||
Prüfungen scheitern. Ob Philips/MediaTek einen autorisierten Signaturpfad oder
|
||||
einen Unlockmechanismus implementiert, ist aus den lokalen Daten nicht
|
||||
entscheidbar. Der konkrete AVB-Hashdescriptor für `boot`, dessen Public Key
|
||||
und Rollback-Indizes wurden nicht exportiert; diese Details bleiben bis zur
|
||||
rein lokalen Analyse eines originalen `vbmeta`-/`boot`-Images **UNBEKANNT**.
|
||||
|
||||
## Partitionierung
|
||||
|
||||
### Schema
|
||||
|
||||
Die Kernel-Commandline definiert die gesamte User-Area mit
|
||||
`blkdevparts=mmcblk0:...` (BR:13). Das ist ein statisches, per Bootargument
|
||||
benanntes Partitionslayout. Die 63 angegebenen Größen summieren sich auf
|
||||
14.809 MiB beziehungsweise 14,462 GiB. Ein nominelles 16-GB-eMMC ist damit
|
||||
**LIKELY**, seine exakte Rohgröße bleibt wegen gesperrtem
|
||||
`/sys/block/mmcblk0/size` und `blockdev --getsize64` unbekannt (EI).
|
||||
|
||||
Es gibt kein `super` in der vollständigen Liste und System/Vendor liegen auf
|
||||
festen physischen A/B-Partitionen. Android Dynamic Partitions beziehungsweise
|
||||
ein logisches `super`-Layout sind für diesen Boot daher **DISPROVED**.
|
||||
Ob auf den ersten eMMC-Sektoren zusätzlich eine GPT- oder MBR-Tabelle liegt,
|
||||
ist hingegen **UNBEKANNT**: KC enthält sowohl `CONFIG_MSDOS_PARTITION=y` als
|
||||
auch `CONFIG_EFI_PARTITION=y`, der Rohblock war nicht lesbar. Die tatsächlich
|
||||
vom laufenden Kernel verwendete Namens-/Größenzuordnung ist aber direkt durch
|
||||
`CONFIG_CMDLINE_PARTITION=y` plus `blkdevparts=` **CONFIRMED**.
|
||||
|
||||
Die generischen Android-12-Dateien `snapuserd.rc` und `lpdumpd.rc` ändern diese
|
||||
Bewertung nicht: beide Dienste sind `disabled`; `lpdumpd` enthält nur den
|
||||
AOSP-Defaultnamen `${ro.boot.super_partition:-super}`. Weder eine
|
||||
`ro.boot.super_partition`-Property noch ein `super`-Blockgerät ist im
|
||||
Gerätebefund vorhanden.
|
||||
|
||||
### Vollständige `mmcblk0`-Zuordnung
|
||||
|
||||
Die folgende Tabelle ist in der Reihenfolge aus BR:13 rekonstruiert. Größen
|
||||
sind die wörtlichen `m`-Angaben der Kernel-Commandline.
|
||||
|
||||
| Block | Größe | Name | Block | Größe | Name |
|
||||
|---|---:|---|---|---:|---|
|
||||
| p1 | 2 MiB | `uboot` | p2 | 2 MiB | `ubootB` |
|
||||
| p3 | 2 MiB | `uboot_env` | p4 | 2 MiB | `uboot_envB` |
|
||||
| p5 | 10 MiB | `perm` | p6 | 8 MiB | `dtbo_a` |
|
||||
| p7 | 8 MiB | `dtbo_b` | p8 | 1 MiB | `vbmeta_a` |
|
||||
| p9 | 1 MiB | `vbmeta_b` | p10 | 1 MiB | `ciplus` |
|
||||
| p11 | 2 MiB | `dvbsDB_a` | p12 | 2 MiB | `eeprom_A` |
|
||||
| p13 | 1 MiB | `misc` | p14 | 40 MiB | `boot_a` |
|
||||
| p15 | 2390 MiB | `system_a` | p16 | 40 MiB | `factory_b` |
|
||||
| p17 | 128 MiB | `linux_rootfs_a` | p18 | 1 MiB | `basic_a` |
|
||||
| p19 | 368 MiB | `3rd_a` | p20 | 256 MiB | `3rd_rw` |
|
||||
| p21 | 1 MiB | `Panel_Table_b` | p22 | 1 MiB | `channelA` |
|
||||
| p23 | 1 MiB | `channelB` | p24 | 30 MiB | `pq_a` |
|
||||
| p25 | 5 MiB | `aq_a` | p26 | 1 MiB | `bootlogo` |
|
||||
| p27 | 1 MiB | `al_a` | p28 | 4 MiB | `tzbp_a` |
|
||||
| p29 | 8 MiB | `adsp_a` | p30 | 1 MiB | `part_29` |
|
||||
| p31 | 1 MiB | `part_30` | p32 | 1352 MiB | `upgrade` |
|
||||
| p33 | 40 MiB | `dolby` | p34 | 1 MiB | `SCHEDPVR` |
|
||||
| p35 | 460 MiB | `vendor_a` | p36 | 1 MiB | `odm_a` |
|
||||
| p37 | 5115 MiB | `userdata` | p38 | 1 MiB | `al_b` |
|
||||
| p39 | 40 MiB | `factory_a` | p40 | 1 MiB | `Panel_Table_a` |
|
||||
| p41 | 4 MiB | `r20_a` | p42 | 4 MiB | `r21_a` |
|
||||
| p43 | 4 MiB | `3dlut_a` | p44 | 2 MiB | `dvbsDB_b` |
|
||||
| p45 | 40 MiB | `boot_b` | p46 | 2390 MiB | `system_b` |
|
||||
| p47 | 128 MiB | `linux_rootfs_b` | p48 | 1 MiB | `basic_b` |
|
||||
| p49 | 368 MiB | `3rd_b` | p50 | 30 MiB | `pq_b` |
|
||||
| p51 | 5 MiB | `aq_b` | p52 | 1000 MiB | `user_setting` |
|
||||
| p53 | 8 MiB | `tpv_misc_a` | p54 | 4 MiB | `tzbp_b` |
|
||||
| p55 | 8 MiB | `adsp_b` | p56 | 460 MiB | `vendor_b` |
|
||||
| p57 | 1 MiB | `odm_b` | p58 | 1 MiB | `Panel_Table_b` |
|
||||
| p59 | 4 MiB | `r20_b` | p60 | 4 MiB | `r21_b` |
|
||||
| p61 | 4 MiB | `3dlut_b` | p62 | 1 MiB | `reserved2` |
|
||||
| p63 | 8 MiB | `tpv_misc_b` | | | |
|
||||
|
||||
Die doppelte Bezeichnung `Panel_Table_b` bei p21 und p58 steht **wörtlich** in
|
||||
der Commandline und wurde nicht korrigiert. Welche davon aktiv/legacy ist,
|
||||
bleibt unbekannt.
|
||||
|
||||
Zusätzlich sind `/sys/block/mmcblk0boot0` und `/sys/block/mmcblk0rpmb`
|
||||
**CONFIRMED** vorhanden (BR:156599-156600, 156627-156628). Ein `boot1`-Knoten
|
||||
wurde vom Bugreport nicht aufgeführt; das ist kein Beweis, dass die eMMC-
|
||||
Hardware keinen zweiten Bootbereich besitzt.
|
||||
|
||||
### A/B-OTA
|
||||
|
||||
`ro.build.ab_update=true` (BR:248186) und `ro.boot.slot_suffix=_a` belegen ein
|
||||
A/B-System. Die deklarierte OTA-Liste lautet (BR:248252):
|
||||
|
||||
```text
|
||||
boot,system,vbmeta,vendor,dtbo,linux_rootfs,factory,3rd,tzbp,pq,aq,adsp,
|
||||
tpv_misc,uboot,uboot_env,ubootB,uboot_envB,dolby,r20,r21,3dlut
|
||||
```
|
||||
|
||||
Nicht jede physische Partition ist geslottet; `userdata`, `upgrade`,
|
||||
`user_setting`, `3rd_rw`, `misc`, `perm` und weitere Daten-/Konfigurations-
|
||||
bereiche sind gemeinsam. `update_engine` lief im Bugreport als root-Prozess
|
||||
(BR:159499, 161329) und `init.svc.update_engine=running` (BR:247719).
|
||||
|
||||
`IA:4-21` legt zur Bootzeit die generischen `by-name`-Symlinks auf den
|
||||
aktuellen `${ro.boot.slot_suffix}` um. `IA:24-57` erzeugt für tatsächlich
|
||||
ungeslottete Bereiche künstliche `_a`- und `_b`-Aliase auf denselben Block;
|
||||
besonders wichtig sind hier `misc`, `upgrade`, `userdata`, `uboot` und deren
|
||||
Umgebungen. Die bloße Existenz eines `_a`/`_b`-Alias beweist bei diesem Vendor
|
||||
daher **nicht**, dass zwei physische Kopien existieren.
|
||||
|
||||
Zusätzlich ist ein root laufender Boot-Control-HAL registriert
|
||||
(`android.hardware.boot@1.0::IBootControl default` in
|
||||
`android.hardware.boot@1.0-service.rc`:1-5). Dies bestätigt die A/B-
|
||||
Slotverwaltung. Der Dienst `update_engine` wird in
|
||||
`system-init/update_engine.rc`:8-9 ausdrücklich bei vorhandenem
|
||||
`ro.boot.slot_suffix` aktiviert.
|
||||
|
||||
### Beobachtete Mounts und Dateisysteme
|
||||
|
||||
| Mountpoint | Quelle | FS / Optionen | Status |
|
||||
|---|---|---|---|
|
||||
| `/` | `/dev/root`, Major/Minor 254:0 = dm-0 | ext4, ro | **CONFIRMED**, MI:1 |
|
||||
| `/vendor` | `/dev/block/dm-1` | ext4, ro | **CONFIRMED**, MI:8 |
|
||||
| `/data` | `.../by-name/userdata`, 179:37 | ext4, rw, `data=ordered` | **CONFIRMED**, MI:46 |
|
||||
| `/perm` | `.../by-name/perm`, 179:5 | ext4, ro | **CONFIRMED**, MI:49 |
|
||||
| `/data/ota_package` | `.../by-name/upgrade`, 179:32 | ext4, rw | **CONFIRMED**, MI:51 |
|
||||
| `/data/vendor/user_setting` | `.../by-name/user_setting`, 179:52 | ext4, rw | **CONFIRMED**, MI:61 |
|
||||
| `/data/vendor/3rd_rw` | `.../by-name/3rd_rw`, 179:20 | ext4, rw | **CONFIRMED**, MI:62 |
|
||||
| `/mnt/vendor/linux_rootfs` | dm-2 | squashfs, ro | **CONFIRMED**, MI:47 |
|
||||
| `/mnt/vendor/3rd` | dm-3 | squashfs, ro | **CONFIRMED**, MI:48 |
|
||||
| Adopted USB unter `/mnt/expand/...` | dm-4 | ext4, rw | **CONFIRMED**, MI:64 |
|
||||
| `/storage/emulated` | `/dev/fuse` über adopted storage | FUSE/sdcardfs-Schichten | **CONFIRMED**, MI:77-93 |
|
||||
|
||||
Der df-Snapshot bestätigt `/` mit 2.370.764 KiB, `/vendor` mit 434.492 KiB
|
||||
und `/data` mit 5.089.920 KiB Dateisystemgröße (BR:248515-248525).
|
||||
|
||||
### Vorhandene beziehungsweise fehlende Standardpartitionen
|
||||
|
||||
| erwarteter Bereich | Befund | Bewertung |
|
||||
|---|---|---|
|
||||
| `boot` | `boot_a` p14, `boot_b` p45 | **CONFIRMED** |
|
||||
| `system` | `system_a` p15, `system_b` p46 | **CONFIRMED** |
|
||||
| `vendor` | `vendor_a` p35, `vendor_b` p56 | **CONFIRMED** |
|
||||
| `odm` | kleine `odm_a` p36 / `odm_b` p57, aber kein eigener `/odm`-Mount | Partitionen **CONFIRMED**, Inhalt/Funktion **UNBEKANNT** |
|
||||
| `userdata` | p37, `/data` | **CONFIRMED** |
|
||||
| `misc` | p13 | **CONFIRMED**, nicht normal gemountet |
|
||||
| `vbmeta` | p8/p9 | **CONFIRMED** |
|
||||
| `dtbo` | p6/p7 | **CONFIRMED** |
|
||||
| `recovery` | keine so benannte Partition; Fstab-Zeile ist auskommentiert | klassische separate Recoverypartition **DISPROVED**; Recovery in `boot`/`factory` bleibt **POSSIBLE** |
|
||||
| `cache` | keine so benannte Partition und kein eigener Mount | separate Cachepartition **DISPROVED**; `/data/cache` ist nur ein Datenverzeichnis |
|
||||
| `metadata` | keine so benannte Partition / kein Mount | separate Android-Metadatapartition **DISPROVED** in diesem Layout |
|
||||
| `persist` | keine so benannte Partition | **DISPROVED** als Name; `perm` darf ohne weitere Evidenz nicht mit `persist` gleichgesetzt werden |
|
||||
| `super` | nicht vorhanden | Dynamic-Partition-Schema **DISPROVED** |
|
||||
| `factory` | `factory_a`/`factory_b`; Fstab beschreibt `/factory` als AVB-geschütztes, geslottetes SquashFS | Abbildformat/-schutz **CONFIRMED**, genaue Recovery-/Service-Rolle **LIKELY**, aber nicht vollständig belegt |
|
||||
| `upgrade` | p32, unter `/data/ota_package` gemountet | **CONFIRMED** |
|
||||
|
||||
## Recovery-, Fastboot- und Service-Modi
|
||||
|
||||
| Modus/Fähigkeit | lokale Evidenz | Bewertung |
|
||||
|---|---|---|
|
||||
| Android RecoverySystem/OTA | RecoverySystemService startet (BR:8008); `upgrade`-Partition, UpdateEngine, `uncrypt` und BCB-Dienste vorhanden | **CONFIRMED** als Update-/Recovery-Infrastruktur |
|
||||
| separate klassische Recoverypartition | fehlt in vollständiger Tabelle | **DISPROVED** |
|
||||
| Recovery steckt in `boot_a/b` | mit Layout und fehlender Recoverypartition vereinbar | **POSSIBLE**, nicht verifiziert |
|
||||
| `factory_a/b` enthält ein OEM-Serviceabbild; eine Recovery-Rolle ist möglich | Fstab: AVB-geschütztes SquashFS auf `/factory`; `factory_service.rc` definiert `/factory/bin/factory_service` oneshot in `u:r:vold:s0` | Serviceabbild/-definition **CONFIRMED**; Recovery-Rolle **POSSIBLE**, tatsächlicher Start/Inhalt/Bedienschnittstelle nicht beobachtet |
|
||||
| Vendor-Recovery-Installationshook | `vendor_flash_recovery.rc` definiert `/vendor/bin/install-recovery.sh` als `class main`, oneshot | Definition **CONFIRMED**, tatsächlicher Lauf sowie Ziel/Aktion des nicht gesicherten Skripts **UNBEKANNT** |
|
||||
| BCB-Steuerung | `uncrypt.rc` definiert `setup-bcb` und `clear-bcb`, jeweils disabled/oneshot | **CONFIRMED** als vorhandene, aber nicht ausgelöste Infrastruktur |
|
||||
| TPV-spezifischer One-button-/USB-Recoverypfad | `factory`, `upgrade`, Install-Recovery-Hook und OEM-Pakete sind damit vereinbar, belegen aber keinen konkreten Bedienpfad | **POSSIBLE**, nicht gestartet oder positiv nachgewiesen |
|
||||
| Android-Rebootziel `bootloader` | `system-init/apexd.rc`:15 enthält als Fehleraktion `reboot,bootloader,...` | Zielstring/Anforderung **CONFIRMED**; Verhalten des Bootloaders **UNBEKANNT** |
|
||||
| Bootloader/Fastboot-Transport | keine Sitzung, Bootloader-USB-ID oder Fastbootausgabe im Korpus | **POSSIBLE / UNBEKANNT**; der Rebootzielstring allein beweist kein Fastboot-Protokoll |
|
||||
| `fastbootd` | kein positiver lokaler Beleg; kein `super`/Dynamic-Partitions-Schema | **POSSIBLE**, aber eher **UNLIKELY**; nicht als abwesend bewiesen |
|
||||
| serielle Android-Konsole | Commandline `console=ttyMT0,115200n1`; KI:39-40 startet den Console-Service früh; `system-init/hw/init.rc`:1223-1230 definiert `/system/bin/sh` als User `shell`, Domain `u:r:shell:s0` | Konfiguration **CONFIRMED**; kein Root, keine Pinbelegung/Spannung und kein physischer Zugriff geprüft |
|
||||
| Bootloader ist derzeit entsperrt | AVB meldet locked/green | **DISPROVED** |
|
||||
| Unlock-Fähigkeit | `get_unlock_ability`/`oem device-info` nie sicher abgefragt | **UNBEKANNT** |
|
||||
|
||||
Weder Fastboot noch Recovery sollte allein zum Nachweis neu gebootet werden,
|
||||
solange kein modellgenauer Rückweg und keine sichere Eingabemethode bestätigt
|
||||
sind. Der lokale Befund genügt nur für die Architektur, nicht für die
|
||||
Erreichbarkeit dieser Modi.
|
||||
|
||||
## Offene Punkte und sinnvolle spätere Nur-Lese-Nachweise
|
||||
|
||||
Diese Befehle wurden von diesem Agenten **nicht ausgeführt**. Falls der
|
||||
Hauptauftrag sie später live erlaubt und die Shell sie freigibt, würden sie
|
||||
die verbleibenden Lücken ohne Flashen schließen:
|
||||
|
||||
```text
|
||||
getprop ro.boot.flash.locked
|
||||
getprop ro.boot.dynamic_partitions
|
||||
getprop ro.boot.super_partition
|
||||
bootctl get-current-slot
|
||||
bootctl hal-info
|
||||
ls -lZ /dev/block/bootdevice/by-name
|
||||
cat /proc/device-tree/compatible
|
||||
ls -l /system/bin/fastbootd /system/bin/bootctl /system/bin/update_engine
|
||||
```
|
||||
|
||||
Direkte Reads von `/proc/partitions`, `/proc/cmdline`,
|
||||
`/sys/block/mmcblk0/size` und dem Rohblock wurden bereits als `Permission
|
||||
denied` dokumentiert; wiederholtes Probieren verspricht ohne geänderten
|
||||
Kontext keinen Erkenntnisgewinn. Das Einsteigen in Fastboot/Recovery wäre kein
|
||||
reiner Dateilesezugriff und ist nicht Teil dieser lokalen Auswertung.
|
||||
|
||||
## Lokal ausgeführte Auswertungsschritte
|
||||
|
||||
Es wurden nur Host-Dateien gelesen und Text verarbeitet:
|
||||
|
||||
```powershell
|
||||
rg --files
|
||||
Get-Content diagnostics\bugreport\bugreport-metadata.txt
|
||||
rg -n <Property-, Boot-, AVB-, SoC- und Mountmuster> <lokaler Bugreport>
|
||||
Get-Content <lokaler Bugreport> -First 30
|
||||
Get-Content diagnostics\bugreport\extracted\files\00018__mountinfo
|
||||
Import-Csv -Delimiter "`t" diagnostics\bugreport\extracted\manifest.tsv
|
||||
```
|
||||
|
||||
Die Partitionsnummern und die Größensumme wurden lokal deterministisch aus dem
|
||||
`blkdevparts=mmcblk0:`-Abschnitt bis `blkdevpartenc=` geparst. Kein Befehl in
|
||||
dieser Phase kommunizierte mit `<TV_IP>:5555`.
|
||||
158
diagnostics/bugreport-analysis.md
Normal file
158
diagnostics/bugreport-analysis.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Bugreport-Analyse – Philips 43PUS7394/12
|
||||
|
||||
> **Nachtrag 2026-08-25:** Eine spätere, rein lesende Abfrage des noch
|
||||
> vorhandenen Android-Eventpuffers lieferte
|
||||
> `storaged_emmc_info: [emmc 5.1,1,10,10]`. Die geräteeigene Event-Tagdefinition
|
||||
> weist die Felder als `(mmc_ver,eol,lifetime_a,lifetime_b)` aus. Damit ist
|
||||
> `PRE_EOL_INFO=0x01` (Normal) entgegen den damaligen, nur auf den extrahierten
|
||||
> Haupttext bezogenen Nichtfund-Aussagen inzwischen **bestätigt**. Der Bericht
|
||||
> unterscheidet unten zwischen dem ursprünglichen Haupttext-Nichtfund, dem
|
||||
> späteren Event-Export und dem weiterhin gesperrten direkten Rohbyte.
|
||||
|
||||
Stand: 2026-08-25. Der Bugreport wurde mit dem normalen, autorisierten ADB-
|
||||
Zugang erzeugt. Alle Aktionen am Fernseher waren lesend; Entpacken und Suchen
|
||||
erfolgten ausschließlich im lokalen Workspace.
|
||||
|
||||
## Erzeugung und Vollständigkeit
|
||||
|
||||
| Merkmal | Ergebnis |
|
||||
|---|---|
|
||||
| Datei | `bugreport-PH9M_EA_5599-STT2.230526.001-2026-08-25-13-26-44.zip` |
|
||||
| lokale Größe | 6.420.496 Byte (6,12 MiB) |
|
||||
| lokaler Zeitstempel | 2026-08-25 13:29:05 +02:00 |
|
||||
| ZIP-Einträge | 1.790 |
|
||||
| extrahierte Dateien | 1.792, einschließlich zweier Einträge aus einem eingebetteten ZIP |
|
||||
| extrahierte Nutzdaten | 61.268.602 Byte |
|
||||
| Hauptbericht | 46.511.209 Byte |
|
||||
| Textsuche | 1.787 Dateien geprüft, 5 Binärdateien ausgelassen, 2.450 Trefferzeilen |
|
||||
|
||||
Windows `Expand-Archive` konnte Android-Dateinamen mit `::` und einzelne sehr
|
||||
lange cgroup-Pfade nicht abbilden. Deshalb wurde das ZIP mit
|
||||
`extract_bugreport.py` verlustfrei in flach nummerierte Dateien extrahiert.
|
||||
`extracted/manifest.tsv` ordnet jede lokale Datei ihrem ursprünglichen
|
||||
ZIP-Pfad zu. Dieser Fehler betraf nur die lokale Extraktion, nicht den
|
||||
Bugreport auf dem Fernseher.
|
||||
|
||||
## Dumpstate-Einschränkungen
|
||||
|
||||
Der Hauptbericht ist verwertbar, aber nicht vollständig privilegiert:
|
||||
|
||||
- `perfetto --save-for-bugreport` wird auf diesem `user`-Build nicht unterstützt;
|
||||
- der historische `logcat -L`-Abruf scheiterte;
|
||||
- `iotop` ist nicht installiert;
|
||||
- mehrere HIDL-Debug-Aufrufe, darunter TPV-/MediaTek-Dienste, antworteten mit
|
||||
`FAILED_TRANSACTION`;
|
||||
- `dumpstate` meldet ausdrücklich `No IDumpstateDevice implementation`;
|
||||
- deshalb existiert weder `dumpstate_board.bin` noch `dumpstate_board.txt`;
|
||||
- der Kernel-Log-Abschnitt enthält nur `Unexpected klogctl return value: -1`;
|
||||
- `/proc/last_kmsg` fehlt; der Recovery-`last_kmsg`-Eintrag ist leer;
|
||||
- Dienst-Dumps von UpdateEngineService scheiterten, die separaten
|
||||
Update-Engine-Logdateien sind jedoch vorhanden und erfolgreich.
|
||||
|
||||
`IDumpstateDevice` ist eine optionale OEM-Schnittstelle. Ihr Fehlen bedeutet,
|
||||
dass dieser Build keinen zusätzlichen Philips/TPV-Board-Dump in den
|
||||
Standard-Bugreport einspeist. Die leeren `lshal-debug`-Dateien liefern daher
|
||||
keine weitere eMMC-Gesundheitsinformation. Siehe
|
||||
[AOSP: Scoped vendor logging](https://source.android.com/docs/core/tests/debug/scoped-vendor-logging).
|
||||
|
||||
## eMMC-Befunde
|
||||
|
||||
Der Bugreport bestätigt unabhängig von den vorherigen Einzelabfragen:
|
||||
|
||||
```text
|
||||
[persist.odm.tpv.sys.emmc_lifetime]: [0x0a 0x0a]
|
||||
[init.svc.emmclifereaderd]: [stopped]
|
||||
[ro.boottime.emmclifereaderd]: [11750142162]
|
||||
OnBootLogger: logEMMCDetails devLifeTime = 0x0a 0x0a
|
||||
dev_lt_a:"a", dev_lt_b:"a"
|
||||
TVStorageMonitorService: propvalue = 0x0a 0x0a
|
||||
TVStorageMonitorService: EmmcLifetime 90%-100% device lifetime used
|
||||
```
|
||||
|
||||
Die ursprüngliche native Boot-Zeile
|
||||
`D eMMCLifeReader: eMMC life time 0x0a 0x0a` ist im neuen Bugreport nicht mehr
|
||||
im aktuellen Log-Ring enthalten; sie bleibt in den zuvor gesicherten Bootlogs
|
||||
belegt. Der neue Bericht wiederholt denselben Wert über Property, OnBootLogger
|
||||
und TVStorageMonitorService über mehrere Stunden.
|
||||
|
||||
Neu ist die genaue Debugfs-Quelle, auf die Androids `storaged` zugreifen wollte:
|
||||
|
||||
```text
|
||||
root 2644 storaged: avc: denied { read }
|
||||
path="/sys/kernel/debug/mmc0/mmc0:0001/ext_csd"
|
||||
scontext=u:r:storaged:s0
|
||||
tcontext=u:object_r:debugfs_mmc:s0
|
||||
```
|
||||
|
||||
Damit sind Gerät `mmc0:0001`, ein vorhandener EXT_CSD-Debugfs-Knoten und die
|
||||
SELinux-Sperre direkt belegt. Bemerkenswert ist: Auch der Prozess `storaged`
|
||||
mit Linux-UID root wurde blockiert. Ein bloßer UID-0-Zugang würde den Wert
|
||||
daher nicht automatisch lesbar machen; zusätzlich wäre ein geeigneter
|
||||
SELinux-Domain-/Policy-Zugang erforderlich.
|
||||
|
||||
Für `life_time` und `pre_eol_info` enthält der extrahierte Hauptbericht die
|
||||
Audit-Denials der ADB-Shell. Dort erscheint kein ausgeschriebener
|
||||
`PRE_EOL_INFO`-Wert. Die spätere Eventlog-Abfrage hat den Wert jedoch als
|
||||
`eol=1` bestätigt. Weiterhin fehlen ein vollständiger EXT_CSD-Rohdump, CID und
|
||||
eine Hersteller- oder Modellkennung.
|
||||
|
||||
## Life Time A, Life Time B und PRE_EOL
|
||||
|
||||
| Feld | Befund | Status |
|
||||
|---|---|---|
|
||||
| Life Time A | Philips speichert `0x0a`; OnBootLogger meldet `dev_lt_a="a"` | **belegt: 90–100 % verbraucht** |
|
||||
| Life Time B | Philips speichert `0x0a`; OnBootLogger meldet `dev_lt_b="a"`; TVStorageMonitor wertet diesen zweiten Wert aus | **belegt: 90–100 % verbraucht** |
|
||||
| PRE_EOL_INFO | direkter Pfad für Shell gesperrt; späterer Storaged-Event meldet `eol=1` | **bestätigt: `0x01` (Normal)**; Rohbyte nicht direkt gelesen |
|
||||
|
||||
Die Interpretation von `0x0a` stützt sich auf die JEDEC-Wertetabelle, wie sie
|
||||
beispielsweise in Herstellerunterlagen wiedergegeben wird. Der genaue
|
||||
Verschleiß innerhalb des 10-%-Bandes ist nicht bestimmbar.
|
||||
|
||||
## Prüfung auf Storage-Fehler
|
||||
|
||||
Die automatisierte Suche erfasste Hauptbericht, ANRs, Recovery-Logs,
|
||||
Incident-Report, Update-Engine-Logs und alle eingebetteten Textdateien. Danach
|
||||
wurden die Treffer manuell klassifiziert.
|
||||
|
||||
Nicht gefunden wurden:
|
||||
|
||||
- `EXT4-fs error`, Journal-Fehler oder schreibgeschützte Remounts;
|
||||
- `Buffer I/O error`, Block-I/O-Fehler oder `blk_update_request`;
|
||||
- MMC-Timeouts, CRC-/ECC-Fehler, Controller-Resets oder Command-Fehler;
|
||||
- `disk I/O error`, SQLite-`malformed` oder nachgewiesene DB-Korruption;
|
||||
- OTA-Schreibfehler oder erfolglose Update-Engine-Aktionen;
|
||||
- ein unerwarteter Neustart mit belegtem Storage-Bezug.
|
||||
|
||||
Die vier automatischen Treffer der Kategorie `storage_errors` waren:
|
||||
|
||||
1. ein DNS/SSL-`write error` mit `Try again`;
|
||||
2. ein TLS-Lesefehler nach `Connection reset by peer`;
|
||||
3. ein TLS-Shutdown mit `Broken pipe`;
|
||||
4. `incidentd: Buffer corrupted` beim Lesen eines Incident-Puffers.
|
||||
|
||||
Keiner dieser Treffer ist einem Blockgerät oder Dateisystem zuzuordnen. Alle
|
||||
sechs enthaltenen Update-Engine-Logs enden mit `ErrorCode::kSuccess` und
|
||||
`Processing Done`. PackageManager-Meldungen, dass ältere Systemkopien wegen
|
||||
vorhandener neuerer Updates nicht ausgewählt wurden, sind normale
|
||||
Paket-Selektion und keine Schreibfehler.
|
||||
|
||||
Das Fehlen sichtbarer Fehler ist ausdrücklich **kein Gesundheitsnachweis**:
|
||||
Kernel-`dmesg`, EXT4-Fehlerzähler und OEM-Board-Dumps fehlen. Es bedeutet nur,
|
||||
dass im verfügbaren Diagnosefenster kein Storage-Fehler nachweisbar ist.
|
||||
|
||||
## Ergebnis
|
||||
|
||||
Der Bugreport bestätigt die bisherige Speicherursache und die beiden
|
||||
Philips-eMMC-Werte, liefert aber keinen dritten Health-Wert. Sein stärkster
|
||||
neuer Beitrag ist die direkte Identifikation des EXT_CSD-Knotens und der
|
||||
SELinux-Barriere. Ein unmittelbarer Medienausfall ist nicht belegt; die beiden
|
||||
hohen Life-Time-Bänder bleiben dennoch ein ernstzunehmender Verschleißhinweis.
|
||||
|
||||
## Lokale Artefakte
|
||||
|
||||
- `bugreport/original/`: unverändertes ZIP
|
||||
- `bugreport/extracted/manifest.tsv`: vollständige Pfadzuordnung
|
||||
- `bugreport/analysis/search-hits.tsv`: Treffer mit Quelle, Zeile und Kontext
|
||||
- `bugreport/analysis/search-summary.tsv`: Treffer je Datei/Kategorie
|
||||
- `bugreport/analysis/search-totals.tsv`: Summen je Kategorie
|
||||
- `bugreport/analysis/binary-skipped.tsv`: ausgelassene Binärdateien
|
||||
335
diagnostics/deep-forensics/emmc/emmc-kernel-agent.md
Normal file
335
diagnostics/deep-forensics/emmc/emmc-kernel-agent.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# Tiefe eMMC-/Kernel-Forensik – lokaler Artefaktbefund
|
||||
|
||||
Stand: 2026-08-25
|
||||
Gerät: Philips 43PUS7394/12, TPM191E, Android TV 12
|
||||
Arbeitsweise: ausschließlich lokale, lesende Auswertung bereits gesicherter
|
||||
Bugreport-, Log- und Dokumentationsdateien. Es wurden **keine neuen
|
||||
ADB-Gerätebefehle** ausgeführt und am Fernseher keinerlei Änderungen
|
||||
vorgenommen.
|
||||
|
||||
## Wichtiger Nachtrag – PRE_EOL inzwischen unabhängig belegt
|
||||
|
||||
Eine spätere, rein lesende Live-Abfrage des bereits vorhandenen Android-
|
||||
Eventlogs hat die zentrale damalige Lücke geschlossen. Sie lieferte:
|
||||
|
||||
```text
|
||||
storaged_emmc_info: [emmc 5.1,1,10,10]
|
||||
```
|
||||
|
||||
Die geräteeigene Definition
|
||||
`storaged_emmc_info (mmc_ver),(eol),(lifetime_a),(lifetime_b)` beweist die
|
||||
Feldreihenfolge. Damit meldet Android Storaged für diesen Boot eMMC 5.1,
|
||||
`PRE_EOL_INFO=0x01` (Normal), Lifetime A `0x0a` und Lifetime B `0x0a`.
|
||||
AOSPs Android-12-Implementierung gewinnt diese Werte aus den MMC-sysfs-
|
||||
Attributen beziehungsweise als Fallback aus EXT_CSD-Debugfs. Dieser Befund
|
||||
ist unabhängig von Philips' persistenter Property. Die nachfolgenden Tabellen
|
||||
sind auf diesen Gesamtstand aktualisiert; gesperrt bleiben nur der direkte
|
||||
Shell-Read, der vollständige EXT_CSD-Rohdump, CID/CSD und die
|
||||
Herstelleridentität.
|
||||
|
||||
Primärquellen:
|
||||
[AOSP Storaged Android 12](https://android.googlesource.com/platform/system/core/+/refs/heads/android12-gsi/storaged/storaged_info.cpp),
|
||||
[AOSP EventLogTags](https://android.googlesource.com/platform/system/core/+/master/storaged/EventLogTags.logtags).
|
||||
|
||||
## Kurzfazit
|
||||
|
||||
Philips hat beim Booten zwei eMMC-Lebensdauerwerte `0x0a 0x0a` gemeldet; kurz
|
||||
danach liegt derselbe Wert in einer persistenten Property vor. Mehrere voneinander
|
||||
getrennte Philips-Komponenten bestätigen beide Felder als Life Time A und B;
|
||||
`TVStorageMonitorService` ordnet den zweiten Wert dem Band „90–100 % device
|
||||
lifetime used“ zu. Diese Philips-interne A/B-Aussage ist **direkt und mehrfach
|
||||
belegt**. Nicht unabhängig belegt sind die aktuellen Rohbytes 268/269 des
|
||||
EXT_CSD, weil alle direkten Pfade gesperrt sind.
|
||||
|
||||
Der direkte sysfs-/EXT_CSD-Rohzugriff auf `PRE_EOL_INFO` bleibt der ADB-Shell verwehrt.
|
||||
Der logische Wert ist inzwischen dennoch unabhängig bestätigt: Android
|
||||
Storaged meldet `eol=1`, also `PRE_EOL_INFO=0x01` (Normal). Auch der genaue
|
||||
Debugfs-EXT-CSD-Knoten existiert:
|
||||
`/sys/kernel/debug/mmc0/mmc0:0001/ext_csd`. Androids `storaged` versuchte ihn
|
||||
als Unix-UID `root` zu lesen und wurde von SELinux abgewiesen. Das ist der
|
||||
stärkste direkte Nachweis dafür, dass „UID 0“ auf diesem Produktions-Build
|
||||
allein keinen eMMC-Rohzugriff garantiert.
|
||||
|
||||
Im durchsuchbaren Diagnosefenster gibt es **keinen nachweisbaren MMC-, Block-
|
||||
I/O-, EXT4- oder Journalfehler**. Alle vier automatisch als Storage-Fehler
|
||||
erkannten Zeilen sind Netzwerk- beziehungsweise Incident-Pufferfehler. Diese
|
||||
negative Aussage ist jedoch stark eingeschränkt: `dmesg`, PStore/Last-Kmsg,
|
||||
EXT4-Fehlerzähler und ein OEM-Board-Dump fehlen. Aus dem Nichtfund darf daher
|
||||
weder „eMMC gesund“ noch „kein historischer Medienfehler“ abgeleitet werden.
|
||||
|
||||
## Quellen und Auswertungsumfang
|
||||
|
||||
Primär ausgewertet wurden:
|
||||
|
||||
- `diagnostics/bugreport/extracted/files/01790__bugreport-...txt`
|
||||
(46.511.209 Byte, Standard-Bugreport);
|
||||
- `diagnostics/bugreport/extracted/manifest.tsv` mit 1.790 äußeren Einträgen
|
||||
und zwei zusätzlich extrahierten inneren Dateien;
|
||||
- `diagnostics/bugreport/analysis/search-hits.tsv` und
|
||||
`storage-error-review.txt`;
|
||||
- die sechs Dateien `00012__update_engine...` bis
|
||||
`00017__update_engine...`;
|
||||
- `diagnostics/raw/emmc/emmc-property-init-and-bootlog.txt`;
|
||||
- `diagnostics/raw/emmc/emmc-identity-and-access.txt`;
|
||||
- `diagnostics/raw/emmc/emmc-filesystem-health-access.txt`;
|
||||
- die vorhandenen Berichte `emmc-analysis.md`, `bugreport-analysis.md` und
|
||||
`privilege-analysis.md`.
|
||||
|
||||
Die vorhandene reproduzierbare Analyse hat 1.787 textartige Dateien geprüft,
|
||||
fünf Binärdateien wegen ihres NUL-Anteils ausgelassen und 2.450 kategorisierte
|
||||
Trefferzeilen erzeugt. Zusätzlich wurden die ernsthaften MMC-/EXT4-Muster
|
||||
nochmals exakt über den extrahierten Textkorpus geprüft.
|
||||
|
||||
## Evidenzklassen
|
||||
|
||||
| Kennung | Bedeutung | Anwendung in diesem Bericht |
|
||||
|---|---|---|
|
||||
| **E1 – direkt belegt** | Wortlaut, Pfad, Property, Audit oder Prozesszustand liegt im lokalen Artefakt vor | zum Beispiel `0x0a 0x0a`, AVC-Denial für `ext_csd`, Prozess-UID/-Domain |
|
||||
| **E2 – stark gestützte Schlussfolgerung** | mehrere E1-Indizien bilden eine konsistente Kette, ein entscheidender Rohzugriff fehlt aber | der Boot-Reader gewinnt A/B sehr wahrscheinlich aus dem eMMC-EXT_CSD |
|
||||
| **E3 – begrenzter Nichtfund** | im tatsächlich verfügbaren Textfenster nicht gefunden; fehlende privilegierte Quellen begrenzen die Aussage | keine sichtbaren MMC-/EXT4-Fehler |
|
||||
| **U – unbekannt/nicht prüfbar** | kein Wert oder keine ausreichende Quelle vorhanden | CID/Hersteller, vollständiger EXT_CSD-Rohdump, vollständiger historischer Kernelzustand |
|
||||
|
||||
E3 ist ausdrücklich keine schwächere Form eines Gesundheitsnachweises,
|
||||
sondern eine Aussage über den untersuchten Korpus.
|
||||
|
||||
## Physische und logische Speicherzuordnung
|
||||
|
||||
Folgende Zuordnung ist aus den gesicherten Rohdaten direkt belegt:
|
||||
|
||||
```text
|
||||
MediaTek-Host: 1006d000.MSDC0
|
||||
eMMC-Gerät: mmc0:0001
|
||||
Blockgerät: mmcblk0
|
||||
/data: /dev/block/mmcblk0p37, ext4, rw
|
||||
Debugfs: /sys/kernel/debug/mmc0/mmc0:0001/ext_csd
|
||||
sysfs: /sys/block/mmcblk0/device -> .../mmc0:0001
|
||||
Blocknode: /dev/block/mmcblk0, brw------- root root, major:minor 179:0
|
||||
```
|
||||
|
||||
Der Kernel-Commandline-Eintrag enthält eine statische
|
||||
`blkdevparts=mmcblk0:...`-Partitionstabelle und bestätigt unter anderem die
|
||||
`userdata`-Partition. Die rohe Blockgröße blieb dagegen unlesbar; aus der
|
||||
Partitionstabelle wird deshalb hier weder Modell noch exakte physische
|
||||
Kapazität abgeleitet.
|
||||
|
||||
## Status der eMMC-Gesundheitsinformationen
|
||||
|
||||
| Information | Zugriff/Beobachtung | Blockade | Benötigte Rechte bzw. Funktion | Legitimer risikoarmer Weg | Evidenz |
|
||||
|---|---|---|---|---|---|
|
||||
| Life Time A | Philips-Property `0x0a 0x0a`; OnBootLogger `dev_lt_a="a"`; Storaged-Event `lifetime_a=10` | direkter EXT-CSD-Bytezugriff fehlt | lesender Zugriff auf EXT_CSD-Byte 268 für einen Rohdump | autorisierter TPV/Philips-Serviceexport | exportierter Wert `0x0a` **E1**; Rohbyte weiterhin **U** |
|
||||
| Life Time B | dieselbe Property; `dev_lt_b="a"`; TVStorageMonitor wertet zweiten Wert als 90–100 % aus; Storaged-Event `lifetime_b=10` | direkter EXT-CSD-Bytezugriff fehlt | lesender Zugriff auf EXT_CSD-Byte 269 für einen Rohdump | autorisierter Service-/Engineering-Dump | exportierter Wert `0x0a` **E1**; Rohbyte weiterhin **U** |
|
||||
| `PRE_EOL_INFO` | sysfs-Knoten existiert, direkter Shell-Read erhält AVC-Denial; Storaged-Event meldet `eol=1` | nur der direkte Rohbytezugriff bleibt gesperrt | passende SELinux-Domain nur für unabhängigen Rohdump von EXT-CSD-Byte 267 nötig | signierter OEM-Diagnosedienst bzw. autorisierter Engineering-Build | logischer Wert `0x01` (Normal) **E1**; Rohbyte weiterhin **U** |
|
||||
| vollständiger EXT_CSD | exakter Debugfs-Knoten ist durch Audit belegt | `storaged:s0` wird trotz UID root an `debugfs_mmc:s0` abgewiesen; Shell schon am Verzeichnis | Verzeichnissuche plus `open/read/getattr` für `debugfs_mmc`; ein hierfür erlaubter OEM-/Kernelkontext | Philips/TPV-Servicewerkzeug, das nur lesend exportiert | **E1** für Existenz/Blockade, **U** für Inhalt |
|
||||
| CID | sysfs-Knoten existiert, `cat` durch `shell:s0` abgewiesen | SELinux; zusätzlich Rohblock-DAC ist restriktiv | erlaubter sysfs-Lesekontext | OEM-Board-/Service-Dump | **U** |
|
||||
| CSD | sysfs-Knoten existiert, `cat` durch `shell:s0` abgewiesen | SELinux | erlaubter sysfs-Lesekontext | OEM-Board-/Service-Dump | **U** |
|
||||
| Hersteller/Modell/Serienkennung | `manfid`, `oemid`, `name`, `serial` und weitere Attribute wurden angefragt | AVC-Denials beziehungsweise `Permission denied` | erlaubter sysfs-Lesekontext | OEM-Serviceausgabe; alternativ offizielle Firmware-/BOM-Unterlage | **U** |
|
||||
| Rohblock `/dev/block/mmcblk0` | Node ist `brw------- root root`; `blockdev --getsize64` scheiterte | Unix-DAC und voraussichtlich SELinux-Blockdevice-Policy | Root-DAC **und** erlaubte SELinux-Domain; nur lesender Blockzugriff | OEM-Werkstatttool; kein allgemeiner Root-/Exploitversuch | **E1** für Blockade, **U** für Rohinhalt |
|
||||
| EXT4-Fehlerzähler für `/data` | `/sys/fs/ext4/mmcblk0p37/{errors_count,first_error_time,last_error_time,lifetime_write_kbytes,session_write_kbytes}` sind bekannt | alle Leseversuche `Permission denied` | erlaubter sysfs-/ext4-Diagnosekontext | privilegierter, signierter Read-only-Dump | **U** |
|
||||
| Kernel-Ringpuffer | Bugreport-Sektion: `Unexpected klogctl return value: -1`; direktes `dmesg` war nicht erlaubt | Kernel-/SELinux-Loggingrechte | Dumpstate-/Kernelkontext mit zulässigem `klogctl`, typischerweise zusätzlich entsprechende Capability/Policy | OEM-Bugreport/Engineering-Dump | **U** für nicht sichtbare Kernelereignisse |
|
||||
| PStore/Ramoops/Last-Kmsg | `/proc/last_kmsg` fehlt; Incidentd wird an `pstorefs` abgewiesen und kann `console-ramoops*` nicht öffnen | fehlender Pfad und SELinux-`pstorefs`-Denial | erlaubter PStore-Lesekontext, sofern Daten existieren | autorisierter Crash-/Board-Dump | **U** |
|
||||
| OEM-Board-Dump | `No IDumpstateDevice implementation`; keine `dumpstate_board.bin/.txt` | HAL nicht im VINTF-Manifest implementiert | passende OEM-`IDumpstateDevice`-Implementierung | zukünftige offizielle Philips-Firmware oder Hersteller-Servicebuild | **U** |
|
||||
| Implementierung `emmclifereader` | Init-RC, Root-Start, Dateilabel und Ausgabe sind bekannt | Shell darf an `tpvdebug_exec:s0` weder `getattr` noch `read` | autorisierte Systemimage-/Quellcodeanalyse oder erlaubter Servicekontext | offizielle Firmwaredatei/TPV-Servicepaket lokal analysieren | **E1** für äußere Kette, **U** für internen Codepfad |
|
||||
|
||||
## Beweiskette für `0x0a 0x0a`
|
||||
|
||||
Die gesicherten Artefakte bilden folgende Kette:
|
||||
|
||||
```text
|
||||
init: service emmclifereaderd /system/bin/emmclifereader
|
||||
oneshot, user root, group system
|
||||
|
|
||||
| Boot-Log: eMMCLifeReader: eMMC life time 0x0a 0x0a
|
||||
v
|
||||
persist.odm.tpv.sys.emmc_lifetime = 0x0a 0x0a
|
||||
|
|
||||
+--> OnBootLogger: dev_lt_a="a", dev_lt_b="a"
|
||||
|
|
||||
+--> TVStorageMonitorService:
|
||||
propvalue = 0x0a 0x0a
|
||||
EmmcLifetime 90%-100% device lifetime used
|
||||
```
|
||||
|
||||
Direkt belegt sind Init-Definition, Root-Anforderung, Boot-Ausgabe, persistente
|
||||
Property und beide Philips-Verbraucher. Der Dienst ist beim Bugreport bereits
|
||||
`stopped`, passend zu `oneshot`; `ro.boottime.emmclifereaderd` ist gesetzt.
|
||||
Das ausführbare Objekt trägt das Label `u:object_r:tpvdebug_exec:s0`.
|
||||
|
||||
Nicht direkt belegt sind:
|
||||
|
||||
- die Laufzeit-SELinux-Domain des sehr kurz laufenden Readers;
|
||||
- welcher seiner möglichen Kernelpfade tatsächlich geöffnet wurde;
|
||||
- die im Moment des Bugreports vorhandenen Rohbytes 268/269.
|
||||
|
||||
Die Formulierung „Philips hat beim Boot A/B als `0x0a/0x0a` ermittelt“ ist
|
||||
daher E1. Die engere Formulierung „der Reader hat genau
|
||||
`/sys/kernel/debug/mmc0/mmc0:0001/ext_csd` geöffnet und daraus unmittelbar die
|
||||
Bytes 268/269 gelesen“ bleibt E2. Dass der persistente Wert bloß eine
|
||||
Belegungsanzeige sei, ist durch die getrennte A/B-Telemetrie und die Philips-
|
||||
Zuordnung widerlegt.
|
||||
|
||||
## Unix- und SELinux-Barrieren
|
||||
|
||||
Die Befunde zeigen vier getrennte Zugriffsebenen:
|
||||
|
||||
1. **Unix-DAC:** Der rohe Blocknode ist nur für `root:root` lesbar. Eine
|
||||
normale ADB-Shell scheitert bereits hier.
|
||||
2. **SELinux Type Enforcement:** UID root hebt die Policy nicht auf.
|
||||
`storaged` läuft als root, aber in `u:r:storaged:s0`; der Zielknoten ist
|
||||
`u:object_r:debugfs_mmc:s0`, und `{ read }` wird verweigert.
|
||||
3. **Domain-spezifische Debugfs-Rechte:** `dumpstate:s0` darf einzelne generisch
|
||||
gelabelte Binder-/DMA-Buf-Debugfs-Dateien lesen. Daraus folgt nicht, dass es
|
||||
auch `debugfs_mmc:s0` lesen darf. Die getrennten Zieltypen sind gerade der
|
||||
Zweck der Policy.
|
||||
4. **fehlende Exports/Schnittstellen:** Selbst mit einem geeigneten Prozess
|
||||
muss eine Diagnosefunktion den Wert ausgeben. Der Standard-Bugreport hat
|
||||
keine OEM-`IDumpstateDevice`-Implementierung und exportiert deshalb keinen
|
||||
Board-Health-Dump.
|
||||
|
||||
Weitere direkt beobachtete SELinux-Beispiele stützen diese Einordnung:
|
||||
|
||||
- `shell:s0 -> sysfs:s0`: `life_time`, `pre_eol_info`, CID, CSD, `manfid`,
|
||||
`oemid` und `serial` werden beim Lesen abgewiesen;
|
||||
- `shell:s0 -> debugfs_mmc:s0`: schon `read/search` am Verzeichnis `mmc0`
|
||||
wird abgewiesen;
|
||||
- `shell:s0 -> tpvdebug_exec:s0`: `getattr` und `read` am Reader-Binary werden
|
||||
abgewiesen;
|
||||
- `incidentd:s0 -> pstorefs:s0`: Verzeichnissuche wird abgewiesen;
|
||||
- `tpvdebug:s0 -> proc_uid_io_stats:s0`: TPVs root-Prozess
|
||||
`storagedcapture` erhält wiederholt `getattr`-Denials.
|
||||
|
||||
Damit wäre die Aussage „Root genügt“ technisch unpräzise. Erforderlich ist
|
||||
eine Kombination aus passender Unix-UID/GID beziehungsweise Capability,
|
||||
einer von SELinux zugelassenen Domain, zugänglichen Zieltypen und einer
|
||||
read-only implementierten Ausgabefunktion.
|
||||
|
||||
## Privilegierte und speichernahe Prozesse
|
||||
|
||||
| Prozess/Dienst | beobachtete Unix-Identität | SELinux-Domain | Speicherbezug im Artefakt | Forensische Grenze |
|
||||
|---|---|---|---|---|
|
||||
| `emmclifereaderd` | Init fordert `root`, Gruppe `system`; beim Bugreport beendet | Laufzeitdomain nicht beobachtet; Binary `tpvdebug_exec:s0` | erzeugt direkt beobachtbar die Boot-Logausgabe; steht zeitlich vor der gleichlautenden Property | Property-Schreibcode, Binary und exakter Quellpfad gesperrt; kein PRE_EOL-Export |
|
||||
| `storaged` PID 2644 | `root` | `u:r:storaged:s0` | publiziert eMMC 5.1, EOL 1 und A/B 10/10; versucht zusätzlich `/sys/kernel/debug/mmc0/mmc0:0001/ext_csd` zu lesen | Eventwerte **E1**; zusätzlicher Debugfs-Rohread durch AVC blockiert |
|
||||
| `storagedcapture` PID 2613 | `root` | `u:r:tpvdebug:s0` | TPV-Prozess, liest periodisch Storage-/UID-I/O-Daten und schreibt laut Log eigene Capturedateien | sogar `/proc/uid_io/stats` ist per SELinux gesperrt; keine EXT-CSD-Ausgabe im Bugreport |
|
||||
| `vold` PID 1442 | `root` | `u:r:vold:s0` | Mount-, fscrypt- und Volumeverwaltung; e2fsck des adoptierten `dm-4` | kein eMMC-Health-Export; `Can't find service: vold` im Dumpstate-Log betrifft den Dumpsys-Lookup, nicht den laufenden Daemon |
|
||||
| `installd` PID 2630 | `root` | `u:r:installd:s0` | erkennt Quota-Mounts auf `userdata`, `user_setting` und adoptiertem Volume | Paket-/Quota-Verwaltung, kein roher MMC-/EXT4-Health-Dump |
|
||||
| `system_server` PID 3007 | `system` (UID 1000) | `u:r:system_server:s0` | `StorageManagerService`, `DeviceStorageMonitor` und Paket-/Quota-Koordination | liefert logische Speicherzustände, keine EXT-CSD-/PRE_EOL-Rohwerte |
|
||||
| `TVStorageMonitorService` PID 5636 | `system` (UID 1000) | `u:r:system_app:s0` | liest die TPV-Property etwa alle 30 Sekunden und klassifiziert Life Time B | Property-Verbraucher, kein unabhängiger Hardwareleser |
|
||||
| `OnBootLogger`/`candeebug` PID 4186 | `system` (UID 1000) | `u:r:system_app:s0` | teilt die Property in `dev_lt_a` und `dev_lt_b` und protokolliert Dateisystemtelemetrie | bestätigt A/B semantisch, aber ebenfalls nur abgeleiteter Wert |
|
||||
| Standard-`dumpstate` PID 21293 | `shell` | `u:r:dumpstate:s0` | sammelt den Bugreport und darf ausgewählte Debugfs-Typen lesen | Kernel-Log scheitert; kein `IDumpstateDevice`; kein MMC-spezifischer Board-Dump |
|
||||
| TPV `ddg` PID 2609 | `root` | `u:r:dumpstate:s0` | laufender Debug-Dump-Daemon mit Thread `uart-dumper`; Log meldet nur `ddg alive` | keine eMMC-Healthdaten oder dokumentierte sichere Schnittstelle im Artefakt |
|
||||
| TPV `tpv_top_service` PID 2610 | `root` | `u:r:dumpstate:s0` | TPV-Diagnoseprozess | keine eMMC-/EXT4-Ausgabe im Bugreport |
|
||||
| TPV `errlogservice` PID 2578 | `system` | `u:r:hal_tv_errlogservice_default:s0` | möglicher OEM-Fehlerlogdienst | `lshal debug` endet mit `FAILED_TRANSACTION`; Inhalt nicht verfügbar |
|
||||
|
||||
Die Existenz von Root-Diagnosedaemons ist kein Beleg dafür, dass deren
|
||||
Schnittstellen den gesuchten Wert exportieren oder gefahrlos interaktiv
|
||||
aufrufbar sind. Sie wurden in dieser Untersuchung nicht angesprochen.
|
||||
|
||||
## Prüfung sämtlicher Storage-Fehlertreffer
|
||||
|
||||
Die Kategorie `storage_errors` enthält genau vier Zeilen:
|
||||
|
||||
| Treffer | tatsächlicher Kontext | MMC-/EXT4-Relevanz |
|
||||
|---|---|---|
|
||||
| `resolv: SSL_connect write error ... Try again` | DNS-/TLS-Netzwerkpfad | keine |
|
||||
| `NativeCrypto: ... I/O error ... Connection reset by peer` | TLS-Socket wurde vom Gegenüber zurückgesetzt | keine |
|
||||
| `NativeCrypto: SSL shutdown ... I/O error ... Broken pipe` | TLS-Socket beim Shutdown geschlossen | keine |
|
||||
| `incidentd: Buffer corrupted: expect 145 bytes, read 148 bytes` | Serialisierung beziehungsweise interner Incident-Puffer | keine belegte Blockgeräte- oder Dateisystemzuordnung |
|
||||
|
||||
Zusätzliche exakte Volltextprüfungen ergaben keinen Treffer für:
|
||||
|
||||
- `EXT4-fs` mit Error/Warning/Abort/Corruption;
|
||||
- `Buffer I/O error`, `blk_update_request`, Block-I/O-/End-Request-Fehler;
|
||||
- MMC-Timeout, MMC-CRC/ECC, MMC-Reset oder MMC-Command-Error;
|
||||
- JBD2-/Journalfehler oder Journal-Abbruch;
|
||||
- Remount eines Dateisystems als read-only;
|
||||
- SQLite `disk I/O error`, `database disk image is malformed`;
|
||||
- `ENOSPC` beziehungsweise `No space left on device`.
|
||||
|
||||
Die sechs Update-Engine-Logs enden jeweils mit
|
||||
`ErrorCode::kSuccess` und `Processing Done`. Die davor erscheinende Meldung
|
||||
über nicht aktivierbare Binder-Oneway-Spam-Erkennung (`Invalid argument`) ist
|
||||
kein OTA-Schreibfehler.
|
||||
|
||||
`vold` führte `e2fsck` auf `/dev/block/dm-4` aus und meldete dieses **adoptierte
|
||||
USB-Volume** als `clean`. Das ist kein Check von `/data` auf `mmcblk0p37` und
|
||||
darf nicht als interner eMMC-/EXT4-Gesundheitsbeleg verwendet werden.
|
||||
|
||||
Ein separat gesicherter älterer Bootlog enthält einmal
|
||||
`Icing: Array storage bad crc ...`. Das ist ein beschädigter
|
||||
Google-App-Suchindexdatensatz. Der Ausdruck `crc` stammt hier aus der
|
||||
Anwendungsdatenstruktur, nicht aus dem MMC-Controller. Ohne begleitenden
|
||||
Block-I/O-, EXT4- oder MMC-Fehler bleiben Softwareabbruch, unvollständiger
|
||||
Schreibvorgang, Anwendungsfehler und Medienfehler ununterscheidbar. Auch der
|
||||
vorhandene Google-Icing-Tombstone zeigt einen
|
||||
`Check failed: is_initialized()`-Abort, keinen Blockgerätefehler.
|
||||
|
||||
## Warum der Nichtfund nur E3 ist
|
||||
|
||||
Vier besonders wichtige Quellen fehlen:
|
||||
|
||||
1. Die Kernel-Logsektion ist leer (`klogctl` Rückgabewert `-1`).
|
||||
2. `/proc/last_kmsg` existiert nicht; PStore/Ramoops konnte von Incidentd nicht
|
||||
gelesen werden.
|
||||
3. Die EXT4-Zähler von `mmcblk0p37` sind für Shell gesperrt.
|
||||
4. Mangels `IDumpstateDevice` gibt es keinen OEM-Board-Dump; mehrere TPV-/MTK-
|
||||
HIDL-Debugaufrufe scheitern mit `FAILED_TRANSACTION`.
|
||||
|
||||
Deshalb sind folgende Aussagen **nicht** zulässig:
|
||||
|
||||
- „Die eMMC ist gesund.“
|
||||
- „Es gab nie MMC-/EXT4-Fehler.“
|
||||
- „Der normale PRE_EOL-Wert beweise insgesamt eine gesunde eMMC.“
|
||||
- „Ein Ausfall ist ausgeschlossen.“
|
||||
|
||||
Für die Fehlerhistorie zulässig ist nur: Im verfügbaren Android-/Bugreport-
|
||||
Textfenster ist kein Speichergeräte- oder Dateisystemfehler nachweisbar. Der
|
||||
separat bestätigte PRE_EOL-Wert `0x01` bewertet nur die Reserveblockachse.
|
||||
|
||||
## Forensische Gesamtbewertung
|
||||
|
||||
| Aussage | Bewertung |
|
||||
|---|---|
|
||||
| Philips hat beim erfassten Boot beide Life-Time-Felder als `0x0a` gemeldet | **E1 – direkt belegt** |
|
||||
| Philips versteht A und B als getrennte eMMC-Life-Time-Felder | **E1 – direkt belegt** durch `dev_lt_a`/`dev_lt_b` und DEX-/Laufzeitbefund |
|
||||
| Beide `0x0a` entsprechen dem Band 90–100 % geschätzter Lebensdauer verbraucht | **E1** für Philips' eigene B-Zuordnung; zusätzlich durch die vorhandene JEDEC-/Herstelleranalyse gestützt |
|
||||
| Der Reader liest die Werte aus dem eMMC-EXT_CSD | **E2 – sehr wahrscheinlich**, aber nicht durch Binary oder Rohdump geschlossen |
|
||||
| Der Reader verwendet genau den beobachteten Debugfs-Pfad | **E2**, nicht direkt beobachtet |
|
||||
| `PRE_EOL_INFO` ist normal/warning/urgent | **E1: `0x01` (Normal)** über Storaged-Event und lokale Felddefinition; direkter Rohread bleibt **U** |
|
||||
| CID, Hersteller und konkretes eMMC-Modell | **U** |
|
||||
| Es gibt aktuell sichtbare MMC-/EXT4-/Block-I/O-Fehler | **E3-Nichtfund** im verfügbaren Fenster |
|
||||
| Es gab historisch keine solchen Fehler | **U**, weil Kernel-/PStore-/EXT4-Quellen fehlen |
|
||||
| Die eMMC fällt unmittelbar aus | **nicht belegt** |
|
||||
| Die eMMC weist einen ernstzunehmenden Verschleißindikator auf | **E1**, da A und B im höchsten regulären 10-%-Band stehen |
|
||||
|
||||
## Legitime nächste Beweisquelle
|
||||
|
||||
Der technisch präziseste nächste Schritt wäre kein allgemeiner Root-Zugang,
|
||||
sondern ein **autorisierter Philips/TPV-Diagnosekontext**, der ausschließlich
|
||||
lesend folgende Werte exportiert:
|
||||
|
||||
1. EXT-CSD-Bytes 267, 268 und 269 samt vollständigem Rohdump;
|
||||
2. CID/CSD und Herstellerkennung;
|
||||
3. `/sys/fs/ext4/mmcblk0p37/errors_count`, `first_error_time`,
|
||||
`last_error_time` und `lifetime_write_kbytes`;
|
||||
4. Kernel-/PStore-Zeilen zu `mmc0`, `mmcblk0`, `ext4` und `jbd2`;
|
||||
5. Zeitstempel und Rückgabestatus des Readers, damit ein persistenter Altwert
|
||||
sicher ausgeschlossen wird.
|
||||
|
||||
Legitime Wege wären ein signierter Engineering-/Servicebuild, ein offizielles
|
||||
TPV-Werkstatttool oder eine zukünftige Firmware mit korrekt implementiertem
|
||||
`IDumpstateDevice`. Auch eine lokale statische Analyse einer **offiziell
|
||||
bezogenen** passenden Firmware kann die Reader-Implementierung schließen,
|
||||
ohne das Gerät zu verändern. Nicht erforderlich und nicht angemessen sind
|
||||
Root-Exploit, Bootloader-Entsperrung, Recovery, Flashen oder das manuelle
|
||||
Starten privilegierter TPV-Dienste.
|
||||
|
||||
## Reproduzierbarkeit und Sicherheitsgrenze
|
||||
|
||||
Die entscheidenden lokalen Rohfundstellen wurden gegen eMMC-, Init-, Bootlog-
|
||||
und Bugreport-Ausgaben geprüft. Diese Rohdateien sind aus Datenschutzgründen
|
||||
nicht veröffentlicht. Enthalten bleibt die
|
||||
[Bugreport-Gesamtanalyse](../../bugreport-analysis.md).
|
||||
|
||||
Diese Vertiefung beruht ausschließlich auf bereits vorhandenen lokalen
|
||||
Artefakten. Es wurden keine neuen Geräteabfragen, keine Prozessaufrufe am TV,
|
||||
keine Einstellungsänderungen und keine Speicherzugriffe außerhalb der bereits
|
||||
gesicherten Daten vorgenommen.
|
||||
190
diagnostics/deep-forensics/philips/dex_disasm.py
Normal file
190
diagnostics/deep-forensics/philips/dex_disasm.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Small Dalvik disassembler for a selected locally defined method.
|
||||
|
||||
It builds on dex_probe.py and is intentionally limited to textual forensic
|
||||
inspection. It does not execute or modify the APK.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import struct
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from dex_probe import Dex, iter_instructions, u16, u32
|
||||
|
||||
|
||||
NAMES = {
|
||||
0x00: "nop", 0x01: "move", 0x02: "move/from16", 0x03: "move/16",
|
||||
0x04: "move-wide", 0x05: "move-wide/from16", 0x06: "move-wide/16",
|
||||
0x07: "move-object", 0x08: "move-object/from16", 0x09: "move-object/16",
|
||||
0x0A: "move-result", 0x0B: "move-result-wide", 0x0C: "move-result-object",
|
||||
0x0D: "move-exception", 0x0E: "return-void", 0x0F: "return",
|
||||
0x10: "return-wide", 0x11: "return-object", 0x12: "const/4",
|
||||
0x13: "const/16", 0x14: "const", 0x15: "const/high16",
|
||||
0x16: "const-wide/16", 0x17: "const-wide/32", 0x18: "const-wide",
|
||||
0x19: "const-wide/high16", 0x1A: "const-string", 0x1B: "const-string/jumbo",
|
||||
0x1C: "const-class", 0x1D: "monitor-enter", 0x1E: "monitor-exit",
|
||||
0x1F: "check-cast", 0x20: "instance-of", 0x21: "array-length",
|
||||
0x22: "new-instance", 0x23: "new-array", 0x24: "filled-new-array",
|
||||
0x25: "filled-new-array/range", 0x26: "fill-array-data", 0x27: "throw",
|
||||
0x28: "goto", 0x29: "goto/16", 0x2A: "goto/32",
|
||||
0x2B: "packed-switch", 0x2C: "sparse-switch",
|
||||
0x2D: "cmpl-float", 0x2E: "cmpg-float", 0x2F: "cmpl-double",
|
||||
0x30: "cmpg-double", 0x31: "cmp-long", 0x32: "if-eq", 0x33: "if-ne",
|
||||
0x34: "if-lt", 0x35: "if-ge", 0x36: "if-gt", 0x37: "if-le",
|
||||
0x38: "if-eqz", 0x39: "if-nez", 0x3A: "if-ltz", 0x3B: "if-gez",
|
||||
0x3C: "if-gtz", 0x3D: "if-lez",
|
||||
}
|
||||
|
||||
for i, name in enumerate(("aget", "aget-wide", "aget-object", "aget-boolean", "aget-byte", "aget-char", "aget-short",
|
||||
"aput", "aput-wide", "aput-object", "aput-boolean", "aput-byte", "aput-char", "aput-short"), 0x44):
|
||||
NAMES[i] = name
|
||||
for i, name in enumerate(("iget", "iget-wide", "iget-object", "iget-boolean", "iget-byte", "iget-char", "iget-short",
|
||||
"iput", "iput-wide", "iput-object", "iput-boolean", "iput-byte", "iput-char", "iput-short"), 0x52):
|
||||
NAMES[i] = name
|
||||
for i, name in enumerate(("sget", "sget-wide", "sget-object", "sget-boolean", "sget-byte", "sget-char", "sget-short",
|
||||
"sput", "sput-wide", "sput-object", "sput-boolean", "sput-byte", "sput-char", "sput-short"), 0x60):
|
||||
NAMES[i] = name
|
||||
for i, name in enumerate(("invoke-virtual", "invoke-super", "invoke-direct", "invoke-static", "invoke-interface"), 0x6E):
|
||||
NAMES[i] = name
|
||||
for i, name in enumerate(("invoke-virtual/range", "invoke-super/range", "invoke-direct/range", "invoke-static/range", "invoke-interface/range"), 0x74):
|
||||
NAMES[i] = name
|
||||
|
||||
BIN = [
|
||||
"add-int", "sub-int", "mul-int", "div-int", "rem-int", "and-int", "or-int", "xor-int", "shl-int", "shr-int", "ushr-int",
|
||||
"add-long", "sub-long", "mul-long", "div-long", "rem-long", "and-long", "or-long", "xor-long", "shl-long", "shr-long", "ushr-long",
|
||||
"add-float", "sub-float", "mul-float", "div-float", "rem-float", "add-double", "sub-double", "mul-double", "div-double", "rem-double",
|
||||
]
|
||||
for i, name in enumerate(BIN, 0x90):
|
||||
NAMES[i] = name
|
||||
|
||||
ADDR = [
|
||||
"add-int/2addr", "sub-int/2addr", "mul-int/2addr", "div-int/2addr", "rem-int/2addr", "and-int/2addr", "or-int/2addr", "xor-int/2addr", "shl-int/2addr", "shr-int/2addr", "ushr-int/2addr",
|
||||
"add-long/2addr", "sub-long/2addr", "mul-long/2addr", "div-long/2addr", "rem-long/2addr", "and-long/2addr", "or-long/2addr", "xor-long/2addr", "shl-long/2addr", "shr-long/2addr", "ushr-long/2addr",
|
||||
"add-float/2addr", "sub-float/2addr", "mul-float/2addr", "div-float/2addr", "rem-float/2addr", "add-double/2addr", "sub-double/2addr", "mul-double/2addr", "div-double/2addr", "rem-double/2addr",
|
||||
]
|
||||
for i, name in enumerate(ADDR, 0xB0):
|
||||
NAMES[i] = name
|
||||
|
||||
for i, name in enumerate(("add-int/lit16", "rsub-int", "mul-int/lit16", "div-int/lit16", "rem-int/lit16", "and-int/lit16", "or-int/lit16", "xor-int/lit16"), 0xD0):
|
||||
NAMES[i] = name
|
||||
for i, name in enumerate(("add-int/lit8", "rsub-int/lit8", "mul-int/lit8", "div-int/lit8", "rem-int/lit8", "and-int/lit8", "or-int/lit8", "xor-int/lit8", "shl-int/lit8", "shr-int/lit8", "ushr-int/lit8"), 0xD8):
|
||||
NAMES[i] = name
|
||||
|
||||
|
||||
def s8(v: int) -> int:
|
||||
return struct.unpack("b", bytes([v & 0xFF]))[0]
|
||||
|
||||
|
||||
def s16(v: int) -> int:
|
||||
return struct.unpack("<h", struct.pack("<H", v & 0xFFFF))[0]
|
||||
|
||||
|
||||
def s32(v: int) -> int:
|
||||
return struct.unpack("<i", struct.pack("<I", v & 0xFFFFFFFF))[0]
|
||||
|
||||
|
||||
def ab(unit: int) -> tuple[int, int]:
|
||||
return (unit >> 8) & 0xF, (unit >> 12) & 0xF
|
||||
|
||||
|
||||
def fmt(d: Dex, pc: int, op: int, u: list[int]) -> str:
|
||||
name = NAMES.get(op, f"op_{op:02x}")
|
||||
a4, b4 = ab(u[0])
|
||||
aa = u[0] >> 8
|
||||
|
||||
if op in (0x01, 0x04, 0x07) or 0xB0 <= op <= 0xCF:
|
||||
return f"{name} v{a4}, v{b4}"
|
||||
if op in (0x02, 0x05, 0x08):
|
||||
return f"{name} v{aa}, v{u[1]}"
|
||||
if op in (0x03, 0x06, 0x09):
|
||||
return f"{name} v{u[1]}, v{u[2]}"
|
||||
if op in (0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x1D, 0x1E, 0x27):
|
||||
return f"{name} v{aa}"
|
||||
if op in (0x0E,):
|
||||
return name
|
||||
if op == 0x12:
|
||||
return f"{name} v{a4}, #{s8(b4 if b4 < 8 else b4 - 16)}"
|
||||
if op in (0x13, 0x16):
|
||||
return f"{name} v{aa}, #{s16(u[1])}"
|
||||
if op in (0x14, 0x17):
|
||||
return f"{name} v{aa}, #{s32(u[1] | u[2] << 16)}"
|
||||
if op in (0x15, 0x19):
|
||||
return f"{name} v{aa}, #0x{u[1]:04x}0000"
|
||||
if op == 0x18:
|
||||
val = u[1] | u[2] << 16 | u[3] << 32 | u[4] << 48
|
||||
return f"{name} v{aa}, #0x{val:016x}"
|
||||
if op == 0x1A:
|
||||
return f"{name} v{aa}, {d.strings[u[1]]!r}"
|
||||
if op == 0x1B:
|
||||
idx = u[1] | u[2] << 16
|
||||
return f"{name} v{aa}, {d.strings[idx]!r}"
|
||||
if op in (0x1C, 0x1F, 0x22):
|
||||
return f"{name} v{aa}, {d.types[u[1]]}"
|
||||
if op == 0x20:
|
||||
return f"{name} v{a4}, v{b4}, {d.types[u[1]]}"
|
||||
if op == 0x21:
|
||||
return f"{name} v{a4}, v{b4}"
|
||||
if op == 0x23:
|
||||
return f"{name} v{a4}, v{b4}, {d.types[u[1]]}"
|
||||
if op in (0x24, 0x6E, 0x6F, 0x70, 0x71, 0x72):
|
||||
count = b4
|
||||
g = a4
|
||||
regs = [u[2] & 0xF, (u[2] >> 4) & 0xF, (u[2] >> 8) & 0xF, (u[2] >> 12) & 0xF, g][:count]
|
||||
target = d.types[u[1]] if op == 0x24 else d.methods[u[1]]
|
||||
return f"{name} {{{', '.join('v'+str(x) for x in regs)}}}, {target}"
|
||||
if op in (0x25, 0x74, 0x75, 0x76, 0x77, 0x78):
|
||||
count = aa
|
||||
regs = list(range(u[2], u[2] + count))
|
||||
target = d.types[u[1]] if op == 0x25 else d.methods[u[1]]
|
||||
return f"{name} {{{', '.join('v'+str(x) for x in regs)}}}, {target}"
|
||||
if op in (0x26, 0x2B, 0x2C):
|
||||
off = s32(u[1] | u[2] << 16)
|
||||
return f"{name} v{aa}, :{pc + off:04x}"
|
||||
if op == 0x28:
|
||||
return f"{name} :{pc + s8(aa):04x}"
|
||||
if op == 0x29:
|
||||
return f"{name} :{pc + s16(u[1]):04x}"
|
||||
if op == 0x2A:
|
||||
return f"{name} :{pc + s32(u[1] | u[2] << 16):04x}"
|
||||
if 0x2D <= op <= 0x31 or 0x44 <= op <= 0x51 or 0x90 <= op <= 0xAF:
|
||||
return f"{name} v{aa}, v{u[1] & 0xFF}, v{u[1] >> 8}"
|
||||
if 0x32 <= op <= 0x37:
|
||||
return f"{name} v{a4}, v{b4}, :{pc + s16(u[1]):04x}"
|
||||
if 0x38 <= op <= 0x3D:
|
||||
return f"{name} v{aa}, :{pc + s16(u[1]):04x}"
|
||||
if 0x52 <= op <= 0x5F:
|
||||
return f"{name} v{a4}, v{b4}, {d.fields[u[1]]}"
|
||||
if 0x60 <= op <= 0x6D:
|
||||
return f"{name} v{aa}, {d.fields[u[1]]}"
|
||||
if 0xD0 <= op <= 0xD7:
|
||||
return f"{name} v{a4}, v{b4}, #{s16(u[1])}"
|
||||
if 0xD8 <= op <= 0xE2:
|
||||
return f"{name} v{aa}, v{u[1] & 0xFF}, #{s8(u[1] >> 8)}"
|
||||
return name + " " + " ".join(f"0x{x:04x}" for x in u)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("apk", type=Path)
|
||||
ap.add_argument("--method", required=True, help="regex matched against full DEX method name")
|
||||
args = ap.parse_args()
|
||||
rx = re.compile(args.method, re.I)
|
||||
with zipfile.ZipFile(args.apk) as zf:
|
||||
for name in sorted(n for n in zf.namelist() if re.fullmatch(r"classes\d*\.dex", n)):
|
||||
d = Dex(zf.read(name), f"{args.apk}!/{name}")
|
||||
for em in d.encoded_methods:
|
||||
method = d.methods[em.method_idx]
|
||||
if not rx.search(method):
|
||||
continue
|
||||
print(f"\n### {d.source}\n{method}")
|
||||
print(f"registers={u16(d.data, em.code_off)} ins={u16(d.data, em.code_off+2)} outs={u16(d.data, em.code_off+4)}")
|
||||
for pc, op, units in iter_instructions(d.data, em.code_off):
|
||||
print(f"{pc:04x}: {fmt(d, pc, op, units)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
363
diagnostics/deep-forensics/philips/epg-reverse-agent.md
Normal file
363
diagnostics/deep-forensics/philips/epg-reverse-agent.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# Statische Reverse-Analyse von `EpgDatabase.db`
|
||||
|
||||
Stand: 2026-08-25
|
||||
Zielplattform: Philips TPM191E, Firmware `TPM191E_R.211.000.103.000`, Android TV 12
|
||||
Hauptartefakt: `diagnostics/raw/storage/channels.apk`
|
||||
|
||||
## Umfang und Beweisgrade
|
||||
|
||||
Diese Untersuchung war ausschließlich lokal und lesend. Es wurden keine ADB-Gerätebefehle und keine Reparatur-, Lösch-, Reset- oder Rebuild-Aktion auf dem Fernseher ausgeführt. Untersucht wurden die vorhandenen APK-/DEX-Artefakte, deren binäres Manifest und Ressourcen sowie bereits zuvor abgelegte Diagnoseausgaben.
|
||||
|
||||
Die Kennzeichnungen bedeuten:
|
||||
|
||||
- **BEWIESEN**: direkt aus Manifest, DEX-Bytecode, SQL-Literal oder vorhandener Rohdiagnose ableitbar.
|
||||
- **SEHR WAHRSCHEINLICH**: mehrere direkte Belege ergeben dieselbe Erklärung; eine verbleibende Laufzeitunsicherheit besteht.
|
||||
- **PLAUSIBEL**: technisch gut passende Interpretation, deren Entwicklerabsicht oder Laufzeitwirkung nicht direkt belegt ist.
|
||||
- **NICHT VERIFIZIERBAR**: mit den lokal vorhandenen Artefakten nicht entscheidbar.
|
||||
|
||||
## Kurzfazit
|
||||
|
||||
1. **BEWIESEN:** `org.droidtv.channels` verwaltet `EpgDatabase.db` über `org.droidtv.channels.epg.bcepg.EpgDatabaseHelper`, eine Unterklasse von `android.database.sqlite.SQLiteOpenHelper`. Der Konstruktor übergibt den Datenbanknamen `EpgDatabase.db` und die Schemaversion **6**.
|
||||
2. **BEWIESEN:** Die vorgesehene zeitbasierte Stale-Cleanup-Kette endet in `EpgProvider$deleteStaleDataRunnable.run()`. Dieses Runnable berechnet zwei Grenzwerte und protokolliert sie, führt aber **keinerlei Löschung** aus. Es gibt darin weder `SQLiteDatabase.delete/execSQL` noch `ContentResolver.delete/applyBatch` noch einen Aufruf der vorhandenen echten Zeit-Löschmethoden.
|
||||
3. **BEWIESEN:** Der beim Provider-Start ausgeführte Logpfad wird nach 120.000 ms ausgelöst. Die vorhandenen Boot-Logs passen exakt zum Bytecode, beweisen aber keine gelöschte Zeile.
|
||||
4. **BEWIESEN:** Die Rebuild-Pfade löschen Zeilen, komprimieren die SQLite-Datei jedoch nicht. In allen vier DEX-Dateien von `channels.apk` fehlen Literale beziehungsweise Referenzen für `VACUUM`, `PRAGMA auto_vacuum`, `incremental_vacuum` und `wal_checkpoint`.
|
||||
5. **BEWIESEN:** Die bisher kleinen Zeilenzahlen betreffen nur `indextable`, `nowtable`, `nexttable` und `fastipnownexttable`; sie begrenzen weder `scheduletable` noch die beiden FVP-Tabellen. Eine lesende Abfrage des Join-Endpunkts `antenna/scheduletable` lief länger als 120 Sekunden und musste clientseitig abgebrochen werden. Aktive Schedule-/FVP-Daten können daher einen wesentlichen Teil der 1,62 GiB belegen.
|
||||
6. **SEHR WAHRSCHEINLICH:** Der wirkungslose Stale-Cleanup ist ein konkreter Mechanismus, durch den alte Schedule-Daten anwachsen können. Ob die Dateigröße überwiegend aus lebenden Schedule-/FVP-Zeilen, freien Seiten oder interner Fragmentierung besteht, ist ohne `freelist_count`/`dbstat` beziehungsweise DB-Kopie offen.
|
||||
7. **BEWIESEN:** Es gibt interne mutierende Rebuild-URIs, aber keine gefundene Benutzer-Activity, Service-Aktion oder UI-Zeichenkette „EPG zurücksetzen/löschen“. Das Umschalten der TV-Guide-Quelle ist ausdrücklich keine harmlose Reset-Funktion: Ressourcen warnen vor Verlust beziehungsweise Abbruch von Aufnahmen, Erinnerungen und programmierten Aufnahmen.
|
||||
|
||||
## Analysierte Artefakte
|
||||
|
||||
| Artefakt | SHA-256 | Relevanz |
|
||||
|---|---|---|
|
||||
| `diagnostics/raw/storage/channels.apk` | `3A44BC648624F0CC9CD9FEB4CFC3FD33F41323597718E4FCCFC25FE35504D34F` | EPG-Provider, Datenbankhelper, Guide-UI, Aufnahme-/Reminder-Verknüpfungen |
|
||||
| `diagnostics/raw/tvstrings.apk` | `A25724A08369FBF1AE31884044816441C4ABCD9FC0F0116BCDB563FEC44AE923` | lokalisierte TV-Guide-Warntexte |
|
||||
| `diagnostics/raw/TPTvService.apk` | `75F492EE89906C8C267D685D2B7D0CE02BB09F3A6B4CC8456F8B096B9E441ECD` | Quersuche nach externen Rebuild-/Cleanup-Aufrufern |
|
||||
| `diagnostics/raw/eum.apk` | `3B27806AC60B946C47CCAC1BE7EDAD46938CAE10C43C3F2E1F3EF4F9D2D30B98` | Quersuche |
|
||||
| `diagnostics/raw/storage/gel.apk` | `29A68E6B09700ED4CCDF312C0B99EEE0C3F67E329A537FE87ED4341A71D383D7` | Quersuche |
|
||||
| `diagnostics/raw/tvwidgets2k15.apk` | `93876A5FDC637B304BD6EE988A7F7523D957A9B0135AEFD62E3C13A6AC06E496` | Quersuche |
|
||||
| `diagnostics/raw/emmc/usagelogger.apk` | `7416D0F7017B56F2980A4F3042B49A4A6954D9FBACC7FF995509FB8BD9BF6A15` | Quersuche |
|
||||
|
||||
`channels.apk` enthält vier DEX-Dateien: `classes.dex`, `classes2.dex`, `classes3.dex` und `classes4.dex`. Die hier relevante Implementierung liegt überwiegend in `channels.apk!/classes4.dex`.
|
||||
|
||||
Für die reproduzierbare Extraktion wurden die lokalen, dependency-freien Hilfsprogramme `dex_probe.py`, `axml_dump.py` und `resource_strings.py` in diesem Verzeichnis verwendet. Sie dekompilieren nicht heuristisch, sondern lesen DEX-Index-/Klassendaten, Bytecode-Referenzen, binäres Android-Manifest und Resource-Stringpools.
|
||||
|
||||
## Datenbankname, Helper und Version
|
||||
|
||||
### Helper
|
||||
|
||||
- **BEWIESEN:** Klasse `Lorg/droidtv/channels/epg/bcepg/EpgDatabaseHelper;`
|
||||
- **BEWIESEN:** Superklasse `Landroid/database/sqlite/SQLiteOpenHelper;`
|
||||
- **BEWIESEN:** Konstruktor `EpgDatabaseHelper.<init>(android.content.Context)` lädt das Literal `EpgDatabase.db`, setzt die CursorFactory auf `null` und den Versionsparameter auf `6`, bevor `SQLiteOpenHelper.<init>(Context,String,CursorFactory,int)` aufgerufen wird.
|
||||
- **BEWIESEN:** `onCreate(SQLiteDatabase)` ruft ausschließlich `createTables(SQLiteDatabase)` auf.
|
||||
- **BEWIESEN:** In der Helper-Klasse gibt es keine Overrides von `onConfigure()` oder `onOpen()`.
|
||||
|
||||
### Schema v6
|
||||
|
||||
`EpgDatabaseHelper.createTables(SQLiteDatabase)` enthält folgende physischen Tabellen:
|
||||
|
||||
| Tabelle | Exakte Spalten aus dem DDL |
|
||||
|---|---|
|
||||
| `mappingtable` | `mappingid INTEGER PRIMARY KEY AUTOINCREMENT`, `presetid INTEGER`, `svlid INTEGER`, `svlrecid INTEGER` |
|
||||
| `indextable` | `dbitindex INTEGER PRIMARY KEY AUTOINCREMENT`, `satnum`, `sid`, `tsid`, `onid`, `pfshd`, `sdtguidanceinfo TEXT`, `dpsid`, `dptsid`, `pkgid`, `presetnumber`, `presetid`, `favoriteNumber`, `channelname TEXT`, `logoid`, `svlid`, `svlrecid`, `browsable` (nicht als TEXT markierte Felder jeweils `INTEGER`) |
|
||||
| `nowtable` | `_id INTEGER PRIMARY KEY AUTOINCREMENT`, `eventid`, `presetid`, `eventname TEXT`, `version`, `starttime`, `endtime`, `shortinfo TEXT`, `extendedinfo TEXT`, `languageid`, `linktype`, `targetindex`, `genre`, `scrambled`, `rating`, `guidanceinfo TEXT` |
|
||||
| `nexttable` | identisch zu `nowtable` |
|
||||
| `scheduletable` | `_id INTEGER PRIMARY KEY AUTOINCREMENT`, `eventid`, `presetid`, `eventname TEXT`, `version`, `starttime`, `endtime`, `shortinfo TEXT`, `extendedinfo TEXT`, `languageid`, `genre`, `scrambled`, `rating`, `guidanceinfo TEXT` |
|
||||
| `fvpscheduletable` | `channelid`, `onid`, `sid`, `eventid`, `crid`, Start-/Endzeit, Titel-/Synopsis-, Genre-/Rating-, Bild-/On-Demand-, Accessibility-, Delivery-, Video- und Expiry-Felder; zusammengesetzter Primärschlüssel `(channelid,starttime,endtime)` |
|
||||
| `fvpnownexttable` | wie FVP-Schedule mit zusätzlichem `nownext`, `restartstatus` und FVP-Now/Next-Feldern; zusammengesetzter Primärschlüssel `(channelid,starttime,endtime)` |
|
||||
| `channelmapversiontable` | `id INTEGER PRIMARY KEY`, `majorversion INTEGER`, `minorversion INTEGER`, `favouriteversion INTEGER` |
|
||||
| `fastipnownexttable` | `_id INTEGER PRIMARY KEY AUTOINCREMENT`, `channelid`, `eventid`, `programId`, `nownext`, Start-/Endzeit, Titel-/Synopsis-, Genre-/Rating-, Audio-/Video-, `lastmodified`- und `expiretime`-Felder |
|
||||
|
||||
Die vollständigen, ungekürzten DDL-Literale stehen in `epg-dex-context.txt`, insbesondere im Kontext von `EpgDatabaseHelper.createTables()`.
|
||||
|
||||
Definierte Indizes:
|
||||
|
||||
- `mappingindex(svlid,svlrecid)`
|
||||
- `indexdbit(satnum,sid,tsid,onid)`
|
||||
- `indexpresetid(presetid,presetnumber)`
|
||||
- `indexnowendtime(endtime)`, `indexnowevent(presetid)`
|
||||
- `indexnextendtime(endtime)`, `indexnextevent(presetid)`
|
||||
- `indexschedendtime(endtime)`, `indexschedstarttime(starttime)`, `indexschedpresetid(presetid)`, `indexschedeventid(presetid,eventid)`
|
||||
- `ixfvpschedpresetidstet(channelid,starttime,endtime)`
|
||||
- `ixfvpnownext(channelid,starttime,endtime)`
|
||||
|
||||
**BEWIESEN:** `nownexttable` ist trotz des URI-Pfads keine durch `createTables()` angelegte physische Tabelle. `EpgProvider.query()` bildet diesen Endpunkt über den Join
|
||||
|
||||
`indextable AS Dbit LEFT JOIN nowtable AS Now USING(presetid) LEFT JOIN nexttable AS Next USING(presetid)`
|
||||
|
||||
ab. Frühere Rohnotizen, die `nownexttable` als physische Tabelle bezeichnen, sind insofern zu präzisieren.
|
||||
|
||||
## `onUpgrade`, `onDowngrade` und vermeintliche Drop-Funktionen
|
||||
|
||||
### `onUpgrade(SQLiteDatabase,int,int)`
|
||||
|
||||
**BEWIESEN:** Der Bytecode vergleicht den Parameter `newVersion` mit `4`, `5` und `6`:
|
||||
|
||||
- Zielversion 4, 5 oder 6: nur `createTables(db)`; wegen `IF NOT EXISTS` eine additive/idempotente Schemaanlage.
|
||||
- Jede andere Zielversion: `DROP TABLE IF EXISTS` für `indextable`, `nowtable`, `nexttable`, `scheduletable`, `channelmapversiontable` und `fastipnownexttable`, anschließend `createTables(db)`.
|
||||
|
||||
Der Fallback droppt weder `mappingtable` noch `fvpscheduletable` noch `fvpnownexttable`.
|
||||
|
||||
### `onDowngrade(SQLiteDatabase,int,int)`
|
||||
|
||||
**BEWIESEN:** Die Methode protokolliert lediglich `onDowngrade allowed ` und nimmt keine Schemaänderung vor.
|
||||
|
||||
### `resetInstance()`
|
||||
|
||||
**BEWIESEN:** `EpgDatabaseHelper.resetInstance()` setzt nur das statische Singleton-Feld `mEpgDbHelper` auf `null`. Die Methode löscht weder Datei noch Tabellen oder Zeilen.
|
||||
|
||||
### `dropTables()` und `dropIndexTable()`
|
||||
|
||||
Die Namen sind irreführend:
|
||||
|
||||
- **BEWIESEN:** `dropIndexTable()` führt innerhalb einer Transaktion `SQLiteDatabase.delete("indextable", null, null)` aus; die Tabelle selbst bleibt bestehen.
|
||||
- **BEWIESEN:** `dropTables()` führt `SQLiteDatabase.delete(table,null,null)` für `indextable`, `nowtable`, `nexttable`, `scheduletable`, `fvpnownexttable`, `fvpscheduletable`, `channelmapversiontable` und `fastipnownexttable` aus.
|
||||
- **BEWIESEN:** `mappingtable` wird von `dropTables()` nicht geleert. `syncOnChannelTableChange()` leert und rekonstruiert sie später separat aus der kanonischen Senderliste.
|
||||
- **BEWIESEN:** Keine der beiden Methoden führt anschließend `VACUUM` oder eine andere Dateikompaktierung aus.
|
||||
|
||||
## Stale-/Cleanup-Logik
|
||||
|
||||
### Tatsächliche Aufrufketten
|
||||
|
||||
Es existieren zwei Einstiege in dasselbe Runnable:
|
||||
|
||||
1. **Provider-Start**
|
||||
- `EpgProvider$1.run()` führt zunächst `dropIndexTable()`, `syncChannelTable()` und `syncOnChannelTableChange()` aus.
|
||||
- Danach postet die Methode `mDeleteStaleDataRunnable` mit `Handler.postDelayed(..., 120000)`.
|
||||
- Das Literal und die Logmeldung `Posting stale data deletion after 2 mins in onCreate` stimmen überein.
|
||||
|
||||
2. **Alarm/Receiver-Pfad**
|
||||
- Manifest-Receiver: `org.droidtv.channels.epg.bcepg.EpgStaleDataCleanupAlarmReceiver`
|
||||
- Intent-Action: `org.droidtv.channels.epg.bcepg.EpgStaleDataCleanupAlarmReceiver.CLEAR_STALE_DATA`
|
||||
- Data-Scheme: `epgStaleDataCleanup`
|
||||
- `EpgDatabaseHelper.setEpgStaleDataCleanUpAlarm()` erstellt einen expliziten Pending-Broadcast und setzt per `AlarmManager.setExact()` einen RTC-Alarm auf `currentTimeMillis + 43.200.000 ms`, also 12 Stunden.
|
||||
- `EpgStaleDataCleanupAlarmReceiver.onReceive()` ruft `EpgInfoSingleton.cleanEpgStaleData()` auf.
|
||||
- `EpgInfoSingleton$8.run()` führt `ContentResolver.delete(content://org.droidtv.epgdvb/antenna/staledatacleanup,null,null)` aus.
|
||||
- `EpgProvider.delete()` postet dafür `EpgProvider$deleteStaleDataRunnable`.
|
||||
|
||||
**BEWIESEN:** In allen vier DEX-Dateien von `channels.apk` gibt es keinen Invoke-Verweis auf `EpgDatabaseHelper.setEpgStaleDataCleanUpAlarm()`. Der Receiver plant den Alarm nach Empfang nicht erneut. Damit ist die Methode in den lokal sichtbaren direkten Java-Aufrufpfaden verwaist. Ein reflektiver oder aus einem nicht vorhandenen Artefakt kommender Aufruf bleibt **NICHT VERIFIZIERBAR**.
|
||||
|
||||
### Das Cleanup-Runnable löscht nichts
|
||||
|
||||
`Lorg/droidtv/channels/epg/bcepg/EpgProvider$deleteStaleDataRunnable;->run()V` enthält im Uhr-definiert-Zweig exakt folgende relevante Operationen:
|
||||
|
||||
1. `System.currentTimeMillis()`
|
||||
2. Subtraktion des Wide-Literals `1800`
|
||||
3. bei verfügbarem FreeView zusätzlich Subtraktion des Wide-Literals `604800`
|
||||
4. Ausgabe des Ergebnisses über `Log.d("EpgProvider", "Deleting stale data for time " + threshold)`
|
||||
|
||||
**BEWIESEN:** Danach folgt keine Datenoperation. Die vollständige Methodenreferenzliste enthält ausschließlich Clock-/FreeView-Abfragen, StringBuilder/Logging und im Uhr-nicht-definiert-Zweig `Handler.postDelayed()` mit `30000` ms. Sie enthält insbesondere nicht:
|
||||
|
||||
- `SQLiteDatabase.delete`, `execSQL`, `rawQuery` oder eine andere SQLite-Mutation,
|
||||
- `ContentResolver.delete`, `applyBatch`, `update` oder `insert`,
|
||||
- `EpgProgramPump.deleteChannelProgrambytime()` oder `deleteScheduleProgramsByTime()`,
|
||||
- eine native oder sonstige in dieser Methode sichtbare Löschfunktion.
|
||||
|
||||
Damit ist der als Stale-Cleanup bezeichnete Uhr-definiert-Pfad in dieser Firmware funktional ein **No-op mit Logausgabe**.
|
||||
|
||||
### Einheitenfehler ist zusätzlich sichtbar
|
||||
|
||||
- **BEWIESEN:** Der Code verwendet `System.currentTimeMillis()`, subtrahiert die Literale aber direkt als Millisekunden.
|
||||
- **PLAUSIBEL:** `1800` war als 1.800 Sekunden = 30 Minuten und `604800` als 604.800 Sekunden = 7 Tage gemeint; die fehlende Multiplikation mit 1.000 würde daraus 1,8 Sekunden beziehungsweise 10,08 Minuten machen.
|
||||
- **BEWIESEN:** Der vorhandene Boot-Log bestätigt die tatsächliche Einheit: Logzeit `2026-08-25 11:11:48.657`, protokollierter Grenzwert `1787649106837` = lokal `2026-08-25 11:11:46.837`, also rund 1,82 Sekunden früher.
|
||||
- Wegen der vollständig fehlenden Löschoperation ändert selbst dieser falsche Grenzwert derzeit keine Zeilen.
|
||||
|
||||
### Vorhandene echte Zeit-Löschmethoden sind nicht verdrahtet
|
||||
|
||||
Folgende Methoden könnten tatsächlich alte EPG-Zeilen entfernen:
|
||||
|
||||
- `EpgProgramPump.deleteChannelProgrambytime(long)`: drei `ContentProviderOperation.newDelete()`-Operationen mit `endtime < ?`, danach `applyBatch("org.droidtv.epgdvb", ...)`; die drei `mContentUri`-Ziele sind Now, Next und Schedule.
|
||||
- `EpgProgramPump.deleteScheduleProgramsByTime(long)`: `ContentResolver.delete()` für Schedule mit `endtime < ?`.
|
||||
- `EpgSyncThread.deleteScheduleProgramsByTime(long)`: delegiert an die vorige Methode.
|
||||
|
||||
**BEWIESEN:** Es gibt in den vier DEX-Dateien keinen direkten Invoke-Aufrufer für diese drei öffentlichen Methoden. Insbesondere ruft `deleteStaleDataRunnable.run()` keine davon auf. Ein externer/reflektiver Aufruf ist **NICHT VERIFIZIERBAR**, aber für die sichtbare Cleanup-Kette nicht vorhanden.
|
||||
|
||||
Normale EPG-Synchronisation enthält dagegen selektive Lösch-/Update-Pfade wie `deleteProgramsInWindow()` und kanalbezogene Abgleiche. Das erklärt, warum aktuelle Tabellen klein sein können, obwohl der globale Zeit-Cleanup wirkungslos ist.
|
||||
|
||||
## `VACUUM`, `auto_vacuum` und Dateigröße
|
||||
|
||||
- **BEWIESEN:** Quersuche über alle vier `channels.apk`-DEX-Dateien fand kein Literal beziehungsweise keine Referenz für `VACUUM`, `PRAGMA`, `auto_vacuum`, `incremental_vacuum` oder `wal_checkpoint`.
|
||||
- **BEWIESEN:** `dropTables()` und beide Rebuild-Runnables löschen Zeilen, rufen aber selbst weder `VACUUM` noch ein Auto-Vacuum-PRAGMA auf.
|
||||
- **BEWIESEN, aber nicht gerätespezifisch:** Die AOSP-libsqlite-Buildkonfiguration ist mindestens im Android-11-Branch mit `-DSQLITE_DEFAULT_AUTOVACUUM=1` dokumentiert, also Default `FULL`. Der Android-12-Quellbaum besitzt dieselbe Buildstruktur. Dieser Primärquellenhinweis stammt aus der parallelen Plattformanalyse, nicht aus dem Philips-APK.
|
||||
- **SEHR WAHRSCHEINLICH:** Falls TP Vision die AOSP-Option unverändert übernommen und die DB sie bei Anlage geerbt hat, kann SQLite freie Endseiten bei Commits automatisch zurückgeben. Die bloße Abwesenheit eines appseitigen `VACUUM` beweist deshalb **nicht**, dass ein großer Freelist-Anteil bestehen muss.
|
||||
- **NICHT VERIFIZIERBAR:** Die konkrete Vendor-Buildoption sowie der tatsächliche Headerwert von `PRAGMA auto_vacuum`, `page_count`, `freelist_count`, Seitengröße und WAL-Zustand der laufenden Datenbank, da keine lokale Kopie von `EpgDatabase.db` vorliegt und diese Teilanalyse keine Gerätezriffe ausgeführt hat.
|
||||
|
||||
Vorhandene lokale Rohdiagnose (`diagnostics/raw/storage/epg-content-evidence.txt`):
|
||||
|
||||
| Objekt | aktuelle Zeilen | beobachtete IDs |
|
||||
|---|---:|---:|
|
||||
| `indextable` | 358 | `dbitindex` 1.267.806 bis 1.268.163 |
|
||||
| `nowtable` | 195 | `_id` 4.431.681 bis 4.451.328 |
|
||||
| `nexttable` | 162 | `_id` 4.026.589 bis 4.027.685 |
|
||||
| Join-Endpunkt `nownexttable` | 358 | kein physischer AUTOINCREMENT-Schlüssel |
|
||||
| `fastipnownexttable` | 0 | keine aktuellen Zeilen |
|
||||
|
||||
Die Datenbankdatei wurde dort mit `1.742.053.376` Bytes (ca. 1,62 GiB) protokolliert. Diese Tabelle ist ausdrücklich unvollständig: `scheduletable`, `fvpscheduletable` und `fvpnownexttable` wurden damit nicht gezählt.
|
||||
|
||||
- **BEWIESEN:** AUTOINCREMENT-Werte in Millionenhöhe bei nur wenigen hundert aktuellen Zeilen zeigen massiven historischen Insert/Delete-Churn.
|
||||
- **BEWIESEN:** Daraus folgt nicht, dass Millionen Zeilen gleichzeitig noch vorhanden sind, aber ebenso wenig, dass die nicht gemessenen Schedule-/FVP-Tabellen klein sind.
|
||||
- **SEHR WAHRSCHEINLICH:** Der defekte Stale-Cleanup kann insbesondere `scheduletable` über lange Zeit mit abgelaufenen Ereignissen anwachsen lassen.
|
||||
- **NICHT VERIFIZIERBAR:** Der exakte Anteil lebender Schedule-/FVP-Daten, freier Seiten und interner Fragmentierung ohne belastbare Counts beziehungsweise `page_count`, `freelist_count`, `dbstat` oder eine DB-Kopie.
|
||||
|
||||
Die frühere Aussage in `epg-content-evidence.txt`, der Boot-Log zeige eine erfolgte Löschung, wird durch die statische Bytecode-Analyse korrigiert: Er zeigt nur die Ausführung des No-op-Runnables.
|
||||
|
||||
## Query-/URI-Semantik und Grenzen einer risikoarmen Zählung
|
||||
|
||||
### Warum `antenna/scheduletable --projection _id` sehr lange läuft
|
||||
|
||||
**BEWIESEN:** `content://org.droidtv.epgdvb/antenna/scheduletable` wird nicht direkt auf die physische Tabelle abgebildet. `EpgProvider.query()` ersetzt den Tabellenausdruck durch:
|
||||
|
||||
`indextable AS Dbit LEFT JOIN scheduletable AS Schedule USING(presetid)`
|
||||
|
||||
Für eine Client-Projektion `_id` ersetzt der Provider die Spalte ausdrücklich durch `Dbit.dbitindex AS _id`. Die Abfrage zählt oder limitiert damit nicht, sondern liefert eine Join-Zeile pro passendem Schedule-Datensatz. Eine Laufzeit von mehr als 120 Sekunden ist daher mit einer sehr großen Schedule-Menge vereinbar. Sie kann zusätzlich durch Join-/Provider-/SQLite-Latenz beeinflusst sein und ist allein noch kein exakter Rowcount.
|
||||
|
||||
Die physische Tabelle ist auch über `antenna/scheduletablesync` erreichbar. Dieser Pfad vermeidet den Dbit-Join, besitzt aber ebenfalls keinen eingebauten Count- oder Limit-Modus.
|
||||
|
||||
### Projektion und Selection
|
||||
|
||||
- **BEWIESEN:** Für `nownexttable` und `scheduletable` baut der Provider Join-Ausdrücke und schreibt Projektionsnamen mit Unterstrich in qualifizierte Spalten um, zum Beispiel `Schedule_eventid` zu `Schedule.eventid AS Schedule_eventid`.
|
||||
- **BEWIESEN:** Die Sonderprojektion `_id` wird immer zu `Dbit.dbitindex AS _id`. Das ist bei Join-Endpunkten beabsichtigt, bei `fvpscheduletable` aber ungültig, weil dort kein Alias `Dbit` existiert.
|
||||
- **BEWIESEN:** `fvpscheduletable` und `fvpnownexttable` besitzen schemabedingt keine `_id`-Spalte; ihr Primärschlüssel ist `(channelid,starttime,endtime)`.
|
||||
- **BEWIESEN:** Bestimmte Selection-Strings sind keine SQL-Where-Klauseln, sondern Provider-Kommandos: `GetNowNext`, `GetNowNextNoHidden`, `GetSchedule` und `getFastIpNowNextForChannel`. Bei `GetSchedule` setzt der Provider selbst die Bedingung `Schedule_presetid = ? AND Schedule_endtime > ? AND Schedule_starttime < ?` und sortiert nach `Schedule_starttime`.
|
||||
- **BEWIESEN:** Für die Join-Endpunkte tokenisiert der Provider eine normale Selection an Whitespace und ersetzt Unterstriche in Tokens durch Punkte. Komplexe Ausdrücke sind daher nicht verlässlich transparent.
|
||||
- **BEWIESEN:** Zwei Distinct-Temp-XML-AIT-Endpunkte bauen SQL sogar per Stringverkettung auf. Sie sind für die Tabellenzählung nicht relevant und keine empfehlenswerte Diagnosefläche.
|
||||
|
||||
### Sortierung und Limit
|
||||
|
||||
- **BEWIESEN:** Der fünfte Legacy-Query-Parameter `sortOrder` wird zu Beginn nur auf den Sonderwert `PF_EVENT` geprüft. Dieser Wert löst eine EPG-Sync-Notification aus und liefert keinen Cursor.
|
||||
- **BEWIESEN:** Der Client-Sortstring wird danach nicht als freie Sortierung an die abschließende SQLite-Abfrage weitergereicht. Einige interne Pfade setzen stattdessen feste Sortierungen (`Dbit_presetnumber`, `Schedule_starttime`, `starttime`).
|
||||
- **BEWIESEN:** Der letzte Parameter der verwendeten 8-Argument-Überladung `SQLiteDatabase.query(..., orderBy, limit)` wird immer auf `null` gesetzt. Es gibt in diesem Provider keinen generischen URI-Queryparameter oder Legacy-Pfad für `LIMIT 1`.
|
||||
- **BEWIESEN:** Ein `LIMIT` im Sortstring wäre hier nicht nur unerwünschte SQL-Einschleusung, sondern wegen der Providerlogik auch kein belastbarer Weg.
|
||||
|
||||
### Gibt es einen legitimen Count-Weg?
|
||||
|
||||
- **BEWIESEN:** Der Provider implementiert keinen eigenen Count-Endpunkt, kein `DatabaseUtils.queryNumEntries()` und kein festes `SELECT COUNT(*)` für Schedule/FVP.
|
||||
- **NICHT VERIFIZIERBAR:** Ob ein Aggregate-Ausdruck wie `COUNT(*) AS row_count` als Client-Projektion auf jedem URI akzeptiert wird. Die Provider-Umschreibung verwirft oder verändert Projektionen abhängig vom Pfad; auf dem normalen `scheduletable`-Join ist dies deshalb kein dokumentierter, verlässlicher Vertrag.
|
||||
- **PLAUSIBEL:** Die Spezialpfade `antenna/fvpbackwardepg`, `antenna/fvpnownexttable` und `antenna/fastipnownexttable` reichen Projektionen direkter an SQLite weiter; dort könnte ein Aggregate-Count rein lesend funktionieren. Das ist statisch nicht als Philips-API zugesichert und sollte, falls später überhaupt getestet, nur mit kurzer externer Zeitbegrenzung erfolgen.
|
||||
- **BEWIESEN:** Ein garantiert maximal einzeiliger, aber nicht zählender Leseweg besteht über eine eindeutige Schlüssel-Selection: bei `scheduletablesync` `_id = <bekannter Wert>`, bei FVP `(channelid,starttime,endtime)` mit bekannten Werten. Das beantwortet nur Existenz/Einzeldaten, nicht die Gesamtgröße.
|
||||
|
||||
Risikoarme weitere Diagnose bedeutet daher: keine unbeschränkte Ausgabe des Schedule-Joins, keine Sort-/Selection-Injection und keine mutierenden URI-Endpunkte. Sinnvoll sind ausschließlich zeitlich eng begrenzte, indexgestützte Leseselektionen auf der physischen `scheduletablesync`-Tabelle (`starttime`/`endtime`) sowie kurz begrenzte Aggregate-Experimente nur dort, wo die Projektion statisch unverändert durchgereicht wird. Ein vollständiger, verlässlicher Tabellen-/Seitenbefund erfordert letztlich SQLite-Metadaten oder eine lesbare DB-Kopie.
|
||||
|
||||
## Interne Reset-/Rebuild-Flächen
|
||||
|
||||
### ContentProvider
|
||||
|
||||
Manifest-Komponente:
|
||||
|
||||
- Klasse: `org.droidtv.channels.epg.bcepg.EpgProvider`
|
||||
- Authority: `org.droidtv.epgdvb`
|
||||
- `android:exported="true"`
|
||||
- Im Provider-Element ist keine `readPermission` oder `writePermission` deklariert.
|
||||
|
||||
Die folgenden URI-Pfade werden in `EpgUriMatcher` erkannt. **Alle werden über `ContentResolver.delete()` angestoßen und sind deshalb mutierend; sie wurden nicht ausgeführt.**
|
||||
|
||||
| URI | Runnable/Funktion | Tatsächliche Wirkung |
|
||||
|---|---|---|
|
||||
| `content://org.droidtv.epgdvb/antenna/staledatacleanup` | `EpgProvider$deleteStaleDataRunnable` | derzeit nur Grenzwertberechnung/Log; keine Löschung |
|
||||
| `content://org.droidtv.epgdvb/antenna/rebuilddb` | `EpgProvider$rebuildDbRunnable` | `dropIndexTable()`, `syncChannelTable()`, `syncOnChannelTableChange()`, Acquisition-Notification; selektiver Reindex/Mapping-Abgleich |
|
||||
| `content://org.droidtv.epgdvb/antenna/rebuilddbonnewchannellist` | `EpgProvider$rebuildDbOnNewChannelListRunnable` | `dropTables()`, zusätzlich `dropIndexTable()`, danach Kanal-/Mapping-Neuaufbau und Notification; umfassende Zeilenleerung der EPG-Ereignistabellen, aber keine Dateikompaktierung |
|
||||
|
||||
`EpgProgramPump.rebuildEpgDataBase()` ruft den regulären `rebuilddb`-URI über `ContentResolver.delete()` auf. **BEWIESEN:** Auch diese Methode hat innerhalb der vier DEX-Dateien keinen direkten Invoke-Aufrufer.
|
||||
|
||||
Der tatsächlich verdrahtete interne Rebuild erfolgt in `EpgInfoSingleton$MyRunnableHandleMessage.run()`:
|
||||
|
||||
- bei Senderlisten-Update-Ende und Installationsabbruch über `REBUILD_CONTENT_URI` (`rebuilddb`),
|
||||
- bei `CHANNEL_INSTALL_COMPLETE` über `REBUILD_ONNEWCHLIST_CONTENT_URI` (`rebuilddbonnewchannellist`).
|
||||
|
||||
Das ist ein interner Lifecycle-Pfad der Senderinstallation, keine gefundene Benutzerfunktion zum Freigeben von Speicher.
|
||||
|
||||
### Manifest-Activities, Services und Intent-Actions
|
||||
|
||||
Gefundene, EPG-nahe Benutzerkomponenten sind:
|
||||
|
||||
- `org.droidtv.channels.ChannelsActivity`: `org.droidtv.action.GUIDE`, `org.droidtv.action.DUKTPGUIDE`, `org.droidtv.intent.action.SHOW_PLANNER`, `org.droidtv.intent.action.CHANLIST`
|
||||
- `org.droidtv.channels.epg.bcepg.epgui.RecordingsManager`: `org.droidtv.ACTION.SHOW_RECORDINGS_LIST`
|
||||
- `org.droidtv.channels.epg.bcepg.epgui.NowNextOverview`: `org.droidtv.ACTION.SHOW_NOWNEXT_OVERVIEW`
|
||||
- `org.droidtv.channels.epg.bcepg.epgui.ScheduleReminder`: `org.droidtv.ACTION.SET_REMINDER`
|
||||
- `org.droidtv.channels.epg.bcepg.epgui.ScheduleManualRecording`: `org.droidtv.ACTION.SCHEDULE_MANUAL_RECORDING`
|
||||
- `org.droidtv.channels.epg.bcepg.epgui.ScheduleEventRecording`: `org.droidtv.ACTION.SCHEDULE_EVENT_RECORDING`
|
||||
- `org.droidtv.channels.epg.bcepg.epgui.ScheduleRecording`: `org.droidtv.ACTION.SCHEDULE_RECORDING`
|
||||
- `org.droidtv.channels.epg.bcepg.RecordingsUpdateService`: Aktion `org.droidtv.bcepg.RecordingsUpdateService`, ausdrücklich `android:exported="false"`
|
||||
- `org.droidtv.channels.epg.search.EPGSearchProvider`: Authority `org.droidtv.channels.epg.search`; Suchprovider, kein Reset-Pfad
|
||||
|
||||
**BEWIESEN:** Keine dieser Manifest-Aktionen ist als EPG-Datenbank-Reset implementiert. In Manifest- und Ressourcenquersuche wurde keine Activity, kein Service und kein expliziter Intent mit der Bedeutung „EPG/Guide database reset/clear/rebuild“ gefunden.
|
||||
|
||||
`EpgStaleDataCleanupAlarmReceiver` hat ein Intent-Filter, aber kein explizites `android:exported`-Attribut. Bei `targetSdkVersion=30` ergibt sich auf der untersuchten Plattform aus dem Intent-Filter typischerweise Exportierbarkeit; die genaue vom PackageManager auf diesem Gerät aufgelöste Export-Eigenschaft ist in dieser rein lokalen Teilanalyse **NICHT VERIFIZIERBAR**. Für den Befund ist dies unerheblich, weil der Empfänger letztlich nur den No-op-Cleanup auslöst.
|
||||
|
||||
### Quersuche in weiteren lokalen Philips-APKs
|
||||
|
||||
**BEWIESEN:** `TPTvService.apk`, `eum.apk` und `usagelogger.apk` enthalten gemeinsame EPG-Vertragskonstanten für normale EPG-Datenabfragen, aber keine Treffer für `rebuilddb`, `rebuilddbonnewchannellist`, `staledatacleanup` oder `EpgDatabase.db`. `gel.apk`, `tvstrings.apk` und `tvwidgets2k15.apk` enthalten ebenfalls keinen solchen Aufrufer. Ein Aufrufer aus einem nicht lokal vorliegenden Paket bleibt **NICHT VERIFIZIERBAR**.
|
||||
|
||||
## Abhängigkeiten und Risikoabgrenzung
|
||||
|
||||
### Senderliste
|
||||
|
||||
- **BEWIESEN:** Die kanonische Senderliste liegt außerhalb von `EpgDatabase.db`. `EpgDatabaseHelper.syncChannelTable()` fragt `android.media.tv.TvContract.Channels.CONTENT_URI` ab.
|
||||
- **BEWIESEN:** `indextable` ist ein EPG-interner Spiegel von Sender-/Presetdaten einschließlich `presetid`, `presetnumber`, `svlid`, `svlrecid`, Name, `browsable` und `favoriteNumber`.
|
||||
- **BEWIESEN:** `mappingtable` ordnet `presetid` zu `svlid/svlrecid` zu.
|
||||
- **BEWIESEN:** Die Rebuild-Runnables löschen die kanonische `TvContract.Channels`-Tabelle nicht; sie lesen sie und bauen EPG-Index/Mapping neu auf.
|
||||
- **NICHT VERIFIZIERBAR:** Ob ein konkreter Laufzeitfehler während des Abgleichs zu sichtbaren temporären Kanal-/EPG-Zuordnungsproblemen führen würde.
|
||||
|
||||
### Favoriten
|
||||
|
||||
- **BEWIESEN:** Favoritenlisten werden über einen separaten Provider `org.droidtv.tv.provider.favorite` angesprochen, unter anderem `content://org.droidtv.tv.provider.favorite/FavoriteList` sowie Mixed-/Terrestrial-/Cable-/Satellite-Listen.
|
||||
- **BEWIESEN:** `EpgDatabase.db` enthält nur Spiegel-/Versionsfelder (`favoriteNumber`, `favouriteversion`), nicht die externen Favoritentabellen selbst.
|
||||
- **BEWIESEN:** Die EPG-Rebuild-Runnables enthalten keinen direkten Delete-Aufruf gegen den Favoriten-Provider.
|
||||
- **PLAUSIBEL:** Der Reindex kann vorübergehend Darstellung oder Zuordnung beeinflussen, bis die Spiegelwerte erneut synchronisiert sind.
|
||||
|
||||
### Aufnahmen, geplante Aufnahmen, Erinnerungen und Timer
|
||||
|
||||
Die persistenten Aufnahme-/Reminder-Daten liegen ebenfalls außerhalb von `EpgDatabase.db`:
|
||||
|
||||
- Authority: `org.droidtv.playtv.provider.recordremind`
|
||||
- Datenbank: `RecordingsRemindersList.db`
|
||||
- Aufnahmen: `.../RecordingsTable`
|
||||
- Erinnerungen: `.../RemindersTable`
|
||||
- gemeinsame View: `.../RecRemView`
|
||||
|
||||
- **BEWIESEN:** Diese Tabellen kommen weder im v6-Schema von `EpgDatabaseHelper` noch in `dropTables()` vor.
|
||||
- **BEWIESEN:** `EpgProgramPump` registriert einen `ContentObserver` auf `IRecordingsContract.CONTENT_URI_Recordings` und korreliert Aufnahmen über Felder wie `EventId` und `ChannelId`; Methoden wie `syncRecordingDBbyEvent()` und `RecDynamicEventUpdateHandler` aktualisieren Aufnahme-Metadaten bei EPG-Ereignisänderungen.
|
||||
- **BEWIESEN:** Ein URI-basierter EPG-Rebuild besitzt keinen direkten Codepfad, der `RecordingsTable` oder `RemindersTable` löscht.
|
||||
- **NICHT VERIFIZIERBAR:** Vollständige Nebenwirkungsfreiheit auf Darstellung, Event-ID-Verknüpfung, Folgeaufnahmen oder Scheduler nach Reakquisition. Das externe Persistieren verhindert nicht, dass neu vergebene Event-/Channel-IDs Beziehungen vorübergehend oder dauerhaft beeinflussen könnten.
|
||||
|
||||
### TV-Guide-Quelle ist kein sicherer Reset
|
||||
|
||||
`tvstrings.apk` enthält unter anderem die deutsche Warnung:
|
||||
|
||||
> Wenn Sie zu TV-Guide-Informationen aus dem Internet wechseln, gehen alle Aufnahmen verloren, die Sie mit Informationen der Sendeanstalt aufgenommen haben. Alle Erinnerungen und programmierten Aufnahmen werden abgebrochen.
|
||||
|
||||
Die Gegenrichtung warnt ebenfalls vor Verlust von Aufnahmen und Abbruch programmierter Aufnahmen.
|
||||
|
||||
- **BEWIESEN:** Das Umschalten zwischen Broadcaster- und Internet-Guide ist laut Philips-Ressourcen eine ausdrücklich destruktive Benutzeraktion für Aufnahme-/Timerdaten.
|
||||
- **BEWIESEN:** Es ist nicht mit den internen `rebuilddb`-URI-Pfaden gleichzusetzen und sollte nicht als Speicherbereinigung vorgeschlagen werden.
|
||||
|
||||
## Gesamteinordnung zur Speicherwarnung
|
||||
|
||||
Die statische Implementierung liefert eine konsistente Ursache für die ungewöhnlich große EPG-Datei:
|
||||
|
||||
1. EPG-Synchronisation erzeugt sehr hohen Insert/Delete-Churn; die hohen AUTOINCREMENT-Werte beweisen Millionen historische Inserts.
|
||||
2. Der als periodischer Stale-Cleanup bezeichnete Kernpfad löscht in dieser Firmware tatsächlich nichts.
|
||||
3. Der sichtbare 12-Stunden-Alarm-Setter hat keinen direkten Caller und wäre selbst bei Ausführung nur ein einmaliger Einstieg in dasselbe wirkungslose Runnable.
|
||||
4. Interne Rebuilds leeren Zeilen, verwenden aber selbst kein `VACUUM`; ob ein Vendor-/DB-seitig aktives Full-Auto-Vacuum die Datei dabei verkleinert, ist offen.
|
||||
5. Nur Now/Next/Index wurden als klein gemessen. Der unbeschränkte Schedule-Join lief länger als 120 Sekunden; Schedule/FVP können daher einen großen lebenden Datenanteil enthalten.
|
||||
|
||||
**BEWIESEN:** `EpgDatabase.db` ist mit 1,62 GiB der konkrete große Speicherverbraucher. **SEHR WAHRSCHEINLICH:** Der defekte Stale-Cleanup trägt wesentlich zu ihrem Wachstum bei, insbesondere durch nicht entfernte Schedule-Daten. **NICHT VERIFIZIERBAR:** Ob die aktuelle Dateigröße überwiegend aus lebenden Schedule-/FVP-Zeilen oder aus freien/fragmentierten Seiten besteht.
|
||||
|
||||
**NICHT VERIFIZIERBAR:** Der genaue Beitrag eines ungewöhnlichen SQLite-Headerzustands, WAL-/Journalresten oder Dateisystembesonderheiten. Dafür wären ausschließlich weitere lesende Datenbank-/Header-Messungen erforderlich; sie waren nicht Teil dieser lokalen APK-Teilanalyse.
|
||||
|
||||
## Keine Reparaturfreigabe aus diesem Bericht
|
||||
|
||||
Die gefundenen `ContentResolver.delete()`-Endpunkte und Rebuild-Pfade sind zustandsverändernd. Sie wurden nicht ausgeführt und dieser Bericht empfiehlt keinen konkreten Ausführungsbefehl. Insbesondere ist der umfassende New-Channel-List-Rebuild nicht gleichbedeutend mit einer Dateikompaktierung und eine vollständige Nebenwirkungsfreiheit für EPG-Verknüpfungen zu Aufnahmen/Timern ist nicht bewiesen.
|
||||
|
||||
## Reproduzierbare Belegdateien
|
||||
|
||||
- `diagnostics/deep-forensics/philips/epg-dex-context.txt` – Helper, DDL, Lifecycle, Provider und Query-Joins
|
||||
- `diagnostics/deep-forensics/philips/epg-runnable-context.txt` – vollständige Referenzkontexte der Provider-Runnables
|
||||
- `diagnostics/deep-forensics/philips/epg-cleanup-context.txt` – Cleanup- und echte Delete-Methoden
|
||||
- `diagnostics/deep-forensics/philips/epg-stale-context.txt` – Alarm-/Receiver-/URI-Kette
|
||||
- `diagnostics/deep-forensics/philips/epg-sync-thread-context.txt` – SyncThread-Methoden
|
||||
- `diagnostics/deep-forensics/philips/epg-recording-deps-context.txt` – Aufnahme-/Reminder-Verträge und Interaktionen
|
||||
- `diagnostics/deep-forensics/philips/epg-delete-rebuild-string-context.txt` – interne Rebuild-Auslösung bei Senderlistenereignissen
|
||||
- `diagnostics/deep-forensics/philips/channels-manifest.txt` – dekodiertes binäres Manifest
|
||||
- `diagnostics/deep-forensics/philips/tvstrings-epg-matches.txt` – relevante TV-Guide-Ressourcen
|
||||
- `diagnostics/raw/storage/epg-content-evidence.txt` – bereits vorhandene Laufzeit-Zeilenzahlen/IDs/Dateigröße
|
||||
- `diagnostics/bugreport/extracted/files/01790__bugreport-PH9M_EA_5599-STT2.230526.001-2026-08-25-13-26-44.txt` – Boot-Log, insbesondere Zeilen um 32807–32812
|
||||
192
diagnostics/diagnosis.md
Normal file
192
diagnostics/diagnosis.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Schlussdiagnose: Philips 43PUS7394/12 – interne Speicherwarnung
|
||||
|
||||
Stand: 2026-08-25. Plattform TPM191E, Firmware
|
||||
`TPM191E_R.211.000.103.000`, Android TV 12, Kernel `4.9.310-ab4215`.
|
||||
|
||||
Dies ist die aktuelle Kurzfassung. Die vertrauliche Deep-Forensics-Fassung mit
|
||||
Angriffsflächenanalyse ist nicht Teil der kuratierten Veröffentlichung.
|
||||
|
||||
**Phase-4-Update:** Eine konsistente UID1000-Kopie der EPG-DB schließt die
|
||||
letzte zentrale Lücke. `scheduletable` hält 3.071.250 vollständig abgelaufene
|
||||
Zeilen; 97,81 % sind älter als 30 Tage. Die Tabelle belegt 1.436,98 MiB, ihre
|
||||
vier Indizes 222,06 MiB; `freelist_count=0`, `auto_vacuum=FULL`,
|
||||
`integrity_check=ok`. Damit ist jahrelang nicht gelöschte Schedule-Historie die
|
||||
bewiesene Hauptursache. Die veröffentlichten Details stehen in
|
||||
[`phase4/epg-database-forensics.md`](phase4/epg-database-forensics.md).
|
||||
|
||||
## Ergebnis
|
||||
|
||||
Die Warnung ist kein Beleg dafür, dass Androids `/data`-Dateisystem unmittelbar
|
||||
voll ist. Die letzte Momentaufnahme zeigte:
|
||||
|
||||
| Wert | Ergebnis |
|
||||
|---|---:|
|
||||
| `/data` gesamt | 5.089.920 KiB = 4,85 GiB |
|
||||
| belegt | 3.831.944 KiB = 78 % |
|
||||
| verfügbar | 1.136.840 KiB = 1,084 GiB |
|
||||
| Inodes | 7 % belegt |
|
||||
| Android-Status | `NORMAL` |
|
||||
|
||||
Philips verwendet jedoch einen eigenen Monitor in der Nähe von 1 GiB freiem
|
||||
`/data`. Zur 1-GiB-Marke blieben zuletzt nur 88.264 KiB beziehungsweise
|
||||
86,2 MiB. Normale Schwankungen reichen deshalb aus, die Philips-Warnung
|
||||
wiederholt auszulösen, obwohl Android selbst noch keinen Low-Storage-Zustand
|
||||
meldet.
|
||||
|
||||
Eine nach dem späteren Aufwachen sichtbare Philips-Zeile mit rund 762 MB frei
|
||||
misst nicht `/data`, sondern die separate 1000-MiB-ext4-Partition
|
||||
`/data/vendor/user_setting`. TvProvider warnt dort erst unter rund 13 % und
|
||||
10 %; aktuell sind bezogen auf die Rohpartitionsgröße rund 72,7 % frei. Diese
|
||||
zweite Messung ist daher unauffällig und nicht die Ursache der Meldung.
|
||||
|
||||
Der klare Hauptverursacher ist eine Philips-EPG-Datenbank:
|
||||
|
||||
| Verursacher | Größe |
|
||||
|---|---:|
|
||||
| `org.droidtv.channels/.../EpgDatabase.db` | 1.742.053.376 B = 1,622 GiB |
|
||||
| `org.droidtv.gel/.../androidx.work.workdb` | 229.453.824 B = 218,824 MiB |
|
||||
| sichtbarer Code von 18 aktualisierten System-Apps | 686,195 MiB |
|
||||
| statbare Basis-ODEX/VDEX dieser Apps | 226,381 MiB |
|
||||
|
||||
Insgesamt sind konservativ 2.830,530 MiB beziehungsweise rund 75–76 % der
|
||||
belegten `/data`-Kapazität konkret erklärt. Rund 0,88–0,89 GiB bleiben wegen
|
||||
App-Sandbox, DAC und SELinux nicht einzeln aufteilbar; dieser Rest wurde nicht
|
||||
als frei oder unbenutzt behandelt.
|
||||
|
||||
## EPG-Datenbank: Ursache und offene Größe
|
||||
|
||||
Für `EpgDatabase.db` sind folgende Werte direkt bewiesen:
|
||||
|
||||
- Seitengröße 4096 Byte;
|
||||
- `page_count` 425.306;
|
||||
- Dateigröße exakt `425.306 × 4096 = 1.742.053.376` Byte;
|
||||
- nur 358 sichtbare Index-, 195 Now- und 162 Next-Zeilen, aber
|
||||
AUTOINCREMENT-IDs im Millionenbereich;
|
||||
- eine unbeschränkte lesende Schedule-Abfrage war nach mehr als 120 Sekunden
|
||||
noch nicht vollständig und wurde clientseitig abgebrochen.
|
||||
|
||||
Die APK-Analyse erklärt einen konkreten Wachstumsmechanismus: Der als
|
||||
Stale-Cleanup benannte Runnable berechnet Grenzwerte und schreibt Logmeldungen,
|
||||
führt aber keine Löschoperation aus. Zusätzlich sind die Zeitkonstanten dort
|
||||
offenbar als Millisekunden statt als die beabsichtigten längeren Intervalle
|
||||
verwendet. Vorhandene echte Zeit-Löschmethoden sind nicht mit diesem globalen
|
||||
Cleanup-Pfad verbunden.
|
||||
|
||||
Philips' interne `rebuilddb`-Pfade sind ebenfalls kein physischer
|
||||
Datenbank-Neuaufbau. Sie löschen beziehungsweise synchronisieren Zeilen in
|
||||
derselben Datei und rufen weder `VACUUM` noch einen Dateiersatz auf.
|
||||
|
||||
Wichtig ist die Grenze der Aussage: `freelist_count`, der persistierte
|
||||
`auto_vacuum`-Headerwert und die Seitennutzung der großen Schedule-/FVP-
|
||||
Tabellen sind nicht zugänglich. AOSP baut Plattform-SQLite langjährig mit
|
||||
`SQLITE_DEFAULT_AUTOVACUUM=1` (`FULL`); da die Philips-App keinen anderen
|
||||
Modus setzt, ist FULL eine starke Inferenz, aber kein Gerätebeweis. Deshalb
|
||||
ist eine sehr große Freelist **nicht** als Hauptursache bewiesen. Die Datei
|
||||
kann zu wesentlichen Teilen aus noch lebenden Schedule-Daten, freien Seiten
|
||||
und/oder interner Fragmentierung bestehen. Der tatsächlich rückgewinnbare
|
||||
Platz ist derzeit nicht seriös bezifferbar.
|
||||
|
||||
## GEL-WorkManager-Datenbank
|
||||
|
||||
GELs `androidx.work.workdb` ist rund 2.155-mal größer als typische
|
||||
WorkManager-Datenbanken auf demselben Gerät. Die APK erzeugt wiederholt
|
||||
`GEL_WORK` mit `APPEND_OR_REPLACE`; eine lang gewachsene Work-Kette ist daher
|
||||
sehr wahrscheinlich. Die Hauptdatei hat 4096-Byte-Seiten und 56.019 Seiten;
|
||||
der Live-Stand umfasst wegen aktivem WAL 56.020 Seiten. WAL und SHM belegen
|
||||
zusammen nur 0,531 MiB und erklären den Ausreißer nicht. Tabellen- und
|
||||
Freelist-Anteile bleiben ohne autorisierten Owner-Zugriff unbekannt.
|
||||
|
||||
## Adoptierter Speicher
|
||||
|
||||
Das adoptierte 112-GiB-Volume ist korrekt gemountet und praktisch leer. Die
|
||||
18 Pakete unter `/data/app` sind jedoch ausnahmslos Updates von System-Apps;
|
||||
auch Channels und GEL sind Systempakete. Android verschiebt solche Pakete und
|
||||
ihre privaten Daten nicht auf das adoptierte Volume. Der große freie
|
||||
USB-Speicher kann die beiden Datenbanken daher nicht automatisch aufnehmen.
|
||||
|
||||
Ein Philips-spezifischer `mFreeSize 0`-Fehler für öffentliche/adoptierte
|
||||
Volumes ist in Logs vorhanden. Androids eigener StorageManager misst diese
|
||||
Volumes korrekt. Ein Einfluss des Fehlers auf einzelne Philips-Oberflächen ist
|
||||
plausibel, aber er erklärt weder die reale `/data`-Belegung noch ist er als
|
||||
Auslöser des hier dekompilierten Warnpfads bewiesen.
|
||||
|
||||
## eMMC-Gesundheit
|
||||
|
||||
Philips hat beim Boot beide eMMC-Life-Time-Felder als `0x0a/0x0a` ermittelt,
|
||||
protokolliert und in `persist.odm.tpv.sys.emmc_lifetime` gespeichert. Nach der
|
||||
von KIOXIA mit JEDEC-Genehmigung wiedergegebenen Tabelle bedeutet `0x0a` für
|
||||
beide Felder die grobe Klasse 90–100 % der geschätzten Lebensdauer verbraucht.
|
||||
Das ist keine exakte Restlebensdauer und beweist keinen gegenwärtigen I/O-
|
||||
Fehler.
|
||||
|
||||
`PRE_EOL_INFO` beschreibt getrennt den Verbrauch reservierter Ersatzblöcke.
|
||||
Eine spätere rein lesende Eventlog-Abfrage hat diesen Wert unabhängig von der
|
||||
Philips-Property sichtbar gemacht:
|
||||
`storaged_emmc_info: [emmc 5.1,1,10,10]`. Die installierte Tagdefinition ordnet
|
||||
die Felder als Version, EOL, Lifetime A und Lifetime B zu. Damit sind eMMC 5.1,
|
||||
`PRE_EOL_INFO=0x01` (**Normal**) und A/B `0x0a/0x0a` für diesen Boot bestätigt.
|
||||
Nur der direkte Rohdump sowie CID, CSD, Hersteller und Modell bleiben auf
|
||||
diesem Production-Build durch SELinux gesperrt. `storaged` wurde beim
|
||||
zusätzlichen Lesen des EXT_CSD-Debugfs-Knotens als UID 0 durch SELinux
|
||||
abgewiesen; Root allein wäre daher weiterhin keine vollständige Zugriffs- oder
|
||||
Reparaturlösung.
|
||||
|
||||
Im verfügbaren Bugreport-/Logfenster wurden keine echten MMC-, Block-I/O-,
|
||||
EXT4-, JBD2-, ENOSPC- oder Read-only-Remount-Fehler gefunden. Weil dmesg,
|
||||
PStore, EXT4-Fehlerzähler und ein OEM-Board-Dump fehlen, ist das ein begrenzter
|
||||
Nichtfund und kein Gesundheitszertifikat.
|
||||
|
||||
Quellen: [KIOXIA Wear Leveling](https://www.kioxia.com/content/dam/kioxia/shared/business/memory/mlc-nand/asset/productbrief/KIOXIA_Understanding_Wear_Leveling_Tech_Brief.pdf),
|
||||
[KIOXIA Bad Block Management](https://tw.kioxia.com/content/dam/kioxia/shared/business/memory/mlc-nand/asset/productbrief/KIOXIA_Understanding_Bad_Block_Tech_Brief.pdf),
|
||||
[AOSP EXT_CSD-Auswertung](https://android.googlesource.com/platform/frameworks/native/+/c93afd5/cmds/dumpstate/utils.cpp),
|
||||
[AOSP SQLite-Buildoptionen](https://android.googlesource.com/platform/external/sqlite/+/refs/heads/android11-qpr3-s1-release/dist/Android.bp)
|
||||
und [SQLite VACUUM](https://www.sqlite.org/lang_vacuum.html).
|
||||
|
||||
## Bewertung möglicher Maßnahmen – nichts davon ausgeführt
|
||||
|
||||
| Option | Bewertung |
|
||||
|---|---|
|
||||
| Philips-dokumentierter, geordneter EPG-Dateirebuild | technisch größter gezielter Hebel und voraussichtlich schreibärmer als `VACUUM`; wurde aber weder als UI-/Supportweg gefunden noch auf Nebenwirkungen verifiziert |
|
||||
| internes `rebuilddb` / `rebuilddbonnewchannellist` | kein bestätigter Dateirebuild; Speichergewinn abhängig vom unbekannten Auto-Vacuum und mit EPG-/Verknüpfungsrisiken |
|
||||
| manuelles Löschen von `EpgDatabase.db` | nicht vertretbar bei laufendem Provider, offenen FDs und unbekannten Labels/Abhängigkeiten |
|
||||
| `VACUUM` | vermeiden: hohe Schreiblast; kann bis zu ungefähr eine zusätzliche DB-Größe benötigen, während nur rund 1,1 GiB frei sind |
|
||||
| `pm clear org.droidtv.channels` | zu grob; löscht das gesamte Paketprofil und kann Spiegel/Einstellungen betreffen |
|
||||
| Werksreset/Firmware-Flash | unverhältnismäßig, schreibintensiv und ohne Beleg für dauerhafte Fehlerbehebung |
|
||||
| adoptierter Speicher | für die bestehenden Systempaketdaten kein gangbarer Hebel |
|
||||
|
||||
Die risikoärmste Folgerung lautet daher weiterhin: **keine spontane Mutation**.
|
||||
Sinnvoll wäre ein von Philips/TPV ausdrücklich dokumentierter Ablauf, der den
|
||||
Channels-Prozess geordnet schließt, nur die EPG-Datenbank konsistent neu
|
||||
erzeugt, Sender/Favoriten/Timer schützt und die Nebenwirkungen benennt. Eine
|
||||
solche Prozedur wurde in der Firmware und den vorhandenen Supportartefakten
|
||||
nicht gefunden.
|
||||
|
||||
## Standby-Nachprüfung
|
||||
|
||||
Der während der Analyse beobachtete Aus-Zustand war kein Absturz und kein
|
||||
Neustart. Nach dem manuellen Einschalten lief die Uptime weiter. Die Logs
|
||||
belegen `power off by auto power down`, den Übergang nach
|
||||
`POWER_STATE_SEMI_STANDBY` mit `detail=SWITCH_OFF_TIMER` und später das
|
||||
Aufwachen per `WAKE_REASON_WAKE_KEY`. Die Kontrollmessung danach blieb bei
|
||||
78 % `/data`-Belegung und Android-Status `NORMAL`. Im erhaltenen aktuellen
|
||||
Logfenster trat nach dem Aufwachen kein `MEMORY_CRITICAL`, `CLEANUP_APPS` oder
|
||||
Android-Low-/Full-Storage-Ereignis auf.
|
||||
|
||||
## Nachvollziehbarkeit und Sicherheit
|
||||
|
||||
Am Fernseher wurden keine Dateien gelöscht oder verändert, keine Einstellungen
|
||||
oder Pakete geändert und kein Reboot, Recovery-, Flash-, Root- oder
|
||||
Reparaturvorgang ausgeführt. Lange Provider-Leseabfragen wurden nur auf dem PC
|
||||
abgebrochen. Transparenz-Ausnahme: Das vom Auftrag ausdrücklich verlangte
|
||||
`dumpsys diskstats` meldete selbst einen `[512B Data Write]`-Latenztest; dieser
|
||||
Android-Test wurde danach nicht wiederholt.
|
||||
|
||||
Detailberichte:
|
||||
|
||||
- [Speicher-/SQLite-Analyse](storage-analysis.md)
|
||||
- [EPG-Reverse-Engineering](epg-analysis.md)
|
||||
- [eMMC-Analyse](emmc-analysis.md)
|
||||
- [Bugreport-Analyse](bugreport-analysis.md)
|
||||
|
||||
Rohbefehle, private Zustandsdaten sowie Privilegien-/SELinux-Berichte sind aus
|
||||
dieser Veröffentlichung ausgeschlossen.
|
||||
295
diagnostics/emmc-analysis.md
Normal file
295
diagnostics/emmc-analysis.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# eMMC-Analyse – Philips 43PUS7394/12
|
||||
|
||||
Stand: 2026-08-25. Alle Geräteabfragen waren lesend. Der Grundbericht wurde
|
||||
aus lokal gesicherten Artefakten erstellt; der unten hervorgehobene
|
||||
Storaged-Nachtrag stammt aus einer späteren rein lesenden Live-Abfrage des
|
||||
bereits vorhandenen Android-Eventlogs.
|
||||
|
||||
## Ergebnis in einem Satz
|
||||
|
||||
**BEWIESEN:** Zwei voneinander unabhängige Android-/Philips-Pfade berichten
|
||||
für Lifetime A/B jeweils `0x0a`, also das höchste noch nicht überschrittene
|
||||
JEDEC-Band **90–100 % der geschätzten Lebensdauer verbraucht**. Der
|
||||
Android-Eventlog enthält außerdem `storaged_emmc_info: [emmc 5.1,1,10,10]`;
|
||||
die geräteeigene Tagdefinition ordnet dies eindeutig als eMMC 5.1,
|
||||
`PRE_EOL_INFO=0x01`, Lifetime A `0x0a`, Lifetime B `0x0a` zu. `0x01` bedeutet
|
||||
für den Reserveblockindikator **Normal**. Die Rohbytes, CID, CSD, Hersteller
|
||||
und Modell bleiben gesperrt. Hoher Lifetime-Bucket und normaler PRE_EOL-Wert
|
||||
sind gleichzeitig möglich und bilden unterschiedliche Gesundheitsachsen;
|
||||
zusammen belegen sie weiterhin keinen unmittelbar bevorstehenden Ausfall.
|
||||
|
||||
## Nachtrag: unabhängiger Android-Storaged-Wert
|
||||
|
||||
Die rein lesende Abfrage des vorhandenen Eventpuffers lieferte:
|
||||
|
||||
```text
|
||||
01-01 01:00:17.642 2644 2644 I storaged_emmc_info: [emmc 5.1,1,10,10]
|
||||
```
|
||||
|
||||
Die auf genau diesem Gerät installierte Event-Tag-Definition lautet:
|
||||
|
||||
```text
|
||||
2733 storaged_emmc_info (mmc_ver|3),(eol|1),(lifetime_a|1),(lifetime_b|1)
|
||||
```
|
||||
|
||||
Damit sind Feldreihenfolge und Werte **BEWIESEN**. AOSPs Android-12-
|
||||
`storaged_info.cpp` liest dafür `rev`, `pre_eol_info` und `life_time` aus dem
|
||||
MMC-sysfs und fällt bei Bedarf auf EXT_CSD-Debugfs zurück; der Kernel erzeugt
|
||||
die sysfs-Werte aus den entsprechenden EXT_CSD-Feldern. Dieser Storaged-Pfad
|
||||
ist von Philips' persistenter Property unabhängig. Die Übereinstimmung beider
|
||||
Lifetime-Werte schließt deshalb die frühere Hauptunsicherheit „möglicherweise
|
||||
nur veralteter Philips-Cache“ für diesen Boot praktisch aus. Der vollständige
|
||||
EXT_CSD-Rohdump bleibt dennoch nicht zugänglich.
|
||||
|
||||
Primärquellen:
|
||||
|
||||
- [AOSP Android 12: `storaged_info.cpp`](https://android.googlesource.com/platform/system/core/+/refs/heads/android12-gsi/storaged/storaged_info.cpp)
|
||||
- [AOSP: Storaged-Event-Tagdefinition](https://android.googlesource.com/platform/system/core/+/master/storaged/EventLogTags.logtags)
|
||||
- [Android-Kernel: MMC-sysfs aus EXT_CSD](https://android.googlesource.com/kernel/common/+/60a02c0d815e1df9bb8e6dd14105c31d8c298f2d/drivers/mmc/core/mmc.c)
|
||||
|
||||
## Beweiskette
|
||||
|
||||
### BEWIESEN
|
||||
|
||||
1. Der Boot-Log enthält die Ausgabe der nativen Philips-Komponente:
|
||||
|
||||
```text
|
||||
D eMMCLifeReader: eMMC life time 0x0a 0x0a
|
||||
```
|
||||
|
||||
2. Die persistente Eigenschaft enthält aktuell denselben Wert:
|
||||
|
||||
```text
|
||||
persist.odm.tpv.sys.emmc_lifetime = 0x0a 0x0a
|
||||
```
|
||||
|
||||
3. Die dazugehörige Init-Definition startet `/system/bin/emmclifereader` als
|
||||
`root`, `oneshot`. Der Dienst ist nach dem Boot erwartungsgemäß `stopped`.
|
||||
|
||||
4. Philips' `OnBootLogger` liest die Property, zerlegt sie in zwei Werte und
|
||||
übermittelt beide separat als `dev_lt_a="a"` und `dev_lt_b="a"`.
|
||||
|
||||
5. `TVStorageMonitorService.getEmmcLifetime()` liest dieselbe Property. Die
|
||||
Bytecode-Analyse zeigt, dass dieser konkrete Warndienst nur den Text nach
|
||||
dem letzten `x` auswertet, also den zweiten Wert (Typ B). Er ordnet `0x0a`
|
||||
selbst der Stufe `90%-100% device lifetime used` zu.
|
||||
|
||||
### SEHR WAHRSCHEINLICH, aber nicht vollständig direkt verifizierbar
|
||||
|
||||
Die zeitliche Boot-Ausgabe des `eMMCLifeReader`, seine privilegierte
|
||||
One-shot-Init-Definition, die anschließend identische persistente Property und
|
||||
zwei unabhängige Philips-Verbraucher bilden eine starke Kette dafür, dass der
|
||||
native Reader die eMMC-EXT_CSD-Felder beim Boot liest und die Property befüllt.
|
||||
Die lokal gesicherte SELinux-Policy schließt einen weiteren Teil der Kette:
|
||||
`/system/bin/emmclifereader` trägt `tpvdebug_exec`, und ein
|
||||
`typetransition init ... tpvdebug_exec process tpvdebug` führt den von Init
|
||||
gestarteten Prozess in die Domain `tpvdebug`. Diese Domain darf TPV-Properties
|
||||
setzen. Die konkrete Property-Zuordnung, der native Schreibcode und der vom
|
||||
Reader geöffnete Kernelpfad bleiben aber unsichtbar. Deshalb ist die Herkunft
|
||||
aus EXT_CSD **SEHR WAHRSCHEINLICH**, nicht unabhängig **BEWIESEN**.
|
||||
|
||||
## JEDEC-/Herstellerinterpretation
|
||||
|
||||
Die Felder `DEVICE_LIFE_TIME_EST_TYP_A` (EXT_CSD-Byte 268) und Typ B (Byte 269)
|
||||
geben eine herstellerspezifisch ermittelte, mittlere Abnutzung relativ zur
|
||||
maximal geschätzten Lebensdauer an. Eine KIOXIA-Fachinformation reproduziert
|
||||
die Tabelle aus JEDEC JESD84-B51: `0x0a` bedeutet 90–100 % verbraucht, `0x0b`
|
||||
erst „maximale geschätzte Lebensdauer überschritten“. Kingston erläutert
|
||||
zusätzlich, dass die Werte nur in 10-%-Bändern berichtet werden und je ein Feld
|
||||
unterschiedliche Speicherarten/-Konfigurationen repräsentiert.
|
||||
|
||||
Quellen:
|
||||
|
||||
- [KIOXIA: Understanding Wear Leveling in NAND Flash Memory](https://tw.kioxia.com/content/dam/kioxia/shared/business/memory/mlc-nand/asset/productbrief/KIOXIA_Understanding_Wear_Leveling_Tech_Brief.pdf)
|
||||
- [KIOXIA: Understanding Bad Block Management in NAND Flash Memory](https://www.kioxia.com/content/dam/kioxia/shared/business/memory/mlc-nand/asset/productbrief/KIOXIA_Understanding_Bad_Block_Tech_Brief.pdf)
|
||||
- [Kingston: eMMC Life-Cycle Estimating, Validating & Monitoring](https://www.kingston.com/en/blog/embedded-and-industrial/emmc-lifecycle)
|
||||
- [Macronix eMMC-Datenblatt mit vollständiger A/B-Wertetabelle](https://www.macronix.com/Lists/Datasheet/Attachments/9163/MX52LM08A11%2C%203V%2C%208GB%2C%20v1.7.pdf)
|
||||
|
||||
`0x0a` ist daher nicht „10 % Zustand“ und auch keine Belegungsanzeige. Es ist
|
||||
das Band 90–100 % der vom eMMC-Controller geschätzten Schreib-/Löschlebensdauer.
|
||||
Der genaue Punkt innerhalb dieses Bandes ist nicht bestimmbar.
|
||||
|
||||
`PRE_EOL_INFO` ist ein davon getrennter Indikator für verbrauchte Reserveblöcke.
|
||||
Die KIOXIA-Unterlage zum Bad-Block-Management ordnet EXT_CSD-Byte 267 zu:
|
||||
`0x01` normal, `0x02` Warnung (80 % der Reserveblöcke verbraucht) und `0x03`
|
||||
dringend. Der direkte sysfs-Read bleibt der Shell durch SELinux verwehrt; der
|
||||
unabhängige Storaged-Boot-Event exportiert jedoch `eol=1`. Für diesen Boot ist
|
||||
`PRE_EOL_INFO=0x01` deshalb **BEWIESEN**. Wichtig: Dieser Wert wurde unabhängig
|
||||
gemessen; er wird nicht aus `0x0a/0x0a` abgeleitet.
|
||||
|
||||
## Identität und Kapazität
|
||||
|
||||
Das interne Gerät ist eindeutig eine eMMC 5.1 als `mmcblk0` am MediaTek-Host
|
||||
`1006d000.MSDC0`; `/data` liegt auf `mmcblk0p37`. Der Zugriff auf `cid`, `name`,
|
||||
`manfid`, `oemid`, `serial`, CSD und die rohe Blockgröße wird durch Rechte/SELinux
|
||||
verweigert. Hersteller, Modell und Seriennummer können daher nicht seriös
|
||||
bestimmt werden. Eine Android-/Philips-Oberfläche berichtet rund 8 GB
|
||||
**nutzbaren internen Speicher**; das ist keine Rohkapazitätsangabe der eMMC.
|
||||
Die vollständige statische Partitionstabelle summiert sich bereits auf
|
||||
14.809 MiB (14,462 GiB) und spricht deshalb stark für eine nominelle
|
||||
16-GB-eMMC. Die exakte Rohsektorzahl ließ sich am Blockgerät weiterhin nicht
|
||||
unabhängig prüfen.
|
||||
|
||||
## Exakte DAC-/SELinux-Barrieren
|
||||
|
||||
Die lokal gesicherten Audit-, Dateikontext- und Policy-Daten trennen die
|
||||
Barrieren präzise:
|
||||
|
||||
| Ziel | Unix-/DAC-Zustand | beobachtete SELinux-Barriere | Ergebnis |
|
||||
|---|---|---|---|
|
||||
| `/dev/block/mmcblk0` | `brw------- root root`, Major:Minor `179:0` | ein geeigneter Blockdevice-Zieltyp wäre zusätzlich erforderlich | Shell scheitert bereits an DAC; Rohgröße und -inhalt **NICHT VERIFIZIERBAR** |
|
||||
| `/sys/block/mmcblk0/device/life_time` | sysfs-Knoten vorhanden | `shell:s0 -> sysfs:s0` `{ read }` verweigert; Policy gewährt Shell nur `getattr` auf generischem sysfs | Rohwert **NICHT VERIFIZIERBAR** |
|
||||
| `/sys/block/mmcblk0/device/pre_eol_info` | sysfs-Knoten vorhanden | dieselbe AVC-Barriere | direkter Shell-Read gesperrt; Wert `0x01` über unabhängigen Storaged-Event **BEWIESEN** |
|
||||
| CID/CSD/`manfid`/`oemid`/`serial` | sysfs-Knoten vorhanden | konkrete `{ read }`-AVCs gegen `sysfs:s0` | Identität **NICHT VERIFIZIERBAR** |
|
||||
| `/sys/kernel/debug/mmc0/mmc0:0001/ext_csd` | Debugfs-Pfad und Datei durch Audit direkt belegt | `storaged:s0 -> debugfs_mmc:s0` `{ read }` verweigert, obwohl Prozess UID root besitzt; Shell wird schon an `mmc0` abgewiesen | Inhalt **NICHT VERIFIZIERBAR** |
|
||||
| `/system/bin/emmclifereader` | Systemdatei, von Init als root gestartet | Shell erhält an `tpvdebug_exec:s0` weder `getattr` noch `read`; Init wechselt beim Start nach `tpvdebug` | äußere Reader-Kette **BEWIESEN**, Implementierung **NICHT VERIFIZIERBAR** |
|
||||
| `/sys/fs/ext4/mmcblk0p37/*` | Health-Knoten wie `errors_count` und `lifetime_write_kbytes` vorhanden | alle direkten Leseversuche `Permission denied` | interne EXT4-Historie **NICHT VERIFIZIERBAR** |
|
||||
|
||||
Die verfügbare Vendor-Policy gewährt `dumpstate` am Typ `debugfs_mmc` nur
|
||||
Verzeichnissuche, nicht den benötigten Datei-Read. Sie erlaubt `dumpstate`
|
||||
zugleich Zugriffe auf andere, generisch gelabelte Debugfs-Bereiche. Ein
|
||||
erfolgreicher Binder-/DMA-Buf-Debugfs-Dump sagt daher nichts über MMC-Zugriff
|
||||
aus. Der reale `storaged`-AVC beweist zusätzlich, dass Unix-UID 0 die
|
||||
SELinux-Type-Enforcement-Regel nicht überstimmt.
|
||||
|
||||
## Prozess- und Rechtematrix
|
||||
|
||||
| Prozess | UID / Domain | Speicherfunktion | belegte Grenze | Einordnung |
|
||||
|---|---|---|---|---|
|
||||
| `emmclifereaderd` | Init: root/system; Policy-Transition nach `u:r:tpvdebug:s0` | einmaliger Philips-Life-Time-Reader | Prozess beim Bugreport bereits beendet; Binary/Property-Schreibcode nicht lesbar | äußere Kette **BEWIESEN**; EXT_CSD als Rohquelle **SEHR WAHRSCHEINLICH**; genau dieser Debugfs-Pfad **PLAUSIBEL** |
|
||||
| `storaged` PID 2644 | root / `u:r:storaged:s0` | Android-I/O-Statistik und Boot-Healthpfad | Debugfs-Read wird durch AVC blockiert; dennoch publiziert der sysfs-/Fallback-Pfad eMMC 5.1, EOL 1, A/B 10/10 | Eventwerte und Blockade **BEWIESEN** |
|
||||
| TPV `storagedcapture` PID 2613 | root / `u:r:tpvdebug:s0` | periodische Storage-/UID-I/O-Erfassung | sogar `getattr` auf `/proc/uid_io/stats` wird abgewiesen | root allein genügt nicht, **BEWIESEN** |
|
||||
| `vold` PID 1442 | root / `u:r:vold:s0` | Mount-, fscrypt- und Volumeverwaltung | kein EXT-CSD-/PRE_EOL-Export; der sichtbare `e2fsck` galt nur dem adoptierten `dm-4` | interner eMMC-Healthwert **NICHT VERIFIZIERBAR** |
|
||||
| `installd` PID 2630 | root / `u:r:installd:s0` | Quota-/Paket-Speicherverwaltung | erkennt `/data`-Mount, liefert keine MMC-Healthdaten | logische Speicherfunktion **BEWIESEN**, Hardwarezustand unbekannt |
|
||||
| `system_server` PID 3007 | system / `u:r:system_server:s0` | StorageManager und DeviceStorageMonitor | logische Schwellwerte, keine Rohregister | Belegung **BEWIESEN**, eMMC-Rohzustand nicht |
|
||||
| `TVStorageMonitorService` PID 5636 | system / `u:r:system_app:s0` | liest TPV-Property regelmäßig und klassifiziert Typ B | nur Property-Verbraucher | Zuordnung **BEWIESEN**, keine unabhängige Messung |
|
||||
| Standard-`dumpstate` PID 21293 | shell / `u:r:dumpstate:s0` | Bugreport-Sammlung | leeres Kernel-Log, keine `IDumpstateDevice`-HAL, kein Board-Dump | privilegierte Lücke **BEWIESEN** |
|
||||
| TPV `ddg` / `tpv_top_service` | root / `u:r:dumpstate:s0` | OEM-Diagnosedaemons | im Artefakt kein eMMC-Health-Export | Fähigkeit dazu **NICHT VERIFIZIERBAR** |
|
||||
| TPV `errlogservice` | system / `u:r:hal_tv_errlogservice_default:s0` | OEM-Fehlerlogdienst | `lshal debug` endet mit `FAILED_TRANSACTION` | Inhalt **NICHT VERIFIZIERBAR** |
|
||||
|
||||
Die AOSP-Historie enthält inzwischen einen Storaged-Fix mit genau derselben
|
||||
AVC-Zeile und demselben Pfad. Der Commit erklärt den Zugriff als veralteten
|
||||
Storaged-Init-Control-File-Pfad und beseitigt ihn, statt `storaged` pauschal
|
||||
MMC-Debugfs-Rechte zu geben. Das bestätigt die technische Einordnung des Audits
|
||||
als reale SELinux-Sperre, beweist aber nicht, dass Philips'
|
||||
`emmclifereader` denselben Pfad verwendet. Quelle:
|
||||
[AOSP storaged: Fix SELinux denial with debugfs_mmc](https://android.googlesource.com/platform/system/core/%2B/65b5858a9e18b38fe8c142ac7852fb010b889ab5).
|
||||
|
||||
## Fehler- und Korruptionslage
|
||||
|
||||
**BEWIESEN** ist, dass die direkt zugänglichen Android-Logs zeigen:
|
||||
|
||||
- keinen `EXT4-fs error`, `Buffer I/O error`, `blk_update_request`, `ENOSPC` oder
|
||||
fehlgeschlagenen Update-Engine-Vorgang;
|
||||
- einen einzelnen `Icing`-Fehler `Array storage bad crc` in einem App-Index;
|
||||
- erfolgreichen aktuellen Dateisystembetrieb: `/data` ist `rw` eingehängt und
|
||||
das vom Auftrag ausdrücklich verlangte `dumpsys diskstats` meldete einen
|
||||
`[512B Data Write]`-Latenztest. Dieser interne 512-Byte-Test ist die
|
||||
dokumentierte Transparenz-Ausnahme und wurde nicht wiederholt.
|
||||
|
||||
Der isolierte Icing-CRC-Fehler belegt einen beschädigten Indexdatensatz, ordnet
|
||||
die Ursache aber nicht dem eMMC-Medium zu. Softwareabbruch, unvollständiger
|
||||
Schreibvorgang und Medienfehler sind damit nicht unterscheidbar.
|
||||
|
||||
Wesentliche Einschränkung: `dmesg`, Kernel-Log, EXT_CSD, `errors_count`,
|
||||
`first_error_time`, `last_error_time` und `lifetime_write_kbytes` sind dem
|
||||
ADB-Shell-Kontext verwehrt. Deshalb bedeutet „keine zugänglichen I/O-Fehler
|
||||
gefunden“ ausdrücklich **nicht**, dass Kernel oder eMMC nie Fehler registriert
|
||||
haben. Der Nichtfund ist nur **BEWIESEN innerhalb des sichtbaren
|
||||
Diagnosefensters**; die vollständige historische Fehlerlage ist
|
||||
**NICHT VERIFIZIERBAR**.
|
||||
|
||||
## Evidenzbewertung
|
||||
|
||||
| Aussage | Stufe | Begründung |
|
||||
|---|---|---|
|
||||
| Philips' nativer Reader meldete beim Boot `0x0a 0x0a` | **BEWIESEN** | native Boot-Logzeile |
|
||||
| Property und Telemetrie enthalten beide A/B-Werte `0x0a` | **BEWIESEN** | Property, OnBootLogger, `dev_lt_a/b` |
|
||||
| Beide Werte bedeuten 90–100 % Lebensdauer verbraucht | **BEWIESEN** | JEDEC-reproduzierende Herstellerunterlagen und Philips-Zuordnung |
|
||||
| TVStorageMonitor bewertet Typ B als 90–100 % verbraucht | **BEWIESEN** | DEX-Analyse und wiederholter Laufzeitlog |
|
||||
| Init startet den Reader als root in der SELinux-Domain `tpvdebug` | **BEWIESEN** | Init-RC, File-Context und Policy-`typetransition` |
|
||||
| Android Storaged meldet eMMC 5.1, PRE_EOL `0x01`, A/B `0x0a/0x0a` | **BEWIESEN** | Boot-Event plus lokale Felddefinition; AOSP-Code ordnet sysfs/EXT_CSD zu |
|
||||
| Philips-Property spiegelt den aktuellen Hardwarewert dieses Boots | **SEHR WAHRSCHEINLICH** | unabhängiger Storaged-Wert stimmt für A/B exakt überein; Philips-Binary bleibt gesperrt |
|
||||
| Reader verwendet genau den beobachteten Debugfs-Pfad | **PLAUSIBEL** | passendes Gerät und EXT-CSD-Pfad vorhanden; kein Reader-Open-Audit |
|
||||
| `PRE_EOL_INFO=0x01` (Normal) | **BEWIESEN** | `storaged_emmc_info` und geräteeigene Event-Tagdefinition |
|
||||
| CID, CSD, Hersteller und Modell | **NICHT VERIFIZIERBAR** | direkte sysfs-/Debugfs-Reads werden verweigert; kein OEM-Export |
|
||||
| Keine MMC-/EXT4-Fehler im sichtbaren Textfenster | **BEWIESEN** | exakte Volltextsuche; alle vier automatischen Treffer fachlich verworfen |
|
||||
| Historisch keine MMC-/EXT4-Fehler | **NICHT VERIFIZIERBAR** | Kernel-Log, PStore und EXT4-Zähler fehlen |
|
||||
| eMMC fällt unmittelbar aus | **NICHT VERIFIZIERBAR** | `0x0a` ist ein grobes Band; PRE_EOL ist noch normal, Fehlerzähler fehlen |
|
||||
| eMMC ist gesund | **nicht haltbar** | beide Lebensdauerfelder stehen bereits im höchsten noch nicht überschrittenen Band |
|
||||
|
||||
## Ergänzung aus dem vollständigen Bugreport
|
||||
|
||||
Der vollständige Bugreport bestätigt Property, OnBootLogger und die wiederholte
|
||||
`TVStorageMonitorService`-Auswertung. Zusätzlich enthält er einen Boot-Audit des
|
||||
Android-Dienstes `storaged`:
|
||||
|
||||
```text
|
||||
root 2644 storaged: avc: denied { read }
|
||||
path="/sys/kernel/debug/mmc0/mmc0:0001/ext_csd"
|
||||
scontext=u:r:storaged:s0
|
||||
tcontext=u:object_r:debugfs_mmc:s0
|
||||
```
|
||||
|
||||
Damit ist der vorhandene EXT_CSD-Knoten für das konkrete Gerät `mmc0:0001`
|
||||
direkt belegt. Der Audit beweist aber auch, dass selbst ein Prozess mit
|
||||
Linux-UID root nicht genügt: SELinux blockiert die `storaged`-Domain. Für eine
|
||||
Direktmessung wäre ein autorisierter OEM-/Engineering-Kontext mit passender
|
||||
Policy nötig.
|
||||
|
||||
Der gespeicherte Haupttext des Bugreports enthält keinen ausgeschriebenen
|
||||
`PRE_EOL_INFO`-Wert; die später rein lesend abgefragte Eventlog-Ringpuffer-
|
||||
Ausgabe enthält ihn jedoch als `eol=1`. Die Kernel-Log-Sektion ist
|
||||
wegen `klogctl`-Fehler leer, und `dumpstate` meldet
|
||||
`No IDumpstateDevice implementation`; ein Philips-/TPV-Board-Dump wurde nicht
|
||||
erzeugt. In allen verfügbaren Textartefakten wurden keine MMC-Timeouts,
|
||||
CRC-/ECC-Fehler, Controller-Resets, Block-I/O-Fehler, EXT4-/Journal-Fehler oder
|
||||
Read-only-Remounts gefunden. Die Treffer mit „I/O error“ waren TLS-/Netzwerk-
|
||||
Fehler. Das ist nur eine Aussage zum verfügbaren Diagnosefenster, kein
|
||||
Gesundheitsbeweis.
|
||||
|
||||
AOSP dokumentiert `IDumpstateDevice` als optionale, vom OEM zu implementierende
|
||||
HAL. Der OEM bestimmt, welche Boarddaten sie in einen Standard-Bugreport
|
||||
aufnimmt; die Implementierung benötigt regelmäßig passende SEPolicy-Regeln.
|
||||
Das Fehlen der HAL auf diesem Build erklärt daher konkret, weshalb `dumpstate`
|
||||
keinen privilegierten TPV-Health-Dump nachliefert. Quelle:
|
||||
[AOSP: Scoped vendor logging / IDumpstateDevice](https://source.android.com/docs/core/tests/debug/scoped-vendor-logging).
|
||||
|
||||
Die Beweiskette ist damit unabhängig doppelt: Android Storaged berichtet
|
||||
eMMC 5.1/EOL 1/A 10/B 10, während Philips separat A/B `0x0a/0x0a` persistiert
|
||||
und auswertet. Nur der genaue native Philips-Readerpfad und der vollständige
|
||||
EXT_CSD-Rohdump bleiben unlesbar.
|
||||
|
||||
Weitere Details: [Bugreport-Analyse](bugreport-analysis.md). Die vollständige
|
||||
lokale Rechte-/Prozessmatrix ist nicht veröffentlicht; die technische
|
||||
eMMC-/Kernel-Auswertung steht in
|
||||
[Tiefe eMMC-/Kernel-Forensik](deep-forensics/emmc/emmc-kernel-agent.md).
|
||||
|
||||
## Rohdaten
|
||||
|
||||
- `raw/emmc/emmc-identity-and-access.txt`
|
||||
- `raw/emmc/emmc-property-init-and-bootlog.txt`
|
||||
- `raw/emmc/emmc-filesystem-health-access.txt`
|
||||
- `raw/emmc/usagelogger.apk`
|
||||
|
||||
## Legitimer nächster Beweisweg
|
||||
|
||||
Für die Speicherwarnungsursache ist keine Privilegieneskalation nötig. Für die
|
||||
noch offene eMMC-Ausfallprognose wäre der risikoärmste, technisch vollständige
|
||||
Weg ein **autorisierter Philips/TPV-Service- oder Engineering-Export**, der
|
||||
ausschließlich lesend liefert:
|
||||
|
||||
1. EXT-CSD-Bytes 267, 268 und 269 sowie den vollständigen EXT-CSD-Dump zur
|
||||
forensischen Gegenprüfung der bereits exportierten Werte;
|
||||
2. CID/CSD und Herstellerkennung;
|
||||
3. EXT4-Zähler für `mmcblk0p37`;
|
||||
4. Kernel-/PStore-Zeilen zu `mmc0`, `mmcblk0`, `ext4` und `jbd2`;
|
||||
5. Reader-Zeitstempel und Rückgabestatus, um einen persistenten Altwert sicher
|
||||
auszuschließen.
|
||||
|
||||
Geeignete legitime Mittel sind ein signierter, exakt passender OEM-Servicebuild,
|
||||
ein Philips/TPV-Werkstatttool oder eine zukünftige Firmware mit korrekt
|
||||
implementiertem `IDumpstateDevice`. Ebenfalls ungefährlich wäre die lokale
|
||||
statische Analyse einer offiziell bezogenen passenden Firmware. Root-Exploit,
|
||||
Bootloader-Entsperrung, Recovery oder Flashen sind dafür weder erforderlich
|
||||
noch angemessen.
|
||||
320
diagnostics/epg-analysis.md
Normal file
320
diagnostics/epg-analysis.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# Konsolidierte Analyse von `EpgDatabase.db`
|
||||
|
||||
Stand: 2026-08-25
|
||||
Gerät: Philips 43PUS7394/12, TPM191E, Android TV 12
|
||||
Firmware: `TPM191E_R.211.000.103.000`
|
||||
|
||||
## Ergebnis in einem Satz
|
||||
|
||||
`/data/user/0/org.droidtv.channels/databases/EpgDatabase.db` ist mit
|
||||
1.742.053.376 Bytes der konkrete große Speicherverbraucher. Phase 4 hat die
|
||||
früher offene Verteilung direkt an einer hashidentischen, integritätsgeprüften
|
||||
Kopie entschieden: `scheduletable` enthält 3.071.250 Zeilen, alle zum Snapshot
|
||||
abgelaufen; Tabelle plus vier Indizes belegen rund 1.659 MiB.
|
||||
`freelist_count=0`, `auto_vacuum=FULL`. Die 1,62 GiB sind daher fast vollständig
|
||||
echte jahrelange Schedule-Altlast, nicht Freelist oder FVP. Details:
|
||||
[`phase4/epg-database-forensics.md`](phase4/epg-database-forensics.md).
|
||||
|
||||
## Beweisgrade
|
||||
|
||||
- **BEWIESEN**: direkt aus lokalem Manifest, DEX-Bytecode, SQL-Literal, Bugreport oder abgelegter Rohdiagnose.
|
||||
- **SEHR WAHRSCHEINLICH**: mehrere unabhängige Belege ergeben dieselbe Erklärung, eine Laufzeitvariable bleibt offen.
|
||||
- **PLAUSIBEL**: technisch passende Interpretation ohne direkten Beleg der Entwicklerabsicht oder konkreten Laufzeitwirkung.
|
||||
- **NICHT VERIFIZIERBAR**: mit dem derzeitigen lesenden Zugriff beziehungsweise den lokalen Artefakten nicht entscheidbar.
|
||||
|
||||
Diese Fassung konsolidiert ausschließlich Diagnose. Es wurde kein Cleanup, Delete, Rebuild, App-Reset oder sonstiger Reparaturpfad ausgeführt.
|
||||
|
||||
## 1. Datenbank und Schema
|
||||
|
||||
### Eigentümer und Version
|
||||
|
||||
- **BEWIESEN:** Paket `org.droidtv.channels`
|
||||
- **BEWIESEN:** Helper `org.droidtv.channels.epg.bcepg.EpgDatabaseHelper`
|
||||
- **BEWIESEN:** Superklasse `android.database.sqlite.SQLiteOpenHelper`
|
||||
- **BEWIESEN:** Dateiname `EpgDatabase.db`
|
||||
- **BEWIESEN:** Schemaversion **6**, als Integerargument im Helper-Konstruktor
|
||||
|
||||
### Physische Tabellen
|
||||
|
||||
`EpgDatabaseHelper.createTables()` legt an:
|
||||
|
||||
- `mappingtable`
|
||||
- `indextable`
|
||||
- `nowtable`
|
||||
- `nexttable`
|
||||
- `scheduletable`
|
||||
- `fvpscheduletable`
|
||||
- `fvpnownexttable`
|
||||
- `channelmapversiontable`
|
||||
- `fastipnownexttable`
|
||||
|
||||
`mappingtable`, `indextable`, `nowtable`, `nexttable`, `scheduletable` und `fastipnownexttable` besitzen Integer-Primärschlüssel; mehrere davon sind `AUTOINCREMENT`. Die FVP-Tabellen verwenden den zusammengesetzten Primärschlüssel `(channelid,starttime,endtime)` und haben keine `_id`-Spalte.
|
||||
|
||||
**BEWIESEN:** Der URI-Name `nownexttable` bezeichnet keine zusätzliche physische Tabelle. Der Provider erzeugt dafür den Join:
|
||||
|
||||
`indextable AS Dbit LEFT JOIN nowtable AS Now USING(presetid) LEFT JOIN nexttable AS Next USING(presetid)`
|
||||
|
||||
Die vollständigen DDL-Literale und Indizes sind in `diagnostics/deep-forensics/philips/epg-reverse-agent.md` sowie `epg-dex-context.txt` dokumentiert.
|
||||
|
||||
## 2. Bisher gemessene Größe und Churn
|
||||
|
||||
Vorhandene lesende Rohdaten:
|
||||
|
||||
| Objekt | aktuelle Zeilen | beobachtete IDs |
|
||||
|---|---:|---:|
|
||||
| `indextable` | 358 | 1.267.806 bis 1.268.163 |
|
||||
| `nowtable` | 195 | 4.431.681 bis 4.451.328 |
|
||||
| `nexttable` | 162 | 4.026.589 bis 4.027.685 |
|
||||
| Join-Endpunkt `nownexttable` | 358 | keine eigene physische ID |
|
||||
| `fastipnownexttable` | 0 | keine aktuellen Zeilen |
|
||||
|
||||
- **BEWIESEN:** Die hohen AUTOINCREMENT-Werte bei kleinen aktuellen Tabellen zeigen millionenfachen historischen Insert/Delete-Churn.
|
||||
- **BEWIESEN:** Diese Messung umfasst nicht `scheduletable`, `fvpscheduletable` und `fvpnownexttable`.
|
||||
- **BEWIESEN:** Eine unbeschränkte lesende Abfrage von `antenna/scheduletable` mit Projektion `_id` lief länger als 120 Sekunden und wurde clientseitig abgebrochen.
|
||||
- **SEHR WAHRSCHEINLICH:** Die Schedule-Menge ist sehr groß oder die Schedule-Abfrage leidet unter einer schwerwiegenden Provider-/SQLite-Latenz. Der Lauf beweist keine genaue Zeilenzahl und keinen konkreten Byteanteil.
|
||||
|
||||
Die frühere Kurzannahme „wenige aktuelle Zeilen, also fast alles Freelist“ ist damit nicht haltbar. Die kleinen Now/Next-Werte sagen nichts über Schedule/FVP aus.
|
||||
|
||||
## 3. Der Stale-Cleanup ist ein No-op
|
||||
|
||||
### Aufrufkette
|
||||
|
||||
Beim Provider-Start:
|
||||
|
||||
1. `EpgProvider$1.run()` synchronisiert Index und Kanalmapping.
|
||||
2. Es postet `EpgProvider$deleteStaleDataRunnable` nach exakt 120.000 ms.
|
||||
3. Die Logmeldung lautet `Posting stale data deletion after 2 mins in onCreate`.
|
||||
|
||||
Alternativer Alarmweg:
|
||||
|
||||
1. `EpgDatabaseHelper.setEpgStaleDataCleanUpAlarm()` würde einen RTC-Alarm auf `currentTimeMillis + 43.200.000 ms` setzen.
|
||||
2. Empfänger: `EpgStaleDataCleanupAlarmReceiver`
|
||||
3. Action: `org.droidtv.channels.epg.bcepg.EpgStaleDataCleanupAlarmReceiver.CLEAR_STALE_DATA`
|
||||
4. Der Receiver ruft `EpgInfoSingleton.cleanEpgStaleData()` auf.
|
||||
5. `EpgInfoSingleton$8.run()` ruft den Provider-Delete-Endpunkt `antenna/staledatacleanup` auf.
|
||||
6. Der Provider postet dasselbe `deleteStaleDataRunnable`.
|
||||
|
||||
**BEWIESEN:** In den vier DEX-Dateien von `channels.apk` existiert kein direkter Invoke-Aufrufer für `setEpgStaleDataCleanUpAlarm()`. Der Receiver plant sich nicht erneut ein. Ein externer oder reflektiver Aufruf ist nicht belegt.
|
||||
|
||||
### Inhalt des Runnables
|
||||
|
||||
`EpgProvider$deleteStaleDataRunnable.run()` macht bei definierter TV-Uhr:
|
||||
|
||||
1. `System.currentTimeMillis()` lesen
|
||||
2. `1800` direkt subtrahieren
|
||||
3. bei FreeView zusätzlich `604800` direkt subtrahieren
|
||||
4. `Deleting stale data for time <Grenzwert>` protokollieren
|
||||
|
||||
**BEWIESEN:** Danach folgt keine Mutation. In der vollständigen Methode fehlen:
|
||||
|
||||
- `SQLiteDatabase.delete()` oder `execSQL()`
|
||||
- `ContentResolver.delete()` oder `applyBatch()`
|
||||
- `EpgProgramPump.deleteChannelProgrambytime()`
|
||||
- `EpgProgramPump.deleteScheduleProgramsByTime()`
|
||||
- irgendein anderer sichtbarer Löschaufruf
|
||||
|
||||
Ist die Uhr nicht definiert, wird nur nach 30.000 ms erneut gepostet.
|
||||
|
||||
**BEWIESEN:** Der Boot-Log belegt exakt diesen No-op-Pfad, keine gelöschte Zeile:
|
||||
|
||||
- Logzeit: `2026-08-25 11:11:48.657`
|
||||
- ausgegebener Grenzwert: `1787649106837`
|
||||
- Grenzwert als Lokalzeit: `2026-08-25 11:11:46.837`
|
||||
|
||||
Die Differenz von rund 1,82 Sekunden passt zum direkt als Millisekunden subtrahierten Literal `1800`.
|
||||
|
||||
### Sichtbarer Einheitenfehler
|
||||
|
||||
- **BEWIESEN:** Basis ist Millisekundenzeit, die Konstanten werden ohne Faktor 1.000 subtrahiert.
|
||||
- **PLAUSIBEL:** `1800` sollte 30 Minuten in Sekunden und `604800` sieben Tage in Sekunden bedeuten.
|
||||
- Selbst bei korrekter Einheit bliebe der aktuelle Pfad wirkungslos, weil kein Delete folgt.
|
||||
|
||||
### Vorhandene, aber nicht verdrahtete Löschmethoden
|
||||
|
||||
- `EpgProgramPump.deleteChannelProgrambytime(long)` löscht Now, Next und Schedule mit `endtime < ?` per Batch.
|
||||
- `EpgProgramPump.deleteScheduleProgramsByTime(long)` löscht Schedule mit `endtime < ?`.
|
||||
- `EpgSyncThread.deleteScheduleProgramsByTime(long)` delegiert an die vorige Methode.
|
||||
|
||||
**BEWIESEN:** Keine dieser Methoden hat in den vier DEX-Dateien einen direkten Caller. Das Cleanup-Runnable ruft keine davon auf.
|
||||
|
||||
## 4. Rebuild-Semantik
|
||||
|
||||
Der Provider `org.droidtv.channels.epg.bcepg.EpgProvider` ist im Manifest mit Authority `org.droidtv.epgdvb` und `android:exported="true"` eingetragen. Das Provider-Element deklariert keine eigene Read-/Write-Permission.
|
||||
|
||||
Die drei relevanten Endpunkte werden über `ContentResolver.delete()` aktiviert und sind deshalb mutierend. Sie sind hier nur dokumentiert:
|
||||
|
||||
| Endpunkt | Codepfad | Wirkung |
|
||||
|---|---|---|
|
||||
| `antenna/staledatacleanup` | `deleteStaleDataRunnable` | aktuell nur Log/No-op |
|
||||
| `antenna/rebuilddb` | `EpgProvider$rebuildDbRunnable` | Index leeren, Senderindex und Mapping abgleichen, selektiv EPG-Zeilen anpassen |
|
||||
| `antenna/rebuilddbonnewchannellist` | `EpgProvider$rebuildDbOnNewChannelListRunnable` | EPG-Ereignis-/Index-/Versionszeilen leeren, Senderindex/Mapping neu aufbauen |
|
||||
|
||||
`EpgDatabaseHelper.dropTables()` droppt entgegen seinem Namen keine Tabellen. Es führt `SQLiteDatabase.delete(table,null,null)` für Index, Now, Next, Schedule, beide FVP-Tabellen, Channelmap-Version und Fast-IP-Now/Next aus. `mappingtable` wird anschließend in `syncOnChannelTableChange()` separat neu aufgebaut.
|
||||
|
||||
**BEWIESEN:** Die Rebuild-Runnables führen kein explizites `VACUUM` aus.
|
||||
|
||||
Interne Rebuild-Aufrufer:
|
||||
|
||||
- Senderlisten-Update-Ende und Installationsabbruch: regulärer `rebuilddb`
|
||||
- `CHANNEL_INSTALL_COMPLETE`: umfassender `rebuilddbonnewchannellist`
|
||||
|
||||
Das sind Lifecycle-Pfade der Senderinstallation, kein gefundener Benutzer-Reset.
|
||||
|
||||
## 5. Kein Philips-UI-Reset gefunden
|
||||
|
||||
Gefundene EPG-nahe Aktionen öffnen Guide, Planner, Now/Next, Aufnahmeliste oder Aufnahme-/Reminder-Dialoge. Keine Manifest-Activity und kein Service besitzt eine Reset-/Clear-/Rebuild-Aktion für die EPG-Datenbank.
|
||||
|
||||
Auch die Ressourcenquersuche fand keine Benutzerzeichenkette für „EPG-Datenbank zurücksetzen/löschen“.
|
||||
|
||||
Das Umschalten der TV-Guide-Quelle ist kein Ersatz. `tvstrings.apk` warnt ausdrücklich:
|
||||
|
||||
> Wenn Sie zu TV-Guide-Informationen aus dem Internet wechseln, gehen alle Aufnahmen verloren, die Sie mit Informationen der Sendeanstalt aufgenommen haben. Alle Erinnerungen und programmierten Aufnahmen werden abgebrochen.
|
||||
|
||||
Die Gegenrichtung enthält eine entsprechende Warnung.
|
||||
|
||||
**BEWIESEN:** Ein Wechsel Broadcaster ↔ Internet kann Aufnahme-/Timerdaten beeinträchtigen und darf nicht als harmlose Speicherbereinigung behandelt werden.
|
||||
|
||||
## 6. Abhängigkeiten
|
||||
|
||||
### Senderliste
|
||||
|
||||
- Kanonische Quelle: `android.media.tv.TvContract.Channels.CONTENT_URI`
|
||||
- `indextable` spiegelt Preset-/Senderdaten einschließlich `favoriteNumber`.
|
||||
- `mappingtable` verbindet `presetid` mit `svlid/svlrecid`.
|
||||
- Rebuild-Runnables lesen die Senderliste und bauen EPG-Spiegel neu auf; sie löschen die kanonische `TvContract.Channels`-Tabelle nicht direkt.
|
||||
|
||||
### Favoriten
|
||||
|
||||
- Separater Provider: `org.droidtv.tv.provider.favorite`
|
||||
- Unter anderem `FavoriteList`, Mixed-, Terrestrial-, Cable- und Satellite-Listen
|
||||
- `EpgDatabase.db` speichert nur Spiegel-/Versionswerte.
|
||||
- Die Rebuild-Runnables enthalten keinen direkten Delete gegen den Favoritenprovider.
|
||||
|
||||
### Aufnahmen, Erinnerungen und Timer
|
||||
|
||||
Persistenz außerhalb der EPG-Datenbank:
|
||||
|
||||
- Authority `org.droidtv.playtv.provider.recordremind`
|
||||
- DB `RecordingsRemindersList.db`
|
||||
- `RecordingsTable`
|
||||
- `RemindersTable`
|
||||
- `RecRemView`
|
||||
|
||||
Diese Tabellen stehen weder im EPG-Schema noch in `dropTables()`.
|
||||
|
||||
**BEWIESEN:** Ein EPG-Rebuild löscht diese externen Tabellen nicht direkt. `EpgProgramPump` korreliert Aufnahmen jedoch über `EventId` und `ChannelId` und aktualisiert Metadaten bei EPG-Ereignisänderungen.
|
||||
|
||||
**NICHT VERIFIZIERBAR:** Vollständige Nebenwirkungsfreiheit eines Rebuilds auf Event-ID-Verknüpfung, Darstellung, Folgeaufnahmen oder Scheduler. Externe Persistenz schützt vor direktem Tabellen-Delete, nicht zwingend vor Neuverknüpfungsproblemen.
|
||||
|
||||
## 7. `auto_vacuum` und Dateikompaktierung
|
||||
|
||||
- **BEWIESEN:** In allen vier `channels.apk`-DEX-Dateien fehlen `VACUUM`, `PRAGMA auto_vacuum`, `incremental_vacuum` und `wal_checkpoint`.
|
||||
- **BEWIESEN:** Die App setzt den Modus nicht selbst und benutzt `SQLiteOpenHelper`/Plattform-SQLite.
|
||||
- **BEWIESEN, aber nicht gerätespezifisch:** Die AOSP-libsqlite-Buildkonfiguration nutzt mindestens im Android-11-Branch `-DSQLITE_DEFAULT_AUTOVACUUM=1`, also Default Full-Auto-Vacuum. Für Android 12 ist dieselbe Buildstruktur vorhanden.
|
||||
- **SEHR WAHRSCHEINLICH:** Der Philips-Vendor-Build könnte diese AOSP-Vorgabe übernommen haben.
|
||||
- **NICHT VERIFIZIERBAR:** Ob TP Vision die Option verändert hat, mit welchem Default die konkrete DB ursprünglich angelegt wurde und welcher Modus im DB-Header steht.
|
||||
|
||||
Folge: Fehlendes appseitiges `VACUUM` beweist nicht automatisch eine riesige Freelist. Bei aktivem Full-Auto-Vacuum kann SQLite freie Endseiten bei Transaktionsende zurückgeben. Umgekehrt kann eine alte, anders angelegte DB den Modus beibehalten.
|
||||
|
||||
Erforderliche lesende Metadaten für eine Entscheidung wären:
|
||||
|
||||
- `auto_vacuum`
|
||||
- `page_size`
|
||||
- `page_count`
|
||||
- `freelist_count`
|
||||
- möglichst `dbstat` oder eine lesbare DB-Kopie
|
||||
|
||||
Diese Werte sind im unprivilegierten Providervertrag nicht als URI exponiert.
|
||||
|
||||
## 8. Query-Semantik und der Schedule-Hänger
|
||||
|
||||
### `antenna/scheduletable`
|
||||
|
||||
Dieser URI fragt nicht direkt `scheduletable` ab, sondern:
|
||||
|
||||
`indextable AS Dbit LEFT JOIN scheduletable AS Schedule USING(presetid)`
|
||||
|
||||
Bei Projektion `_id` erzeugt der Provider `Dbit.dbitindex AS _id`. Es gibt kein Count und kein Limit. Der Client muss jede Join-Zeile entgegennehmen.
|
||||
|
||||
Darum kann der >120-s-Lauf durch sehr viele Schedule-Zeilen entstehen. Er kann keine genaue Anzahl oder Byteverteilung liefern.
|
||||
|
||||
### Physischer Schedule-Pfad
|
||||
|
||||
`antenna/scheduletablesync` wird auf die physische `scheduletable` gemappt. Dieser Pfad vermeidet den Dbit-Join, besitzt aber keinen dokumentierten Count- oder Limit-Modus.
|
||||
|
||||
Die Tabelle hat Indizes auf `endtime`, `starttime`, `presetid` sowie `(presetid,eventid)`. Enge zeitliche oder presetbezogene Selektionen sind deshalb die risikoärmste Möglichkeit, kleine Stichproben zu lesen.
|
||||
|
||||
### FVP-Pfade
|
||||
|
||||
`fvpscheduletable` und `fvpnownexttable` haben keine `_id`; ihr Schlüssel ist `(channelid,starttime,endtime)`. Der generische Provider-Projektionsrewriter ersetzt `_id` mit `Dbit.dbitindex`, was auf einer direkten FVP-Tabelle ungültig ist und den beobachteten Mappingfehler erklärt.
|
||||
|
||||
Spezialpfade wie `fvpbackwardepg` reichen Projektionen direkter an SQLite weiter. Ein Aggregate-Count könnte dort rein lesend funktionieren, ist aber nicht als offizieller Philips-Count-Vertrag implementiert.
|
||||
|
||||
### Selection, Sortierung, Limit
|
||||
|
||||
- `GetNowNext`, `GetNowNextNoHidden`, `GetSchedule` und `getFastIpNowNextForChannel` sind Provider-Kommandos im Selection-Feld, keine freien Where-Ausdrücke.
|
||||
- Normale Join-Selektionen werden an Whitespace zerlegt und Unterstriche werden in Punkte umgeschrieben.
|
||||
- `sortOrder` wird nur auf den Sonderwert `PF_EVENT` geprüft; dieser löst Sync aus und liefert keinen Cursor.
|
||||
- Interne Querypfade setzen eigene Sortierungen wie `Dbit_presetnumber`, `Schedule_starttime` oder `starttime`.
|
||||
- Der `limit`-Parameter der abschließenden 8-Argument-`SQLiteDatabase.query()` ist immer `null`.
|
||||
|
||||
**BEWIESEN:** Es gibt keinen generischen, legitimen `LIMIT 1`-Pfad und keinen eigenen Count-Endpunkt. Ein `LIMIT` im Sortstring wäre weder zuverlässig noch eine akzeptable Diagnosemethode.
|
||||
|
||||
Ein Aggregate-Ausdruck `COUNT(*) AS row_count` als Projektion ist je nach URI möglicherweise technisch ausführbar, wird aber auf Join-/generischen Pfaden umgeschrieben oder verworfen. Er ist daher ohne enges Timeout nicht belastbar. Eine eindeutige Schlüssel-Selection liefert garantiert höchstens einen Datensatz, zählt aber die Tabelle nicht.
|
||||
|
||||
## 9. Aktuelle Ursachenbewertung
|
||||
|
||||
### Sicher
|
||||
|
||||
- `EpgDatabase.db` belegt 1,62 GiB und ist der große konkrete Speicherverbraucher.
|
||||
- Historischer EPG-Churn liegt im Millionenbereich.
|
||||
- Der als Stale-Cleanup bezeichnete Kernpfad löscht nichts.
|
||||
- Schedule/FVP sind bisher nicht zuverlässig gezählt.
|
||||
- Der Schedule-Join ist unbeschränkt und lief länger als 120 Sekunden.
|
||||
- Appseitiges explizites `VACUUM` fehlt.
|
||||
|
||||
### Sehr wahrscheinlich
|
||||
|
||||
- Der No-op-Cleanup ermöglicht das langfristige Anwachsen abgelaufener Schedule-Daten.
|
||||
- Schedule und eventuell FVP enthalten einen erheblichen lebenden Datenbestand oder verursachen zumindest den dominanten Abfrageaufwand.
|
||||
- Die Philips-Speicherwarnung wird durch die große EPG-Datei ausgelöst beziehungsweise wesentlich verstärkt.
|
||||
|
||||
### Offen
|
||||
|
||||
- Lebende Bytes je Tabelle
|
||||
- Freelist-/Fragmentierungsanteil
|
||||
- tatsächlicher Auto-Vacuum-Modus
|
||||
- WAL-/Journalbeitrag
|
||||
- exakte Rebuild-Nebenwirkungen auf Aufnahme-/Timer-Verknüpfungen
|
||||
|
||||
## 10. Risikoarme weitere Strategie
|
||||
|
||||
Weiterhin ausschließlich lesend:
|
||||
|
||||
1. Keine weitere unbeschränkte Ausgabe des `scheduletable`-Joins starten.
|
||||
2. Schedule nur über `scheduletablesync` in sehr engen, indexgestützten Zeit- oder Presetfenstern beproben und jeden Clientlauf kurz begrenzen.
|
||||
3. FVP nur über seine tatsächlichen Schlüsselspalten beziehungsweise geeignete Spezialpfade untersuchen; `_id` nicht verwenden.
|
||||
4. Aggregate-Projektionen nur als kurz begrenztes, rein lesendes Experiment auf Pfaden erwägen, die Projektionen statisch unverändert weiterreichen. Ein Fehlschlag darf nicht durch SQL-Injection umgangen werden.
|
||||
5. Nach Möglichkeit den DB-Header beziehungsweise `auto_vacuum`, `page_count`, `freelist_count` und `page_size` rein lesend beschaffen. Ohne diese Werte keine Aussage „überwiegend Freelist“ oder „überwiegend lebende Zeilen“ treffen.
|
||||
6. Vor jeder späteren mutierenden Maßnahme Senderlisten-, Favoriten-, Aufnahme- und Timer-Abhängigkeiten erneut prüfen und eine ausdrückliche Freigabe einholen.
|
||||
|
||||
Nicht ohne Freigabe ausführen:
|
||||
|
||||
- `staledatacleanup`, `rebuilddb` oder `rebuilddbonnewchannellist`
|
||||
- App-Daten-/Cache-Löschung
|
||||
- Guide-Quellenwechsel
|
||||
- Datenbank-, Datei- oder Paketmanipulation
|
||||
|
||||
## Belege
|
||||
|
||||
- `diagnostics/deep-forensics/philips/epg-reverse-agent.md`
|
||||
- `diagnostics/deep-forensics/philips/epg-dex-context.txt`
|
||||
- `diagnostics/deep-forensics/philips/epg-runnable-context.txt`
|
||||
- `diagnostics/deep-forensics/philips/epg-cleanup-context.txt`
|
||||
- `diagnostics/deep-forensics/philips/epg-stale-context.txt`
|
||||
- `diagnostics/deep-forensics/philips/epg-recording-deps-context.txt`
|
||||
- `diagnostics/deep-forensics/philips/epg-delete-rebuild-string-context.txt`
|
||||
- `diagnostics/deep-forensics/philips/channels-manifest.txt`
|
||||
- `diagnostics/deep-forensics/philips/tvstrings-epg-matches.txt`
|
||||
- `diagnostics/raw/storage/epg-content-evidence.txt`
|
||||
- `diagnostics/bugreport/extracted/files/01790__bugreport-PH9M_EA_5599-STT2.230526.001-2026-08-25-13-26-44.txt`
|
||||
123
diagnostics/phase4/epg-database-forensics.md
Normal file
123
diagnostics/phase4/epg-database-forensics.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# EPG-Datenbank-Forensik
|
||||
|
||||
## Kurzfazit
|
||||
|
||||
`EpgDatabase.db` ist 1,62 GiB groß, weil Philips seit Februar 2023 nahezu alle
|
||||
Schedule-Ereignisse akkumuliert hat. Die Datei enthält **3.071.250 reale
|
||||
`scheduletable`-Zeilen**. Zum Snapshotzeitpunkt waren **100 % abgelaufen**;
|
||||
97,81 % waren älter als 30 Tage und 79,03 % älter als ein Jahr. Es handelt
|
||||
sich nicht um eine große Freelist und nur zu 0,91 % um exakte Dubletten.
|
||||
|
||||
Das bestätigt die bereits statisch rekonstruierte Ursache: Der vorgesehene
|
||||
Philips-Stale-Cleanup wird gestartet und protokolliert einen Grenzwert, führt
|
||||
im Runnable aber keine SQLite-/Provider-Löschung aus. **CONFIRMED / VALIDATED
|
||||
LIVE plus Offline-DB-Beweis.**
|
||||
|
||||
## Konsistenter Snapshot
|
||||
|
||||
Original:
|
||||
`/data/user/0/org.droidtv.channels/databases/EpgDatabase.db`
|
||||
|
||||
- Prozess: `org.droidtv.channels`, PID 7267/31859 je Prozessneustart;
|
||||
- Connection-Pool: eine Primary-Connection, beim Precheck read-only und nicht
|
||||
acquired; Rollback-Journalmodus, kein WAL/SHM;
|
||||
- Journal vor und nach akzeptierter Kopie: 0 Byte;
|
||||
- Originalgröße/inode/mtime während Kopie und beider Hashläufe unverändert;
|
||||
- Original, adopted-USB-Kopie und PC-Kopie:
|
||||
`71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba`.
|
||||
|
||||
Ein erster Versuch wurde wegen gleichzeitig geändertem Original und
|
||||
Hash-Mismatch verworfen. Der zweite Lauf erfüllte alle Gates. Die Original-DB
|
||||
wurde nie geschrieben.
|
||||
|
||||
## SQLite-Metadaten
|
||||
|
||||
| PRAGMA/Check | Ergebnis |
|
||||
|---|---:|
|
||||
| `page_size` | 4.096 B |
|
||||
| `page_count` | 425.306 |
|
||||
| Dateigröße | 1.742.053.376 B = 1.661,35 MiB |
|
||||
| `freelist_count` | **0** |
|
||||
| `auto_vacuum` | **1 / FULL** |
|
||||
| `journal_mode` | `delete` |
|
||||
| `user_version` | 6 |
|
||||
| `schema_version` | 23 |
|
||||
| `quick_check` | `ok` |
|
||||
| `integrity_check` | `ok` |
|
||||
|
||||
Die frühere offene Freelist-/Auto-Vacuum-Frage ist damit geschlossen. Die
|
||||
Datei ist intakt und enthält keine freie Seitenreserve. FULL-Auto-Vacuum ist
|
||||
im persistenten DB-Header direkt bewiesen.
|
||||
|
||||
## Zeilen und Seiten
|
||||
|
||||
| Objekt | Typ | Seiten | MiB | Zeilen |
|
||||
|---|---|---:|---:|---:|
|
||||
| `scheduletable` | Tabelle | 367.868 | 1.436,98 | 3.071.250 |
|
||||
| `indexschedendtime` | Index | 16.827 | 65,73 | – |
|
||||
| `indexschedstarttime` | Index | 16.818 | 65,69 | – |
|
||||
| `indexschedeventid` | Index | 13.183 | 51,50 | – |
|
||||
| `indexschedpresetid` | Index | 10.019 | 39,14 | – |
|
||||
| alle übrigen Objekte zusammen | Tabelle/Index | ca. 76 | <0,3 | 919 + Metadaten |
|
||||
|
||||
Schedule-Tabelle plus vier Indizes belegen rund **1.659,04 MiB** und damit
|
||||
praktisch die ganze 1.661,35-MiB-Datei. Die etwa 2,03-MiB-Differenz besteht
|
||||
vorwiegend aus FULL-Auto-Vacuum-Pointer-Map-/sonstigen Nicht-`dbstat`-Seiten.
|
||||
|
||||
Die Schedule-Tabellen-Payload beträgt 1.079,65 MiB. Allein `shortinfo`
|
||||
enthält 961.046.485 Zeichen (ca. 916,5 Mi-Zeichen), `eventname` 67.158.355.
|
||||
`extendedinfo` und `guidanceinfo` sind in diesem Bestand leer. Teilweise
|
||||
unbenutzter Platz innerhalb aktiver B-Tree-Seiten ist keine Freelist.
|
||||
|
||||
## Altlasten
|
||||
|
||||
Cutoff war der Original-mtime-Zeitpunkt `1787680625` (2026-08-25).
|
||||
|
||||
| Altersklasse nach `endtime` | Zeilen | Anteil |
|
||||
|---|---:|---:|
|
||||
| aktuell/zukünftig | 0 | 0 % |
|
||||
| abgelaufen 0–1 Tag | 1 | <0,001 % |
|
||||
| 1–7 Tage | 8.887 | 0,289 % |
|
||||
| 7–30 Tage | 58.280 | 1,898 % |
|
||||
| 30–365 Tage | 576.718 | 18,778 % |
|
||||
| älter als 365 Tage | 2.427.353 | 79,035 % |
|
||||
| Zeitwert 0 | 11 | <0,001 % |
|
||||
|
||||
Nichtnull-Daten reichen von 2023-02-08 bis 2026-08-24. Es gibt 196 Presets;
|
||||
die größten halten je 30.000–63.888 historische Events. Alle Schedule-Presets
|
||||
existieren in `indextable`: **0 orphan rows**.
|
||||
|
||||
Logische Schlüssel-Dubletten `(presetid,eventid,starttime,endtime)` erklären
|
||||
28.001 überzählige Zeilen; exakte Vollzeilen-Dubletten 27.974, jeweils nur
|
||||
0,91 %. Der dominante Fehler ist daher fehlende Retention, nicht Duplikation.
|
||||
|
||||
## Abgleich mit dem Cleanup-Code
|
||||
|
||||
Live um 19:57/19:59:
|
||||
|
||||
- `Posting stale data deletion after 2 mins in onCreate`;
|
||||
- `deleteStaleDataRunnable: Triggering stale data cleanup`;
|
||||
- `Deleting stale data for time 1787680743361`.
|
||||
|
||||
Der geloggte Wert ist Millisekunden-nahe, während DB-Zeiten Sekunden sind.
|
||||
Entscheidender ist der statische Bytecodebeweis: Das Runnable enthält keinen
|
||||
`SQLiteDatabase.delete/execSQL`, `ContentResolver.delete/applyBatch` oder
|
||||
Aufruf einer echten Löschmethode. Zwei Minuten später war die 3,07-Mio.-Zeilen-
|
||||
Historie weiterhin vollständig vorhanden. Das ist die direkte Ursache des
|
||||
langjährigen Wachstums.
|
||||
|
||||
## Was wäre rückgewinnbar?
|
||||
|
||||
- Echte Live-Nutzlast in `scheduletable`: zum Cutoff **0 zukünftige/aktuelle
|
||||
Zeilen**; Now/Next sind separat klein.
|
||||
- Historische Altlast: praktisch die gesamten 1.437 MiB Tabelle plus 222 MiB
|
||||
Schedule-Indizes.
|
||||
- Gelöscht, aber nicht ans Dateisystem zurückgegeben: **0 Freelist-Seiten**.
|
||||
- Fragmentierung/unused auf aktiven Seiten: vorhanden, aber sekundär.
|
||||
|
||||
Eine tatsächliche Bereinigung wird hier bewusst **nicht** ausgeführt. Bei
|
||||
FULL-Auto-Vacuum kann ein millionenfaches `DELETE` erhebliche Page-Moves,
|
||||
Journaling und eMMC-Schreiblast auslösen. Ein app-kontrollierter Neuaufbau kann
|
||||
konzeptionell write-ärmer sein, muss aber Sender-/Favoriten-/Aufnahme-/Timer-
|
||||
Abhängigkeiten und den exakten Philips-Workflow berücksichtigen.
|
||||
|
||||
49
diagnostics/phase4/usb-volume-uid1000-analysis.md
Normal file
49
diagnostics/phase4/usb-volume-uid1000-analysis.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# USB-/Adopted-Storage-Analyse unter UID 1000
|
||||
|
||||
## Live-Messung
|
||||
|
||||
| Volume | Philips `mTotalSize` | Philips `mFreeSize` | UID1000 `df` frei |
|
||||
|---|---:|---:|---:|
|
||||
| Public FAT `/storage/A5F7-1E0B` | 62.495.129.600 B | 0 | 44.214.720 KiB |
|
||||
| Adopted private `/mnt/expand/0ca9…60fc` | 123.060.879.360 B | 0 | 116.529.660 KiB |
|
||||
|
||||
UID1000 kann beide Pfade statten und mit `df` korrekt messen. Es existiert in
|
||||
ContentExplorer, EPGData, Settings oder TPTvService keine gefundene private
|
||||
Volume-Datenbank, die den Nullwert persistiert. **A und B sind damit stark
|
||||
widerlegt; C als allgemeines Dateisystem-Permissionproblem ist widerlegt.**
|
||||
|
||||
## Konkreter Codepfad
|
||||
|
||||
`/system/framework/tvfw.jar`, SHA-256
|
||||
`a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad`,
|
||||
wurde lokal read-only disassembliert. In
|
||||
`UsbVolumeListerImpl.getVolumes(String)`:
|
||||
|
||||
1. `IStorageManager.getVolumes()` liefert korrekte `VolumeInfo`-Objekte.
|
||||
2. `mRawDiskSize` wird aus `DiskInfo.size` gesetzt; daher sind Totalwerte korrekt.
|
||||
3. `mFreeDiskSize` wird **nur** aus `DeviceManager.getMountPoint(...).mFreeSize`
|
||||
gesetzt, wenn `SystemProperties.get("sys.mtk.dmremoteservice.ready")`
|
||||
exakt `"1"` ist.
|
||||
4. Live ist `sys.mtk.dmremoteservice.ready` leer. Vorhanden ist stattdessen
|
||||
`vendor.mtk.dmremoteservice.ready=0`; der HAL-Prozess
|
||||
`vendor.mediatek.tv.mtkdmservice@1.0-service` läuft.
|
||||
5. Der Initialwert des Java-`long` bleibt deshalb 0 und wird so geloggt.
|
||||
|
||||
Beim adopted Volume ist `VolumeInfo.internalPath=null`, aber der Code nimmt es
|
||||
trotzdem in die Liste auf. Das erklärt einen zusätzlichen Pfadmangel für D.
|
||||
Da selbst das public Volume mit gültigem `path` und `internalPath` denselben
|
||||
Nullwert erhält, ist die gemeinsame Property-/DeviceManager-Gate-Logik der
|
||||
entscheidende Fehler.
|
||||
|
||||
## Klassifikation A–E
|
||||
|
||||
| Hypothese | Urteil |
|
||||
|---|---|
|
||||
| A: nur zur Laufzeit falsch | **CONFIRMED** |
|
||||
| B: Nullwert in DB/Preference gespeichert | **DISPROVED** im inventarisierten relevanten Privatbestand |
|
||||
| C: StatFs-/Pfad-Permission unter UID1000 | **DISPROVED** |
|
||||
| D: falscher/fehlender Pfad | **PARTIALLY CONFIRMED** nur für adopted `internalPath`; erklärt public nicht |
|
||||
| E: gemeinsame Firmwarebibliothek | **CONFIRMED**: Fehler liegt in `tvfw.jar`-`UsbVolumeListerImpl` |
|
||||
|
||||
Keine Property wurde verändert und kein Dienst neu gestartet.
|
||||
|
||||
24
diagnostics/phase5/adopted-app-move-matrix.md
Normal file
24
diagnostics/phase5/adopted-app-move-matrix.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Adopted-storage app move matrix
|
||||
|
||||
Live state 25.08.2026: install location `0[auto]`; no package code path appears
|
||||
under `/mnt/expand/.../app`. All listed update APKs are currently in internal
|
||||
`/data/app`; their base packages remain system/product/vendor apps.
|
||||
|
||||
| Package | Current code | Kind | installLocation evidence | Official move? | Reason |
|
||||
|---|---|---|---|---|---|
|
||||
| `com.google.android.gms` | `/data/app` update; base `/system/product/priv-app` | updated privileged system app | manifest value not exposed; global auto | **No** | PackageManager blocks system packages; data stays internal |
|
||||
| `com.netflix.ninja` | `/data/app` update; base `/vendor/app` | updated system app | not exposed | **No** | still flagged SYSTEM/UPDATED_SYSTEM_APP |
|
||||
| `com.google.android.webview` | `/data/app` update; product base | updated system app | not exposed | **No** | system WebView is boot/runtime critical |
|
||||
| `com.google.android.tts` | `/data/app` update; product base | updated system app | not exposed | **No** | SYSTEM/UPDATED_SYSTEM_APP |
|
||||
| `com.google.android.youtube.tv` | `/data/app` update; product base | updated system app | not exposed | **No** | SYSTEM/UPDATED_SYSTEM_APP |
|
||||
| `com.android.vending` | `/data/app` update; privileged product base | updated privileged system app | not exposed | **No** | system/privileged package |
|
||||
| `org.droidtv.channels` | `/system/priv-app` | Philips privileged system app | fixed system | **No** | code and shared UID/system integration; app data internal |
|
||||
| `org.droidtv.gel` | `/system/app` | Philips system app | fixed system | **No** | system/shared UID integration |
|
||||
| `org.droidtv.playtv` | `/system/app` | Philips system app | fixed system | **No** | system TV component |
|
||||
| `org.droidtv.epgdata` | `/system/priv-app` | Philips privileged system app | fixed system | **No** | privileged TV component |
|
||||
|
||||
This is a capability conclusion, not a move attempt. `pm move-package` was not
|
||||
executed. Ordinary non-system apps that declare/permit external placement may
|
||||
be movable through Android, but none of the dominant listed updated-system
|
||||
packages is a safe candidate. Even a movable APK does not imply every app-data
|
||||
directory or device-protected data moves.
|
||||
51
diagnostics/phase5/adopted-storage-analysis.md
Normal file
51
diagnostics/phase5/adopted-storage-analysis.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Adopted USB architecture
|
||||
|
||||
## Confirmed live layout
|
||||
|
||||
- `disk:8,0` is a 123.060.879.360-byte adoptable USB disk.
|
||||
- `private:8,2` is mounted ext4 at
|
||||
`/mnt/expand/0ca93141-9ebf-42fa-9123-dfb75fff60fc` through `/dev/block/dm-4`;
|
||||
free 114.828.268 KiB. Device-mapper plus vold's private-volume model confirms
|
||||
an encrypted adopted mapping; the exact cipher/key metadata is not readable
|
||||
to shell and remains **UNKNOWN**.
|
||||
- The volume is Android `PRIVATE`, and its UUID is the **primary storage UUID**.
|
||||
Its `media/` directory backs the primary emulated storage through sdcardfs and
|
||||
FUSE at `/storage/emulated/0`.
|
||||
- `/data` remains `/dev/block/mmcblk0p37`, ext4, 5.089.920 KiB. Therefore a
|
||||
112-GiB `/storage/emulated` does not enlarge internal `/data`.
|
||||
- The adopted root contains standard `app`, `user`, `user_de`, `media`, `misc`
|
||||
directories, but no package code path currently resolves below its `app`.
|
||||
- A second `disk:8,16` is public VFAT (`A5F7-1E0B`).
|
||||
|
||||
## Options for EPG placement
|
||||
|
||||
| Variant | Classification | Main issues |
|
||||
|---|---|---|
|
||||
| App patch using an app-private adopted `Context` | **POSSIBLE WITH PATCH; preferred selective experiment** | Channels is a system/shared-UID app and must choose a private volume explicitly; boot/unmount fallback, migration and locking required |
|
||||
| DB path directly under `/mnt/expand/<uuid>/user...` | **POSSIBLE WITH PATCH, brittle** | volume UUID/readiness, sandbox/fscrypt/SELinux paths and OTA compatibility |
|
||||
| Bind-mount only EPG DB directory | **HIGH-RISK HACK** | root/init, vold timing, mount namespace, SELinux, USB loss, recovery |
|
||||
| Symlink | **NOT PRACTICAL** | Android data-directory traversal/SELinux and emulated storage SQLite locking; FUSE target is wrong |
|
||||
| init mount unit for EPG | **HIGH-RISK HACK** | creates early-boot/vold dependency and recovery complexity |
|
||||
| Move all Channels appdata | **POSSIBLE WITH PLATFORM PATCH, not recommended** | boot-critical shared UID consumers, DE/CE split, failures affect TV UI |
|
||||
|
||||
Never put SQLite on `/storage/emulated`/FUSE: use the private ext4 view, preserve
|
||||
atomic rename/fsync and fail closed to a small internal DB when USB is absent.
|
||||
|
||||
## Entire `/data` on USB
|
||||
|
||||
**NOT PRACTICAL / unacceptable boot risk.** `/data` is mounted by early init
|
||||
before vold adopts USB; FBE/fscrypt user setup, SELinux labels, zygote, Package
|
||||
Manager and recovery depend on it. USB enumeration/power/UUID failure would
|
||||
prevent a normal boot. Required fstab/init/recovery/format/OTA changes intersect
|
||||
locked AVB and static partitions. The boot log already shows transient zygote
|
||||
failures while the adopted `user` path was not yet present—evidence that making
|
||||
the whole boot depend on USB would worsen a real ordering edge.
|
||||
|
||||
## Ranking
|
||||
|
||||
1. Keep `/data` internal and repair EPG/GEL leaks – best reliability/OTA/boot.
|
||||
2. Selectively place large, reconstructible, non-boot-critical payloads on
|
||||
private adopted ext4 with an internal fallback.
|
||||
3. Android-supported movement of genuinely movable user apps – low risk but
|
||||
little gain here.
|
||||
4. Entire `/data` or bind-mount hacks – worst recovery and boot safety.
|
||||
54
diagnostics/phase5/epg-dependency-matrix.md
Normal file
54
diagnostics/phase5/epg-dependency-matrix.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Phase 5A – EPG-Abhängigkeiten vor Reparatur
|
||||
|
||||
Quelle ist ausschließlich der verifizierte Snapshot mit SHA-256
|
||||
`71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba`.
|
||||
|
||||
## Tabellenmatrix
|
||||
|
||||
| Tabelle | Zeilen | Größe | Schlüssel/Indizes | Zweck und Abhängigkeit | Kandidat |
|
||||
|---|---:|---:|---|---|---|
|
||||
| `android_metadata` | 1 | 4 KiB | keine PK/FK | SQLite-Locale; keine TV-Nutzdaten | exakt übernehmen |
|
||||
| `channelmapversiontable` | 0 | 4 KiB | `id` PK | Versionscache für Sender-/Favoritenspiegel | exakt übernehmen (leer) |
|
||||
| `indextable` | 358 | 24 KiB | `dbitindex` PK; `(satnum,sid,tsid,onid)`, `(presetid,presetnumber)` | EPG-interner Spiegel von Sender/Preset, Name, `favoriteNumber`, `svlid/svlrecid`; kanonische Sender liegen außerhalb | **exakt übernehmen** |
|
||||
| `mappingtable` | 203 | 4 KiB | `mappingid` PK; `(svlid,svlrecid)` | Preset-zu-SVL-Mappingcache | **exakt übernehmen** |
|
||||
| `nowtable` | 195 | 84 KiB plus 2 kleine Indizes | `_id` PK | reines Now-EPG; alle Werte im Snapshot veraltet | leeren |
|
||||
| `nexttable` | 162 | 76 KiB plus 2 kleine Indizes | `_id` PK | reines Next-EPG; alle Werte veraltet | leeren |
|
||||
| `scheduletable` | 3.071.250 | 1.436,98 MiB plus 222,06 MiB Indizes | `_id` PK; End-/Startzeit, Preset, `(presetid,eventid)` | ausschließlich Broadcast-Schedule; 100 % abgelaufen | **leeren** |
|
||||
| `fastipnownexttable` | 0 | 4 KiB | `_id` PK | Fast-IP Now/Next | leer lassen |
|
||||
| `fvpnownexttable` | 0 | 4 KiB plus Auto-/Sekundärindex | PK `(channelid,starttime,endtime)` | FVP Now/Next | leer lassen |
|
||||
| `fvpscheduletable` | 0 | 4 KiB plus Auto-/Sekundärindex | PK `(channelid,starttime,endtime)` | FVP Schedule | leer lassen |
|
||||
|
||||
## Relationen und persistente TV-Daten
|
||||
|
||||
- `PRAGMA foreign_key_list` ist für **jede Tabelle leer**.
|
||||
- Es gibt **keine Trigger und keine Views**.
|
||||
- Die kanonische Senderliste ist `TvContract.Channels` in einem separaten
|
||||
Provider. `indextable` ist nur Spiegel/Cache, wird aber vorsichtshalber exakt
|
||||
erhalten.
|
||||
- Favoriten liegen in `org.droidtv.tv.provider.favorite`; EPG speichert nur
|
||||
Spiegelwerte wie `favoriteNumber`.
|
||||
- Aufnahmen/Timer/Reminder liegen in separater
|
||||
`/data/vendor/user_setting/rw/RecordingsRemindersList.db` mit externem
|
||||
Provider. Keine ihrer Tabellen kommt im EPG-Schema vor.
|
||||
- Der Philips-Rebuild liest die Senderquelle neu und löscht die externen
|
||||
Sender-/Favoriten-/Recordingtabellen nicht direkt.
|
||||
- Event-ID-Verknüpfungen können während einer leeren EPG-Phase vorübergehend
|
||||
keine Metadaten auflösen. Im Snapshot existiert aber ohnehin **kein einziges
|
||||
aktuelles oder zukünftiges Schedule-Event**.
|
||||
|
||||
## Öffnen, Erstellen und Rebuild
|
||||
|
||||
`EpgDatabaseHelper` erbt `SQLiteOpenHelper`, Dateiname `EpgDatabase.db`,
|
||||
Version 6. `createTables()` legt exakt die obigen Tabellen und Indizes an.
|
||||
`dropTables()` ist irreführend benannt und führt große zeilenweise
|
||||
`SQLiteDatabase.delete(...,null,null)`-Operationen aus. Der
|
||||
`rebuilddbonnewchannellist`-Pfad leert EPG-Tabellen und baut Index/Mapping aus
|
||||
`TvContract.Channels` neu. Ein echtes Datei-Neuanlegen oder `VACUUM` ist dort
|
||||
nicht implementiert.
|
||||
|
||||
**Ergebnis:** Eine schemaidentische kompakte DB mit erhaltenem
|
||||
`indextable`/`mappingtable` und leeren EPG-Payloadtabellen zerstört keine
|
||||
kanonische Sender-, Favoriten-, Aufnahme- oder Timerpersistenz. Dieses Urteil
|
||||
ist für direkte Tabellenlöschung **CONFIRMED**; visuelle und Event-ID-bezogene
|
||||
Folgewirkungen bleiben bis zur Live-Prüfung `USER VALIDATION REQUIRED`.
|
||||
|
||||
37
diagnostics/phase5/epg-repair-options.md
Normal file
37
diagnostics/phase5/epg-repair-options.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Phase 5B – Vergleich der EPG-Reparaturstrategien
|
||||
|
||||
| Methode | eMMC-Schreiblast / Peak-Space | Datenrisiko | Rollback | Gewinn / EPG-Neuladen | Urteil |
|
||||
|---|---|---|---|---|---|
|
||||
| A: `DELETE` von 3.071.250 Zeilen | sehr hoch: Indexupdates, FULL-AutoVacuum-Page-Moves und DELETE-Journal; Peak potenziell hunderte MiB bis problematisch nahe aktuellem Freiraum | geringe semantische Selektivität bei korrektem Cutoff, aber lange Transaktion/Lock/Stromausfallrisiko | Originalsnapshot vorhanden, Live-Rollback teuer | etwa 1,6 GiB; App behält Schema | **nicht empfohlen** |
|
||||
| B: `DROP/CREATE scheduletable` + Indizes | deutlich weniger SQL-Operationen als A, aber DDL/Journaling und FULL-AutoVacuum auf der 1,6-GiB-Datei; App besitzt keinen validierten Live-DDL-Endpunkt | nur Schedule, aber Schema-/Connectioncache- und offener-DB-Risiko | Snapshot; eigenes DDL fehleranfällig | etwa 1,6 GiB; Nachladen wahrscheinlich | möglich, unnötig komplex |
|
||||
| C: kompakte Ersatz-DB | intern nur ca. 160 KiB neue Datei plus atomarer Rename; kein zweiter 1,6-GiB-Stand auf `/data` | behält Senderindex/Mapping exakt, leert nur EPG-Payload; Owner/Label/Open-Connection müssen stimmen | hashgeprüfter PC- und USB-Snapshot; atomarer Rücktausch möglich | erwartet 1.701.068 KiB; leerer Now/Next/Schedule erzwingt frische Befüllung | **eindeutige Empfehlung** |
|
||||
| D: Philips `rebuilddbonnewchannellist` | sehr hoch: intern mehrere `SQLiteDatabase.delete` über Millionen Zeilen plus FULL-AutoVacuum | kanonische Sender/Favoriten nicht direkt gelöscht, aber Mapping komplett rekonstruiert; Eventverknüpfungsrisiko | Snapshot, aber laufender Appworkflow schwerer zu kontrollieren | wahrscheinlich ähnlich, jedoch unnötige Writes | offiziell app-kontrolliert, aber für verschlissene eMMC nicht optimal |
|
||||
|
||||
## Empfehlung
|
||||
|
||||
**Methode C** ist bei diesem konkreten Datenbestand schreibärmster,
|
||||
rückrollbarster und semantisch konservativster. Die Kandidaten-DB wird aus dem
|
||||
Originalschema erstellt, übernimmt ausschließlich:
|
||||
|
||||
- `android_metadata`;
|
||||
- `channelmapversiontable`;
|
||||
- `indextable` inklusive Sender-/Favoritenspiegel;
|
||||
- `mappingtable`.
|
||||
|
||||
Now/Next/Schedule/FVP/FastIP bleiben leer. Das ist konservativer als ein
|
||||
vollständiger Philips-Rebuild, weil die bestehenden Sender- und
|
||||
Preset-Spiegelzeilen unverändert bleiben.
|
||||
|
||||
## Lokal validierter Kandidat
|
||||
|
||||
- Größe: **159.744 B / 39 Seiten**;
|
||||
- SHA-256: `1ae978c7ca9333c6a0528ad535a732f116deb7213321e9d0bd9c0d377a25aa5b`;
|
||||
- exakt gleiche `sqlite_master`-Objekte und SQL-Definitionen;
|
||||
- `page_size=4096`, `auto_vacuum=FULL`, `journal_mode=delete`, `user_version=6`;
|
||||
- alle vier übernommenen Tabellen besitzen identische kanonische Zeilenhashes;
|
||||
- `quick_check=ok`, `integrity_check=ok`, `foreign_key_check=[]`;
|
||||
- alle Foreign-Key-Listen leer; keine Trigger/Views.
|
||||
|
||||
Erwarteter unmittelbarer Dateigewinn: **1.741.893.632 B = 1.701.068 KiB =
|
||||
1.661,20 MiB = 1,6223 GiB**.
|
||||
|
||||
120
diagnostics/phase5/epg-repair.md
Normal file
120
diagnostics/phase5/epg-repair.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Phase 5 – kontrollierte EPG-Reparatur
|
||||
|
||||
Stand: 2026-08-25, Live-Austausch und technische Sofortvalidierung abgeschlossen.
|
||||
|
||||
## Gewählte Methode
|
||||
|
||||
**Methode C: atomarer Austausch gegen eine offline gebaute kompakte DB.** Das
|
||||
Schema, alle Indizes und `user_version=6` sind identisch. Die persistenten
|
||||
Tabellen `android_metadata`, `indextable` und `mappingtable` wurden zeilengleich
|
||||
übernommen; `channelmapversiontable` war und bleibt leer. Ausschließlich die
|
||||
EPG-Nutzlasttabellen (Schedule, Now/Next, FVP und FastIP) beginnen leer.
|
||||
|
||||
Diese Methode vermeidet 3.071.250 Einzel-Deletes, die Auto-Vacuum-
|
||||
Seitentransporte und eine große temporäre Schreiblast. Der Austausch erfolgt
|
||||
erst nach Prozess-Stopp und erneuter Hash-/DAC-/SELinux-Prüfung über eine
|
||||
159.744-Byte-Temporärdatei im originalen Datenbankverzeichnis.
|
||||
|
||||
## BEFORE
|
||||
|
||||
- `/data`: 5.089.920 KiB gesamt, 3.613.240 KiB benutzt, 1.355.544 KiB frei,
|
||||
73 % belegt.
|
||||
- Original: 1.742.053.376 Byte, Inode 131613, Owner/Group `system:system`,
|
||||
Mode `660`, Label `u:object_r:system_app_data_file:s0`.
|
||||
- Original-SHA-256:
|
||||
`71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba`.
|
||||
- Schedule: 3.071.250 Zeilen; zum Snapshotzeitpunkt vollständig abgelaufen.
|
||||
- Journal: vorhanden, 0 Byte; kein offener Channels-Prozess und kein durch
|
||||
`lsof` sichtbarer EPG-Handle im abschließenden Preflight.
|
||||
|
||||
## Rollback-Gates
|
||||
|
||||
| Gate | Ergebnis |
|
||||
|---|---|
|
||||
| PC-Snapshot | PASS – Größe und SHA-256 verifiziert |
|
||||
| adopted-storage-Snapshot | PASS – gleicher SHA-256 |
|
||||
| Original integer | PASS – `integrity_check` und `quick_check` = `ok` |
|
||||
| Kandidat lokal getestet | PASS – Schema identisch, FK-Check leer |
|
||||
| Abhängigkeiten | PASS – Sender/Favoriten und Recordings/Reminder extern; benötigte Mappings erhalten |
|
||||
| Prozess stoppbar/offene Verbindung | PASS – Preflight ohne PID/Handle; vor Commit erneut geprüft |
|
||||
| Owner/Group/Mode/Label | PASS – bekannt; Tempdatei muss exakt entsprechen |
|
||||
| freier Peak-Speicher | PASS – Kandidat benötigt nur 159.744 Byte intern |
|
||||
| Rücksicherung | PASS – vorab definierter, hash-gegatterter atomarer Restore vom USB-Snapshot |
|
||||
|
||||
Alle 14 vom Auftrag geforderten Gates sind erfüllt. Bei jeder Abweichung
|
||||
bricht das Swap-Skript vor dem Rename ab und entfernt nur seine exakt benannte
|
||||
Temporärdatei.
|
||||
|
||||
## ACTION
|
||||
|
||||
Ausgeführt: `org.droidtv.channels` kontrolliert gestoppt; PID-Freiheit geprüft;
|
||||
Original- und Kandidatenhash erneut prüfen; Kandidat in dasselbe Verzeichnis
|
||||
kopieren; Mode, Owner, Group, SELinux-Label und Hash prüfen; unmittelbar vor
|
||||
dem atomaren `mv` nochmals PID-Freiheit prüfen; danach `sync` und Live-Hash.
|
||||
|
||||
## AFTER
|
||||
|
||||
- `COMMIT_OK`; neue DB 159.744 Byte, Inode 140395, `system:system`, Mode 660,
|
||||
Label `u:object_r:system_app_data_file:s0`.
|
||||
- Commit-SHA-256 entsprach exakt dem offline geprüften Kandidaten:
|
||||
`1ae978c7ca9333c6a0528ad535a732f116deb7213321e9d0bd9c0d377a25aa5b`.
|
||||
- Nach normalem Providerstart führte Channels den erwarteten Mapping-Sync aus;
|
||||
die konsistente Nachkopie hat SHA-256
|
||||
`67f8834a7e56d3b84b47f0c75b3781703915b0d1f93e93c42312c69796e6cb36`.
|
||||
- SQLite: `quick_check=ok`, `integrity_check=ok`, FK-Check leer, Version 6,
|
||||
39 Seiten, Freelist 0, Auto-Vacuum FULL.
|
||||
- Zeilen: Schedule 0; Now/Next 0; `indextable` 358 und `mappingtable` 203.
|
||||
- Zwei konsistente Nachmessungen mehrere Minuten auseinander waren bytegleich.
|
||||
- Die unabhängigen Hashes von `RecordingsRemindersList.db`, `tv.db` und
|
||||
`list.db` blieben gegenüber dem Preflight unverändert.
|
||||
|
||||
### Speicher
|
||||
|
||||
| Messwert | Vorher | Nachher | Differenz |
|
||||
|---|---:|---:|---:|
|
||||
| `/data` frei (`df -k`) | 1.355.512 KiB | 3.056.912 KiB | **+1.701.400 KiB = 1.661,52 MiB = 1,62258 GiB** |
|
||||
| `/data` Belegung | 73 % | 39 % | −34 Prozentpunkte |
|
||||
| EPG-Datei | 1.742.053.376 B | 159.744 B | **−1.741.893.632 B = 1.701.068 KiB = 1.661,20 MiB** |
|
||||
| Schedule-Zeilen | 3.071.250 | 0 | −3.071.250 |
|
||||
|
||||
## ROLLBACK
|
||||
|
||||
`phase5-epg-rollback.json` stellt ausschließlich die EPG-DB aus dem
|
||||
verifizierten USB-Snapshot über eine neue Tempdatei und atomaren Rename wieder
|
||||
her. Auch dieser Pfad prüft PID, Hash, Mode, Owner/Group und SELinux-Label vor
|
||||
dem Commit. Er wird nur bei fehlgeschlagener Funktionsvalidierung ausgeführt.
|
||||
|
||||
## VALIDATION
|
||||
|
||||
**VALIDATED LIVE (softwareseitig):** ADB blieb erreichbar; WLAN blieb
|
||||
`CONNECTED/VALIDATED`; Android meldet internes und adopted Volume `NORMAL`;
|
||||
Channels, EPGData, PlayTV und TVInput liefen. Der Provider öffnete die DB und
|
||||
führte erfolgreiche Queries sowie den Sync von 358 Channels/203 Mappings aus.
|
||||
Keine SQLite-Exception, Korruptionsmeldung, ANR oder Crashloop trat auf. Der
|
||||
einzige neue Exit war der dokumentierte `USER REQUESTED` Force-Stop.
|
||||
|
||||
Der nach zwei Minuten geloggte Philips-Stale-Cleanup lief erneut und blieb ohne
|
||||
Mutation – reproduzierbarer Beleg für BUG-001. Im Einstellungsmodus loggte der
|
||||
Sync zunächst `Mode Guide/WatchTV: false`. Nach echtem TV-/Guide-Betrieb wuchs
|
||||
die DB kontrolliert auf 806.912 Byte. Die dritte konsistente Momentaufnahme
|
||||
enthielt 59 Now-, 48 Next- und 1.041 Schedule-Zeilen; Schedule reichte lokal vom
|
||||
25.08.2026 19:00 bis 01.09.2026 02:50. `integrity_check=ok`, 197 Seiten,
|
||||
Freelist 0. Aktuelle EPG-Neubefüllung ist damit **VALIDATED LIVE**.
|
||||
|
||||
Der normale kanonische Sync reduzierte den internen Spiegel im späteren
|
||||
Snapshot von 358/203 auf 357/202 Index-/Mapping-Zeilen. Da der Kandidat anfangs
|
||||
exakt 358/203 enthielt und die App selbst den späteren Sync ausführte, ist dies
|
||||
kein Beleg für blind gelöschte Senderdaten; die sichtbare Senderliste bleibt
|
||||
dennoch zu prüfen.
|
||||
|
||||
Folgende Punkte bleiben **USER VALIDATION REQUIRED**:
|
||||
|
||||
1. Live-TV-Bild/Ton und Senderwechsel prüfen.
|
||||
2. Senderliste und Favoriten öffnen und stichprobenartig vergleichen.
|
||||
3. Im bereits neu befüllten Guide Now/Next und zukünftige Sendungen visuell
|
||||
stichprobenartig prüfen.
|
||||
4. Vorhandene Aufnahmen/Timer/Reminder anzeigen (nichts neu programmieren nötig).
|
||||
5. Prüfen, ob die Speicherwarnung verschwunden ist.
|
||||
|
||||
USB-Snapshot und vorab geprüfte Rollback-Konfiguration bleiben bis zum Abschluss
|
||||
dieser Sichtprüfung erhalten.
|
||||
22
diagnostics/phase5/phase5-summary.md
Normal file
22
diagnostics/phase5/phase5-summary.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Phase 5 summary
|
||||
|
||||
**Repair: VALIDATED LIVE (software), visual UI validation pending.** A
|
||||
schema-identical compact DB preserved 358 channel mirror and 203 mapping rows,
|
||||
removed 3,071,250 expired schedule rows and recovered 1,701,400 KiB according
|
||||
to `df`. `/data` changed from 73% to 39% used.
|
||||
|
||||
After real TV/Guide activity the healthy DB grew to 806,912 bytes with 59 Now,
|
||||
48 Next and 1,041 Schedule rows spanning 2026-08-25 19:00 through 2026-09-01
|
||||
02:50 local time. Current/future EPG reacquisition is proven.
|
||||
|
||||
Confirmed functional vendor bugs: ineffective EPG stale cleanup, pathological
|
||||
GEL WorkManager history, USB free-size zero. The Philips warning threshold is a
|
||||
design issue; the GEL command path is a separate confidential security report.
|
||||
|
||||
Storage conclusion: adopted USB is valid private ext4 and primary emulated
|
||||
storage, but it does not enlarge `/data`; no package is currently installed on
|
||||
it. Keep `/data` internal. Prefer leak fixes and, only in a future patched
|
||||
firmware, selective private-volume placement with an internal fallback.
|
||||
|
||||
Root was not required for the repair. Root/OEM signing/unlock is required only
|
||||
for APK/JAR/system/SELinux/init/AVB changes or mount hacks.
|
||||
63
diagnostics/phase7/commands.md
Normal file
63
diagnostics/phase7/commands.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Phase 7 command record
|
||||
|
||||
Date: 2026-08-26. TV operations were read-only. `adb pull` copied files to the
|
||||
PC and did not change the TV. No install/uninstall/reboot/recovery/flash/remount/
|
||||
settings write was issued.
|
||||
|
||||
## Live ADB command families
|
||||
|
||||
Target was always `<TV_IP>:5555`:
|
||||
|
||||
```text
|
||||
adb devices -l
|
||||
adb shell pm path <package>
|
||||
adb shell dumpsys package <package>
|
||||
adb shell dumpsys package android
|
||||
adb shell dumpsys package packages
|
||||
adb shell cmd package help
|
||||
adb shell service list
|
||||
adb shell ps -A -o USER,PID,PPID,NAME,ARGS,LABEL
|
||||
adb shell getprop
|
||||
adb shell mount
|
||||
adb shell cat /proc/cmdline
|
||||
adb shell cat /proc/self/mountinfo
|
||||
adb shell ls -laZ <targeted system/vendor paths>
|
||||
adb shell stat <targeted APK/JAR/OAT/VDEX/update paths>
|
||||
adb shell sha256sum <targeted stock artifacts>
|
||||
adb shell dumpsys dbinfo org.droidtv.channels -v
|
||||
adb shell df -k /data
|
||||
```
|
||||
|
||||
Targeted local forensic acquisition:
|
||||
|
||||
```text
|
||||
adb pull /system/priv-app/oad/oad.apk diagnostics/phase7/original-apks/oad.apk
|
||||
adb pull /system/app/btota/btota.apk diagnostics/phase7/original-apks/btota.apk
|
||||
adb pull /system/app/USBService/USBService.apk diagnostics/phase7/original-apks/USBService.apk
|
||||
adb pull /system/framework/framework-res.apk diagnostics/phase7/original-apks/framework-res.apk
|
||||
adb pull /system/etc/security/otacerts.zip diagnostics/phase7/otacerts.zip
|
||||
```
|
||||
|
||||
The pulled APKs are multi-megabyte reference evidence and are intentionally
|
||||
excluded from the Phase-7 delta ZIP.
|
||||
|
||||
## Offline command/tool families
|
||||
|
||||
- `rg`/PowerShell inventory and exact-string searches in local bugreport,
|
||||
extracted init/fstab, DEX reports and prior phase documentation.
|
||||
- SHA-256 and size checks for baseline/live-copied artifacts.
|
||||
- `keytool -printcert` and APK signing-block parser for public X.509/v1/v2/v3
|
||||
metadata; no private-key search.
|
||||
- ZIP entry inspection and local DEX method/string analysis for Settings/OAD/
|
||||
USB update components.
|
||||
- `python tools/epg-health-check/health_check.py` in live read-only mode.
|
||||
- The same tool against the immutable historical EPG snapshot for validation.
|
||||
|
||||
## Important latest outputs
|
||||
|
||||
- `dumpsys dbinfo`: `EpgDatabase.db` 8,724,480 bytes.
|
||||
- `df`: userdata 5,089,920 KiB total, 3,047,232 KiB available, 39% used.
|
||||
- Health tool: `OK`, no repair attempted.
|
||||
- Historical snapshot test: 1,742,053,376 bytes, 3,071,250 schedule rows,
|
||||
3,004,958 older than 30 days, `quick_check=ok`,
|
||||
`MAINTENANCE_RECOMMENDED`.
|
||||
259
diagnostics/storage-analysis.md
Normal file
259
diagnostics/storage-analysis.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# Speicheranalyse – `/data` und adoptierter Speicher
|
||||
|
||||
Stand: 2026-08-25, vertieft bis 14:20 Uhr. Die TV-Abfragen waren bis auf die
|
||||
vom Auftrag ausdrücklich verlangte Android-Diagnose `dumpsys diskstats`
|
||||
lesend. Dieser Dienst meldet selbst einen 512-Byte-Latenztest (`[512B Data
|
||||
Write]`); er wird deshalb nicht nochmals verwendet.
|
||||
|
||||
## Kurzfazit
|
||||
|
||||
Die interne `/data`-Belegung ist jetzt zu rund **75,7 %** konkret erklärt.
|
||||
Der größte Einzelverursacher ist eine Philips-EPG-Datenbank mit **1,62 GiB**.
|
||||
Daneben ist eine Philips-WorkManager-Datenbank mit **218,8 MiB** extrem
|
||||
auffällig. Aktualisierte vorinstallierte Apps samt direkt messbaren
|
||||
Basiskompilaten beanspruchen mindestens weitere **912,6 MiB**. Ob und wie viel
|
||||
der beiden SQLite-Hauptdateien intern frei ist, bleibt ohne
|
||||
`freelist_count` unbekannt.
|
||||
|
||||
Das 112-GiB-adoptierte Volume ist korrekt als privater und primärer Speicher
|
||||
eingebunden, bleibt aber fast leer. Es kann diese Hauptverursacher nicht
|
||||
automatisch aufnehmen: Sie gehören zu Systempaketen, und Android verbietet das
|
||||
Verschieben von System-Apps. Auch alle 18 Pakete unter `/data/app` sind trotz
|
||||
ihres Pfades keine normalen Drittanbieter-Apps, sondern aktualisierte
|
||||
System-Apps.
|
||||
|
||||
## Objektive Lage
|
||||
|
||||
| Bereich | Größe/Belegung | Bewertung |
|
||||
|---|---:|---|
|
||||
| `/data` gesamt | 4.970,6 MiB | kleines internes Dateisystem |
|
||||
| `/data` belegt | zuletzt 3.831.944 KiB = 3.742,1 MiB (78 %) | real belegt; laufende TV-Daten schwanken |
|
||||
| `/data` verfügbar | zuletzt 1.136.840 KiB = 1.110,2 MiB | Android `NORMAL` |
|
||||
| Abstand zur Philips-Schwelle | zuletzt 88.264 KiB = 86,2 MiB | sehr geringe Schwankungsreserve |
|
||||
| adoptiert, privat | 112,3 GiB, nur 1,36 MiB belegt | technisch intakt, praktisch leer |
|
||||
| öffentliches USB-Volume | 58,2 GiB, 42,2 GiB frei | unkritisch |
|
||||
|
||||
Inodes sind nicht knapp: `/data` nutzt 7 % seiner Inodes. Der Android-
|
||||
`DeviceStorageMonitor` meldet `NORMAL`; dessen Low-Schwelle liegt bei rund
|
||||
249 MiB. Die Warnung entsteht weiterhin an Philips' separater 1-GiB-Schwelle.
|
||||
|
||||
## Konkrete Belegungsverursacher
|
||||
|
||||
| Struktur | Messwert | Anteil an aktueller `/data`-Belegung | Evidenz |
|
||||
|---|---:|---:|---|
|
||||
| `org.droidtv.channels/.../EpgDatabase.db` | 1.661,352 MiB = 1,622 GiB | 44,5 % | `dumpsys dbinfo`/`meminfo` |
|
||||
| `org.droidtv.gel/.../androidx.work.workdb` | 218,8 MiB | 5,9 % | `dumpsys dbinfo` |
|
||||
| alle weiteren von `dbinfo` gelisteten DB-Dateien/Sidecars | 29,1 MiB zusätzlich | 0,8 % | dienstvermittelte Dateiliste, dedupliziert |
|
||||
| 18 aktualisierte System-Apps: sichtbarer Code | 686,2 MiB | 18,4 % | `du` bekannte Codepfade |
|
||||
| deren statbare Basis-ODEX/VDEX | 226,4 MiB | 6,1 % | `stat` exakte OAT-Dateien |
|
||||
| `/data/anr` und kleine zusätzliche Bugreport-Artefakte | 8,7 MiB | 0,2 % | direkt/Bugreport |
|
||||
| **konservativ konkret zugeordnet** | **2.830,5 MiB = 2,764 GiB** | **75,7 %** | synchronisierte Abschlussbilanz |
|
||||
| **verbleibend, nicht einzeln messbar** | **ca. 905–912 MiB = 0,88–0,89 GiB** | **ca. 24,3 %** | zeitabhängige Differenz |
|
||||
|
||||
Der Rest umfasst unter anderem weitere Split-OAT-Dateien, private Nicht-DB-
|
||||
Appdaten, Dalvik-Cache, `/data/system`, `/data/misc` und Dateisystem-Overhead.
|
||||
Wegen DAC und SELinux ist eine vollständige `du`-Aufteilung auf diesem
|
||||
Produktions-Build nicht möglich. Die genannten 75,7 % sind deshalb keine
|
||||
Schätzung nach Kategorien, sondern eine konservative, deduplizierte Summe
|
||||
dienstvermittelter oder direkt statbarer Werte. Der spätere `df`-Stand wich um
|
||||
rund 6 MiB ab; deshalb wird das Ergebnis sinnvoll als 75–76 % angegeben.
|
||||
|
||||
### 1. Philips EPG-Datenbank
|
||||
|
||||
Pfad:
|
||||
|
||||
```text
|
||||
/data/user/0/org.droidtv.channels/databases/EpgDatabase.db
|
||||
```
|
||||
|
||||
Androids Datenbankdienst meldet 1.742.053.376 Byte; das Rollback-Journal ist
|
||||
0 Byte, und es existieren in der Momentaufnahme weder WAL noch SHM. `dumpsys
|
||||
meminfo 7267` belegt `pgsz=4` KiB und `dbsz=1.701.224` KiB. Damit sind
|
||||
`page_size=4096` Byte und `page_count=425.306` exakt bewiesen
|
||||
(`425.306 × 4096 = 1.742.053.376`). Eine physische Blockzählung wurde der
|
||||
Shell verweigert; die fast vollständige Übereinstimmung mit `df` macht eine
|
||||
weitgehend reale Blockbelegung sehr wahrscheinlich, Sparse-Anteile sind aber
|
||||
nicht separat ausgeschlossen.
|
||||
|
||||
Der ContentProvider erlaubt eine weitere, rein lesende Prüfung:
|
||||
|
||||
| Tabelle | aktuelle Zeilen | beobachtete AUTOINCREMENT-IDs |
|
||||
|---|---:|---:|
|
||||
| `indextable` | 358 | 1.267.806–1.268.163 |
|
||||
| `nowtable` | 195 | 4.431.681–4.451.328 |
|
||||
| `nexttable` | 162 | 4.026.589–4.027.685 |
|
||||
| `nownexttable` | 358 | korrespondierende aktuelle IDs |
|
||||
| `fastipnownexttable` | 0 | – |
|
||||
|
||||
Damit sind millionenfache historische Inserts bei wenigen aktuellen
|
||||
Index-/Now-/Next-Zeilen direkt nachgewiesen. Die statische DEX-Analyse
|
||||
korrigiert jedoch einen früheren Schluss: `deleteStaleDataRunnable.run()`
|
||||
berechnet nur Grenzwerte und protokolliert „Deleting stale data“, führt aber
|
||||
**keine Löschung** aus. Zusätzlich werden `1800` und `604800` direkt von
|
||||
`currentTimeMillis()` subtrahiert; die protokollierte 1,8-Sekunden-Differenz
|
||||
bestätigt den Einheitenfehler. Die echten Zeit-Löschmethoden sind in den vier
|
||||
DEX-Dateien nicht an diesen Pfad verdrahtet.
|
||||
|
||||
Die Philips-`rebuilddb`-Pfade sind ebenfalls kein physischer Rebuild:
|
||||
`dropTables()` führt `SQLiteDatabase.delete()` auf Tabellen aus, ersetzt die
|
||||
Datei aber nicht und ruft kein `VACUUM` auf. Eine explizite `PRAGMA
|
||||
auto_vacuum`-Anweisung fehlt im APK. Zugleich baut AOSP seine Plattform-
|
||||
SQLite-Bibliothek langjährig mit `SQLITE_DEFAULT_AUTOVACUUM=1` (FULL). Da
|
||||
dieses Gerät `/system/lib/libsqlite.so` Version 3.32.2 verwendet, ist FULL für
|
||||
eine normal durch `SQLiteOpenHelper` neu angelegte Datenbank eine starke
|
||||
Inferenz, **aber kein gemessener Headerwert dieses Philips-Vendor-Builds**.
|
||||
|
||||
Eine lesende Vollabfrage von `scheduletable` war nach 120 Sekunden noch nicht
|
||||
beendet und wurde abgebrochen. Das beweist keine Zeilenzahl, zeigt aber, dass
|
||||
aktive Schedule-Daten keinesfalls als vernachlässigbar angenommen werden
|
||||
dürfen. Deshalb gilt nun streng:
|
||||
|
||||
- `freelist_count`: **NICHT VERIFIZIERBAR**;
|
||||
- intern freier Platz: **NICHT BERECHENBAR** (`freelist_count × 4096`);
|
||||
- `auto_vacuum`: **NICHT direkt verifiziert**, FULL ist AOSP-basiert
|
||||
plausibel bis sehr wahrscheinlich;
|
||||
- „fast die gesamte Datei ist leer“: **nicht belegt**.
|
||||
|
||||
SQLite dokumentiert sowohl das Freelist-Verhalten als auch, dass `VACUUM` die
|
||||
DB in eine temporäre Datei kopiert und bis zum Zweifachen der Ursprungsgröße an
|
||||
zusätzlichem freien Platz benötigen kann. Bei nur rund 1,1 GiB frei ist das
|
||||
für eine 1,62-GiB-Datei nicht sicher durchführbar und bleibt ausdrücklich
|
||||
verboten. Quellen: [SQLite VACUUM](https://www.sqlite.org/lang_vacuum.html),
|
||||
[PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum)
|
||||
und [AOSP SQLite-Buildoptionen](https://android.googlesource.com/platform/external/sqlite/+/refs/heads/android11-qpr3-s1-release/dist/Android.bp).
|
||||
|
||||
### 2. Philips GEL-WorkManager-Datenbank
|
||||
|
||||
Pfad:
|
||||
|
||||
```text
|
||||
/data/user/0/org.droidtv.gel/no_backup/androidx.work.workdb
|
||||
```
|
||||
|
||||
Die Datei hat 229.453.824 Byte. Android meldet `page_size=4096` Byte und je
|
||||
nach Momentaufnahme `page_count=56.019–56.020`. Dazu kommen nur 524.288 Byte
|
||||
WAL und 32.768 Byte SHM; der Ausreißer steckt also in der Hauptdatei und nicht
|
||||
in einem außer Kontrolle geratenen WAL. Übliche WorkManager-Dateien auf
|
||||
demselben Gerät liegen bei rund 98–106 KiB; GEL ist ungefähr **2.155-mal
|
||||
größer**. `dbinfo`
|
||||
zeigt aktuelle Inserts in `WorkSpec`, `Dependency`, `WorkTag` und `WorkName`.
|
||||
|
||||
Die statische APK-Analyse belegt:
|
||||
|
||||
- `GatheringService` baut für jeden Start einen `OneTimeWorkRequest`;
|
||||
- der eindeutige Name lautet `GEL_WORK`;
|
||||
- der optimierte Bytecode verwendet die Konfliktpolitik
|
||||
`APPEND_OR_REPLACE` (unboxed enum value 4);
|
||||
- der Dienst läuft im langlebigen Systemprozess `org.droidtv.candeebug`.
|
||||
|
||||
Android beschreibt, dass `APPEND_OR_REPLACE` neue Arbeit an die Blätter einer
|
||||
vorhandenen Kette anhängt. Das stimmt mit den beobachteten
|
||||
`Dependency`-Insertionen überein. Quelle:
|
||||
[Android ExistingWorkPolicy](https://developer.android.com/reference/androidx/work/ExistingWorkPolicy).
|
||||
|
||||
Bewertung: Eine über Jahre gewachsene WorkManager-Kette oder daraus entstandene
|
||||
freie SQLite-Seiten ist sehr wahrscheinlich. Ohne privilegierten SQL-Lesezugriff
|
||||
sind Tabellenzählung und Freelist auch hier nicht endgültig trennbar.
|
||||
|
||||
### 3. Aktualisierte System-Apps
|
||||
|
||||
`pm list packages -3` ist leer. Dennoch liegen 18 aktive Codepfade unter
|
||||
`/data/app`; bei allen meldet PackageManager die Flags `SYSTEM` und
|
||||
`UPDATED_SYSTEM_APP` sowie eine ältere, unveränderliche Ursprungskopie unter
|
||||
`/system/product` oder `/vendor`.
|
||||
|
||||
Die sichtbaren APK-/Split-/DM-Anteile summieren sich auf 686,2 MiB. Durch
|
||||
gezieltes `stat` bekannter `oat/arm/base.odex`- und `base.vdex`-Pfade kommen
|
||||
mindestens 226,4 MiB hinzu. Größte Mindestbelegungen:
|
||||
|
||||
| Paket | mindestens |
|
||||
|---|---:|
|
||||
| Google Play Services | 243,0 MiB |
|
||||
| Netflix | 105,4 MiB |
|
||||
| Google Text-to-Speech | 96,6 MiB |
|
||||
| Android WebView | 93,3 MiB |
|
||||
| Play Store | 63,0 MiB |
|
||||
| YouTube | 52,1 MiB |
|
||||
|
||||
Split-Kompilate und private Daten fehlen in diesen Mindestwerten. Die Updates
|
||||
belegen zusätzlich Platz, während ihre Werksversionen im schreibgeschützten
|
||||
Systemimage unverändert vorhanden bleiben.
|
||||
|
||||
## Warum der adoptierte Speicher leer bleibt
|
||||
|
||||
Die Konfiguration ist objektiv korrekt:
|
||||
|
||||
- privates ext4-Volume `0ca93141-...` ist `MOUNTED`;
|
||||
- das zugehörige emulierte Volume trägt `PRIMARY|VISIBLE`;
|
||||
- `sm get-primary-storage-uuid` liefert dessen UUID;
|
||||
- `/storage/emulated/0` zeigt bereits auf dessen `media`-Bereich;
|
||||
- Installationsmodus ist `0[auto]`.
|
||||
|
||||
Androids AOSP-Dokumentation erklärt, dass adoptierter Speicher Apps und private
|
||||
Daten nur für unterstützte Apps aufnehmen kann und die Migration primär den
|
||||
gemeinsamen `/sdcard`-Inhalt betrifft. Androids PackageManager lehnt das
|
||||
Verschieben von Systempaketen ausdrücklich mit `MOVE_FAILED_SYSTEM_PACKAGE` ab.
|
||||
Quellen:
|
||||
[AOSP Adoptable Storage](https://source.android.com/docs/core/storage/adoptable) und
|
||||
[AOSP Android-12 PackageManager-Logik](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android12-gsi/core/java/android/app/ApplicationPackageManager.java).
|
||||
|
||||
Genau diese Einschränkung trifft hier zu: Es gibt keine Drittanbieterpakete,
|
||||
alle 18 aktualisierten Pakete sind weiterhin System-Apps, und auch
|
||||
`org.droidtv.channels` sowie `org.droidtv.gel` sind Systempakete mit UID 1000.
|
||||
Das adoptierte Volume dient daher aktuell fast nur als primärer gemeinsamer
|
||||
Medienspeicher; dort liegen lediglich etwa 1,2 MiB.
|
||||
|
||||
## Separate Philips-Partition `user_setting`
|
||||
|
||||
Eine beim Aufwachen sichtbare TvProvider-Zeile meldete 762.183.680 Byte
|
||||
verfügbar. Das ist **kein** neuer `/data`-Messwert. Die Mounttabelle belegt:
|
||||
|
||||
```text
|
||||
/dev/block/mmcblk0p52 on /data/vendor/user_setting type ext4
|
||||
```
|
||||
|
||||
Die rekonstruierte Kernel-Partitionsdefinition weist `mmcblk0p52` 1000 MiB
|
||||
zu. Statische Analyse von `TvProvider.apk` zeigt, dass
|
||||
`checkDBSizeOverThreshold()` für exakt `/data/vendor/user_setting` ein
|
||||
`StatFs` erzeugt und `blockSize * availableBlocks` protokolliert. Der Provider
|
||||
setzt seine interne Stufe 2 unter 10 % frei und Stufe 1 unter
|
||||
`totalBytes / 7,69`, also rund 13,0039 % frei.
|
||||
|
||||
762.183.680 Byte entsprechen 726,875 MiB und rund 72,6875 % der rohen
|
||||
1000-MiB-Partition. Weil die StatFs-Gesamtgröße den ext4-Overhead bereits
|
||||
abzieht, liegt der wirkliche freie Prozentsatz des Dateisystems eher noch
|
||||
etwas höher. Im vorherigen Bugreport blieb der Wert über mindestens 90 Minuten
|
||||
nahezu konstant; `USER_SETTING_10_FREE`- oder Insert-Blockiermeldungen wurden
|
||||
nicht gefunden. Die Partition enthält unter anderem `rw/tv.db` und `list.db`,
|
||||
ist derzeit aber klar nicht knapp.
|
||||
|
||||
Direktes `df`, Listing und das Lesen der p52-Sysfs-Größenknoten wurden der
|
||||
ADB-Shell verweigert. Diese Grenze wurde akzeptiert. Der Befund beruht daher
|
||||
auf der direkt sichtbaren Mountzuordnung, der Commandline-Größe, dem laufenden
|
||||
UID-1000-StatFs-Log und der lokalen DEX-Analyse.
|
||||
|
||||
## Schlussfolgerung zur Warnung
|
||||
|
||||
Die Meldung ist kein bloßer falscher Zähler, aber auch kein Android-Low-Storage-
|
||||
Alarm. `/data` hat real nur rund 1,08–1,11 GiB verfügbar und schwankt knapp um Philips'
|
||||
eigene 1-GiB-Warnschwelle. Die beiden ungewöhnlich großen Philips-Datenbanken
|
||||
belegen zusammen rund 1,84 GiB und sind die wichtigsten konkreten Ursachen für
|
||||
diesen knappen Zustand. Die EPG-Datenbank allein ist größer als der gesamte
|
||||
aktuell freie Platz. Ihre genaue reclaimbare Teilmenge ist noch unbekannt;
|
||||
Ursache und Rückgewinn dürfen nicht gleichgesetzt werden.
|
||||
|
||||
Der eMMC-Wert `0x0a 0x0a` verändert nicht die Belegung, sorgt aber dafür, dass
|
||||
Philips eine Unterschreitung der 1-GiB-Schwelle als `MEMORY_CRITICAL` darstellt.
|
||||
Damit sind Belegungsursache und Warnschärfe getrennt erklärt.
|
||||
|
||||
## Rohdaten
|
||||
|
||||
- `raw/storage/filesystem-overview.txt`
|
||||
- `raw/storage/data-access-map.txt`
|
||||
- `raw/storage/updated-system-apps.tsv`
|
||||
- `raw/storage/adopted-storage.txt`
|
||||
- `raw/storage/database-outliers.txt`
|
||||
- `raw/storage/epg-content-evidence.txt`
|
||||
- `raw/storage/final-validation.txt`
|
||||
- `raw/storage/channels.apk`
|
||||
- `raw/storage/gel.apk`
|
||||
207
diagnostics/storage/phase3-storage-repair.md
Normal file
207
diagnostics/storage/phase3-storage-repair.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Phase-3-Speicherreparatur
|
||||
|
||||
Stand: 2026-08-25. Jede Änderung wird vor Ausführung ergänzt.
|
||||
|
||||
## Ausgangslage
|
||||
|
||||
- `/data`: 5.089.920 KiB gesamt, 3.832.668 KiB benutzt,
|
||||
1.136.116 KiB frei, 78 %.
|
||||
- GEL `androidx.work.workdb`: 229.453.824 Byte plus 524.288-Byte-WAL und
|
||||
32.768-Byte-SHM.
|
||||
- Die DB liegt unter
|
||||
`/data/user/0/org.droidtv.gel/no_backup/`; Androids regulärer App-Backup-
|
||||
Mechanismus schließt dieses Verzeichnis absichtlich aus.
|
||||
- `org.droidtv.gel` hat `ALLOW_BACKUP` und `ALLOW_CLEAR_USER_DATA`.
|
||||
- Die normale Actions-DB ist nur 20.480 Byte groß. Der Actions-Satz ist nach
|
||||
bestätigter Philips-Leerkonfiguration leer; alle Phase-3-Testdateien sind
|
||||
entfernt.
|
||||
- Live belegt sind mehrminütige Unique-Work-Graphtraversierungen, dauerhafte
|
||||
lineare Aufarbeitung alter BGM-Status-WorkSpecs und massive Verzögerung neuer
|
||||
GEL-Intents. Die WorkDB ist daher nicht nur groß, sondern funktional
|
||||
pathologisch.
|
||||
|
||||
## Reparaturstufe 1 – normale GEL-Daten sichern, vor Ausführung
|
||||
|
||||
**Hypothese:** Ein Android-Full-Backup von `org.droidtv.gel` sichert die
|
||||
wiederherstellbaren normalen App-Daten (Preferences, `files/`, Actions-DB),
|
||||
während der explizit nicht backupfähige WorkManager-Schedulerzustand
|
||||
ausgeschlossen bleibt. Nach erfolgreicher Sicherung kann genau dieser
|
||||
ephemere Zustand neu aufgebaut werden.
|
||||
|
||||
**Aktionen:**
|
||||
|
||||
1. `am force-stop org.droidtv.gel`, um Service, DB-Writes und die laufende
|
||||
Work-Kette für eine konsistente Sicherung anzuhalten.
|
||||
2. `adb backup -f diagnostics/phase3/gel-normal-data-before-clear.ab -noapk
|
||||
-noobb -noshared org.droidtv.gel`; Bestätigung am Fernseher ohne Passwort.
|
||||
3. Nur ein Archiv mit gültigem `ANDROID BACKUP`-Header **und mindestens einem
|
||||
Tar-Eintrag mit App-Nutzdaten** gilt als Erfolg. Vorher erfolgt kein
|
||||
`pm clear`.
|
||||
|
||||
**Mögliche Nebenwirkungen:** GEL und der gemeinsame candeebug-Prozess werden
|
||||
beendet; Systemereignisse können ihn wieder starten. Während der Backup-
|
||||
Bestätigung erscheint kurz eine System-UI. Die Sicherung schreibt nur auf den
|
||||
PC; die App-Daten werden gelesen, nicht geändert.
|
||||
|
||||
**Rückkehrweg:** Vor einer Bereinigung ist kein Datenrollback nötig; GEL kann
|
||||
durch einen normalen Service-/Eventstart wieder anlaufen. Die Backup-Datei
|
||||
kann später mit `adb restore` (erneute UI-Bestätigung) zurückgespielt werden.
|
||||
|
||||
### Ergebnis des Sicherungsversuchs
|
||||
|
||||
- `am force-stop org.droidtv.gel` wurde ausgeführt. Der gemeinsame Prozess
|
||||
`org.droidtv.candeebug` wurde durch eine andere UID-1000-Komponente wieder
|
||||
gestartet, GEL selbst blieb jedoch `stopped=true`; ein GEL-Service lief
|
||||
nicht.
|
||||
- Die Backup-Bestätigung wurde am Fernseher angenommen und der ADB-Befehl
|
||||
ordnungsgemäß beendet.
|
||||
- Das erzeugte Archiv
|
||||
`diagnostics/phase3/gel-normal-data-before-clear.ab` ist nur 47 Byte groß.
|
||||
Es enthält den Header `ANDROID BACKUP`, Kompression `zlib`, danach aber ein
|
||||
leeres Tar-Archiv: **keine einzige GEL-Datei wurde gesichert**.
|
||||
- Manifest und PackageManager bestätigen zwar `allowBackup=true`, aber GEL
|
||||
läuft als Shared-/Core-UID `android.uid.system` / UID 1000, besitzt keinen
|
||||
deklarierten `backupAgent` und erscheint nicht unter den Teilnehmern von
|
||||
`dumpsys backup`. Das ist die belastbare Laufzeiterklärung für das leere
|
||||
Archiv; die genaue interne Eligibility-Regel ist daraus nur als sehr
|
||||
wahrscheinliche Framework-Inferenz abgeleitet.
|
||||
- Unabhängig davon liegt die 229-MiB-WorkDB unter `no_backup/` und wäre auch
|
||||
bei einem funktionierenden normalen App-Backup absichtlich ausgeschlossen.
|
||||
|
||||
Status: **BACKUP GATE NICHT ERFÜLLT. Kein `pm clear` ausgeführt.** Das
|
||||
47-Byte-Archiv bleibt als Beleg erhalten, darf aber nicht als
|
||||
Wiederherstellungspunkt behandelt werden.
|
||||
|
||||
## Reparaturstufe 2 – ausdrücklich freigegeben
|
||||
|
||||
Das ursprüngliche Backup-/Restore-Konzept ist auf diesem Build nicht
|
||||
realisierbar. Ein Package-Daten-Reset würde die pathologische WorkDB sicher
|
||||
entfernen, zugleich aber auch kleine normale GEL-Daten, für die jetzt kein
|
||||
dateibasierter Rückkehrweg existiert. Bestätigt risikomindernd sind: die
|
||||
Actions-Liste ist leer, die Philips-Konfiguration wird beim App-Start wieder
|
||||
vom kanonischen Dienst bezogen, das System-APK bleibt unverändert und Daten
|
||||
anderer Packages werden nicht gelöscht. Unbekannt bleiben einzelne GEL-
|
||||
Preferences bzw. sonstige interne Dateien.
|
||||
|
||||
Der Eigentümer hat am 2026-08-25 den Verlust unbekannter kleiner GEL-
|
||||
Preferences/private Appdaten ausdrücklich akzeptiert und exakt
|
||||
`pm clear --user 0 org.droidtv.gel` freigegeben.
|
||||
|
||||
### Unmittelbarer BEFORE-Snapshot
|
||||
|
||||
- `/data`: 5.089.920 KiB gesamt, 3.836.812 KiB benutzt,
|
||||
**1.131.972 KiB frei**, 78 %.
|
||||
- GEL: installiertes System-APK Version 7.2.0.56, UID/sharedUser 1000 /
|
||||
`android.uid.system`, `stopped=true`, Datenpfad
|
||||
`/data/user/0/org.droidtv.gel`.
|
||||
- Kein laufender GEL-Service. Der gemeinsame Prozess
|
||||
`org.droidtv.candeebug`, PID 23775, läuft als UID system in
|
||||
`u:r:system_app:s0`; er verwendet seine eigene Package-Datenbank, nicht die
|
||||
gestoppte GEL-WorkDB.
|
||||
- Letzter exakt gemessener WorkDB-Zustand vor dem Stop:
|
||||
229.453.824 Byte Hauptdatei + 524.288 Byte WAL + 32.768 Byte SHM.
|
||||
- JobScheduler-Historie bestätigt für GEL 62 Pending-/60 Active-Ereignisse
|
||||
und fortlaufende erfolgreiche Jobs mit Nummern bis mindestens 257839;
|
||||
aktuell ist nach dem Force-Stop keine GEL-Queue aktiv.
|
||||
- WorkSpec-/Dependency-Zeilenzahlen bleiben **UNKNOWN**; die Datenbank ist für
|
||||
ADB shell weder direkt lesbar noch durch einen offenen GEL-Prozess abfragbar.
|
||||
|
||||
### Vorab dokumentierte Aktion
|
||||
|
||||
**Hypothese:** Der Package-Reset entfernt den 229-MiB-WorkManager-Zustand und
|
||||
die historische serielle Queue. Die System-App rekonstruiert anschließend
|
||||
einen kleinen sauberen Zustand aus der kanonischen Philips-Konfiguration.
|
||||
|
||||
**Aktion:** ausschließlich
|
||||
`pm clear --user 0 org.droidtv.gel`.
|
||||
|
||||
**Erwartung:** `Success`, rund 219 MiB mehr freier `/data`-Platz, kein Eingriff
|
||||
in APK/Firmware/andere Packages.
|
||||
|
||||
**Nebenwirkung:** alle privaten User-0-Daten von GEL werden gelöscht; ein
|
||||
dateibasierter Restore ist wegen des nachweislich leeren ADB-Backups nicht
|
||||
möglich.
|
||||
|
||||
**Rückkehrweg:** GEL-System-APK bleibt vorhanden; normale Neuinitialisierung
|
||||
und erneuter Abruf der Philips-Konfiguration. Kein Restore des bewusst
|
||||
verworfenen Schedulergraphs.
|
||||
|
||||
### Unmittelbares Ergebnis
|
||||
|
||||
- `pm clear --user 0 org.droidtv.gel` antwortete exakt **`Success`**.
|
||||
- `/data` unmittelbar danach: 3.611.820 KiB benutzt,
|
||||
**1.356.964 KiB frei**, 73 %.
|
||||
- Speichergewinn: **224.992 KiB = 219,72 MiB = 0,2146 GiB**.
|
||||
- Die zuletzt gemessenen WorkManager-Dateien erklären 224.620 KiB davon.
|
||||
Die Differenz von nur 372 KiB ist eine Obergrenze aus sonstigen GEL-Daten
|
||||
plus Dateisystem-/Messschwankungen. Der Reset traf damit praktisch exakt den
|
||||
identifizierten WorkManager-Verbraucher.
|
||||
- GEL blieb zunächst `stopped=true`; kein GEL-Service und kein GEL-Prozess.
|
||||
`org.droidtv.candeebug` wurde für seine eigene CollectionService-Nutzung als
|
||||
neuer PID 26108 wieder gestartet.
|
||||
- JobScheduler protokollierte `Can't cancel all jobs for system uid`. Das ist
|
||||
kein Clear-Fehler, sondern eine Schutzschranke gegen das pauschale Löschen
|
||||
sämtlicher Jobs der Shared-UID 1000. Der Package-Clear selbst war erfolgreich,
|
||||
es gab keine aktuelle GEL-Pending-/Active-Queue; die angezeigten 62/60-Werte
|
||||
sind historische Statistik.
|
||||
- Nach 30 Sekunden erfolgte kein automatischer GEL-Lifecycle-Start. Deshalb
|
||||
wird einmalig der bereits verstandene normale Produktpfad
|
||||
`DAC_START_GEL` an `GatheringService` verwendet.
|
||||
|
||||
### Vorab: normale GEL-Neuinitialisierung
|
||||
|
||||
**Aktion:** Expliziter Start von
|
||||
`org.droidtv.gel/.gathering.GatheringService` mit
|
||||
`org.droidtv.tv.intent.action.DAC_START_GEL`.
|
||||
|
||||
**Erwartung:** neuer kleiner WorkManager-Zustand, GELWorker-Ausführung,
|
||||
kanonischer Konfigurationsabruf und stabiler Service.
|
||||
|
||||
**Nebenwirkung:** normale neue GEL-Appdaten/WorkDB und ein legitimer
|
||||
GEL_WORK-Eintrag.
|
||||
|
||||
**Rückkehrweg:** Bei Crashloop `am force-stop org.droidtv.gel`; keine weitere
|
||||
Datenlöschung.
|
||||
|
||||
### Ergebnis der Neuinitialisierung
|
||||
|
||||
- Service-Start von ADB shell wurde akzeptiert; GEL läuft im gemeinsamen
|
||||
Prozess PID 26108, UID 1000, `u:r:system_app:s0`.
|
||||
- `GELWorker` für `DAC_START_GEL` startete sofort und endete mit `SUCCESS`.
|
||||
- GEL forderte die kanonische Konfiguration an; `ConfigLoader` meldete
|
||||
`No actions found in Config`. Die neue Actions-DB ist 20.480 Byte groß und
|
||||
`SELECT uuid FROM Actions` blieb leer.
|
||||
- Neue WorkDB unmittelbar danach: 4.096 Byte; WAL 206.032 Byte; SHM
|
||||
32.768 Byte, zusammen **242.896 Byte = 237,20 KiB**.
|
||||
- Gegenüber 229.453.824 + 524.288 + 32.768 Byte vorher ist der private
|
||||
WorkManager-Dateisatz um rund **219,49 MiB kleiner**.
|
||||
- Keine Connection-Waiter, keine SQLite-Exception und keine Crashloop. Ein
|
||||
legitimer GEL-Job/Alarm wurde neu angelegt; die historische serielle Kette
|
||||
ist nicht zurückgekehrt.
|
||||
|
||||
Bewertung: **GEL-WorkManager-Pathologie BEREINIGT / VALIDATED LIVE.**
|
||||
|
||||
### Mehrminütige Stabilitätsbeobachtung
|
||||
|
||||
- WorkDB-Hauptdatei nach mehreren Alarmzyklen: 102.400 Byte; SHM 32.768 Byte;
|
||||
WAL 461.472 Byte. Kein annähernd lineares Wachstum in Richtung des alten
|
||||
219-MiB-Zustands.
|
||||
- GATHERING_ALARM_INTENT lief im 60-Sekunden-Takt jeweils sofort und mit
|
||||
Worker result SUCCESS; kein serieller Rückstau.
|
||||
- Keine GEL-Crashloop, ANR, SQLiteException, SQLiteDiskIOException oder
|
||||
malformed-Meldung im Beobachtungsfenster.
|
||||
- /data blieb bei 73 % und rund 1.356.336 KiB frei.
|
||||
- ADB erreichbar; WLAN-Netz 100 CONNECTED/VALIDATED. Channels-, EPGData-,
|
||||
PlayTV-, MediaTek-TVInput- und GEL/candeebug-Prozesse liefen.
|
||||
|
||||
Zur quantitativen Bestätigung des kleinen Work-Satzes wird der bereits
|
||||
verstandene AndroidX DiagnosticsReceiver genau einmal aufgerufen. Er erzeugt
|
||||
selbst einen kleinen, prune-fähigen Diagnose-WorkSpec, liest anschließend nur
|
||||
WorkManager-Metadaten und endet regulär mit SUCCESS.
|
||||
|
||||
**Ergebnis:** Der Diagnoseworker listete elf abgeschlossene GEL_WORK-Zeilen,
|
||||
alle SUCCEEDED. Running enthielt nur den Diagnoseworker selbst, der unmittelbar
|
||||
SUCCESS meldete. WorkSpec-Zahl im sichtbaren aktuellen Fenster damit 12
|
||||
einschließlich Diagnose-Work; die exakte gesamte Tabellenzeilenzahl und
|
||||
Dependency-Zahl bleiben ohne sqlite3 **UNKNOWN**. Entscheidend ist, dass keine
|
||||
Pending-/Running-Altkette mehr vorhanden war und die DB klein/stabil blieb.
|
||||
149
diagnostics/system/system-inventory.md
Normal file
149
diagnostics/system/system-inventory.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Phase 1 – System-, Security-, Kernel- und SoC-Inventar
|
||||
|
||||
Stand: 2026-08-25. Ziel: Philips 43PUS7394/12 unter
|
||||
`<TV_IP>:5555`. Alle Geräteabfragen waren lesend; APK-, Fstab-, Init-
|
||||
und Kernel-Config-Pulls schrieben nur Kopien in den Host-Workspace. Es gab
|
||||
keinen Reboot, keine Property-/Settings-Änderung, kein `adb root`, keinen
|
||||
Fastboot-/Recovery-Wechsel und keine Exploit-Ausführung.
|
||||
|
||||
Bewertung: **CONFIRMED** = direkt beobachtet, **LIKELY** = starke Indizkette,
|
||||
**POSSIBLE** = offen/vereinbar, **DISPROVED** = im untersuchten Build direkt
|
||||
widerlegt.
|
||||
|
||||
## Identität und Build
|
||||
|
||||
| Merkmal | Wert | Bewertung |
|
||||
|---|---|---|
|
||||
| Modell | Philips 43PUS7394/12; Android-Modellproperty `TPM191E` | **CONFIRMED** |
|
||||
| ADB-Produkt / Device | `PH9M_EA_5599` | **CONFIRMED** |
|
||||
| Firmware | `TPM191E_R.211.000.103.000` | **CONFIRMED** |
|
||||
| Android / SDK | Android TV 12 / API 31; First API Level 28 | **CONFIRMED** |
|
||||
| Security Patch Level | `2025-11-01` | **CONFIRMED** als Property, nicht als Binärbeweis jedes Vendor-Backports |
|
||||
| Build-ID / Incremental | `STT2.230526.001` / `4215` | **CONFIRMED** |
|
||||
| Buildzeit | 2025-12-09 11:16:50 CST | **CONFIRMED** |
|
||||
| Fingerprint | `Philips/PH9M_EA_5599/PH9M_EA_5599:12/STT2.230526.001/4215:user/release-keys` | **CONFIRMED** |
|
||||
| Buildtyp / Tags | `user` / `release-keys` | **CONFIRMED** |
|
||||
| Debug/Secure | `ro.debuggable=0`, `ro.secure=1`, `ro.adb.secure=1` | **CONFIRMED** |
|
||||
| Priv-App-Permissions | `ro.control_privapp_permissions=enforce` | **CONFIRMED** |
|
||||
| Treble / VNDK | Treble `true`, Framework-VNDK 31, Vendor-VNDK 26 | **CONFIRMED** |
|
||||
|
||||
Seriennummern, Netzwerkadressen und andere eindeutige Gerätekennungen aus der
|
||||
vollständigen Propertyliste werden in diesem Bericht bewusst nicht wiederholt.
|
||||
|
||||
## CPU, SoC und ABI
|
||||
|
||||
| Ebene | Befund | Bewertung |
|
||||
|---|---|---|
|
||||
| CPU | vier ARMv8-Cortex-A53-Kerne (`CPU part 0xd03`) | **CONFIRMED** |
|
||||
| Kernelarchitektur | ARM64/AArch64 (`CONFIG_ARM64=y`, `CONFIG_64BIT=y`) | **CONFIRMED** |
|
||||
| Android-Userspace | ausschließlich 32-Bit ARM: `armeabi-v7a,armeabi`, `zygote32`, keine 64-Bit-ABI | **CONFIRMED** |
|
||||
| Android-/Kernelplattform | `mt5887`; `CONFIG_MACH_MT5887=y`, `CONFIG_SOC_MEDIATEK_MT53XX=y` | **CONFIRMED** |
|
||||
| Android-SoC-Property | Hersteller `Mediatek`, Modell `MT5690` | **CONFIRMED** als Property |
|
||||
| TPV-OEM-Chipproperty | `MT5599` | **CONFIRMED** als OEM-Kennung |
|
||||
| Physische Aliasbeziehung MT5887/MT5690/MT5599 | nicht durch Chip-ID oder Herstellerunterlage bewiesen | **POSSIBLE**, nicht gleichsetzen |
|
||||
|
||||
Die belastbare Formulierung lautet daher: MediaTek-TV-Plattform mit
|
||||
`mt5887`-BSP und den zusätzlichen TPV/Android-Kennungen `MT5599`/`MT5690`.
|
||||
|
||||
## Kernel und Härtung
|
||||
|
||||
```text
|
||||
Linux localhost 4.9.310-ab4215 #1 SMP PREEMPT
|
||||
Tue Dec 9 11:04:39 CST 2025 armv8l
|
||||
Compiler: Linaro GCC 5.5.0 (2017.10)
|
||||
```
|
||||
|
||||
Der 32-Bit-`armv8l`-Maschinenstring kommt vom 32-Bit-Userspace; die gesicherte
|
||||
`/proc/config.gz` beweist einen ARM64-Kernel.
|
||||
|
||||
| Merkmal | Befund | Bewertung |
|
||||
|---|---|---|
|
||||
| SELinux | `Enforcing`; Development-/Disable-Code einkompiliert, aber nicht aktiv | **CONFIRMED** |
|
||||
| User namespaces | nicht einkompiliert | **CONFIRMED** |
|
||||
| Userfaultfd | nicht einkompiliert | **CONFIRMED** |
|
||||
| Kexec, `/dev/mem`, `/dev/kmem` | nicht einkompiliert | **CONFIRMED** |
|
||||
| PAN / SW-TTBR0-PAN | aktiv | **CONFIRMED** |
|
||||
| KASLR / relocatable kernel | `CONFIG_RANDOMIZE_BASE=y`; kein `nokaslr` in der beobachteten Commandline, tatsächliche Entropie nicht separat gemessen | **CONFIRMED** als Buildoption / **LIKELY** zur Laufzeit |
|
||||
| Stack protector / hardened usercopy | stark / aktiv | **CONFIRMED** |
|
||||
| Seccomp / Filter | einkompiliert; ADB-Shell-Prozess meldete aktuell `Seccomp: 0` | **CONFIRMED** |
|
||||
| Perf | `/proc/sys/kernel/perf_event_paranoid=3` | **CONFIRMED** |
|
||||
| Module | Laden/Entladen einkompiliert; `CONFIG_MODULE_SIG` nicht gesetzt | **CONFIRMED**; Shell besitzt dennoch weder `CAP_SYS_MODULE` noch einen nachgewiesenen Ladepfad |
|
||||
| BPF | BPF und BPF-Syscall einkompiliert; Runtimewert für unprivilegiertes BPF durch SELinux verborgen | **CONFIRMED** / offen |
|
||||
| Debugfs | einkompiliert und gemountet; sensible Bereiche durch SELinux begrenzt | **CONFIRMED** |
|
||||
|
||||
Geladene proprietäre Module umfassen unter anderem `dtv_driver`, `mtk_core`,
|
||||
`mali_kbase`, `fusion`, `tpvinput`, Audio-, WLAN- und Videomodule. Das aktive
|
||||
Mali-Kernelmodul enthält `r32p0`-Buildpfade als starkes Basisindiz und den
|
||||
Vermagic-String
|
||||
`4.9.310-ab4215 ... aarch64`; SurfaceFlinger meldet separat einen Mali-G51-
|
||||
Userspace-Stack `r32p1`. Die separate Sicherheits-/CVE-Triage ist aus dieser
|
||||
kuratierten Veröffentlichung ausgeschlossen.
|
||||
|
||||
## Aktueller ADB-/Privilegkontext
|
||||
|
||||
| Merkmal | Befund | Bewertung |
|
||||
|---|---|---|
|
||||
| Transport | `<TV_IP>:5555 device` | **CONFIRMED** |
|
||||
| UID/GID | UID/GID 2000 `shell` | **CONFIRMED** |
|
||||
| Gruppen | unter anderem `shell`, `input`, `log`, `adb`, `inet`, `readproc`, `uhid`; kein `root`/`system` | **CONFIRMED** |
|
||||
| SELinux-Domain | Shellkommando `u:r:shell:s0`; adbd `u:r:adbd:s0` | **CONFIRMED** |
|
||||
| Shell-Capabilities | Effective/Permitted/Inheritable/Ambient jeweils 0; Bounding Set `0xc0` | **CONFIRMED** |
|
||||
| adbd | UID/GID 2000, keine effektiven/permitted Capabilities | **CONFIRMED** |
|
||||
| PID 1 | UID/GID 0 und voller Capabilitysatz; nur Statuslesbarkeit, keine Kontrolle durch Shell | **CONFIRMED** |
|
||||
| `su` | nicht in den geprüften üblichen System-/Vendorpfaden gefunden | **CONFIRMED** als begrenzter Nichtfund |
|
||||
| ADB liefert derzeit einen Root-Kontext | nein | **DISPROVED** |
|
||||
|
||||
Die Init-Definition startet adbd mit der Option
|
||||
`--root_seclabel=u:r:su:s0`, und `service.adb.root=1` würde USB zurücksetzen
|
||||
und adbd neu starten. Das ist noch kein Rootpfad: der Production-Build hat
|
||||
`ro.debuggable=0`, und adbd läuft tatsächlich als Shell ohne Capabilities.
|
||||
`adb root` wurde nicht ausgeführt, weil die lokale Init-Datei einen
|
||||
Verbindungs-/Daemon-Neustart belegt und der Test damit nicht garantiert rein
|
||||
beobachtend ist.
|
||||
|
||||
## Boot- und Verified-Boot-Kurzbild
|
||||
|
||||
| Merkmal | Befund | Bewertung |
|
||||
|---|---|---|
|
||||
| aktiver Slot | `_a` | **CONFIRMED** |
|
||||
| A/B | physische A/B-Partitionen, `ro.build.ab_update=true` | **CONFIRMED** |
|
||||
| System-as-root | `/dev/dm-0` auf `/`, ext4 read-only | **CONFIRMED** |
|
||||
| AVB | AVB 1.2; vbmeta 1.0; Device State `locked`; Verified Boot `green` | **CONFIRMED** |
|
||||
| dm-verity | `enforcing`; System/Vendor sowie mehrere TPV-Images verifiziert | **CONFIRMED** |
|
||||
| Dynamic Partitions | kein `super`, feste physische Partitionen | **DISPROVED** |
|
||||
| separate `recovery`, `cache`, `metadata`, `persist` | keine entsprechenden Partitionsnamen | **DISPROVED** für dieses Layout |
|
||||
| Recovery-Infrastruktur | `uncrypt`, RecoverySystem-Service, BCB/`misc`, UpdateEngine, `factory_a/b` vorhanden | **CONFIRMED**; genaue Recovery-Startart offen |
|
||||
| Fastboot/Fastbootd | keine entsprechenden Binaries im gemounteten laufenden Android-System gefunden und kein Transport sicher getestet | **POSSIBLE / unbekannt** im Bootloader; fastbootd-basierter Dynamic-Partition-Pfad nicht belegt |
|
||||
| öffentliches OEM-Unlock | keine OEM-Lock-Binderinstanz, keine Unlock-Properties, keine offizielle Methode belegt | **POSSIBLE**, aber aktuell nicht nachgewiesen |
|
||||
|
||||
Die vollständige 63-Partitionen-Tabelle, Fstab-Auswertung und Mountmatrix steht
|
||||
in [boot-inventory-agent.md](../boot/boot-inventory-agent.md).
|
||||
|
||||
## Aktueller Speicher- und eMMC-Snapshot
|
||||
|
||||
| Bereich | Befund | Bewertung |
|
||||
|---|---|---|
|
||||
| `/data` | 5.089.920 KiB gesamt, 3.832.452 KiB benutzt, 1.136.332 KiB verfügbar, 78 % | **CONFIRMED** |
|
||||
| Android DeviceStorageMonitor | `NORMAL`; low 260.603.904 Byte | **CONFIRMED** |
|
||||
| adoptierter USB | 117.749.032 KiB gesamt, 116.529.664 KiB verfügbar, 1 % | **CONFIRMED** |
|
||||
| Installationsmodus | `0[auto]`; adopted private Volume gemountet | **CONFIRMED** |
|
||||
| Philips-Lifetime-Property | `0x0a 0x0a` | **CONFIRMED** |
|
||||
| unabhängiger Android-Wert | `storaged_emmc_info: [emmc 5.1,1,10,10]` | **CONFIRMED** |
|
||||
| eMMC-Gesundheit | eMMC 5.1; PRE_EOL `0x01` normal; Lifetime A/B `0x0a/0x0a` = je 90–100-%-Bucket | **CONFIRMED** |
|
||||
|
||||
Der neue Storaged-Wert beweist: Der hohe Lifetime-Bucket ist kein bloßer
|
||||
Philips-Cache, aber der Reserveblockindikator steht noch auf Normal. Das ist
|
||||
kein Beweis eines unmittelbar bevorstehenden Hardwareausfalls. Details:
|
||||
[emmc-analysis.md](../emmc-analysis.md).
|
||||
|
||||
## Phase-1-Grenzen
|
||||
|
||||
- Direkte Reads von EXT_CSD, CID/CSD, `/proc/partitions`, Kernel-Dmesg und
|
||||
mehreren sysfs-/procfs-Sicherheitswerten werden durch DAC/SELinux blockiert.
|
||||
- SUID-/SGID-/world-writable-Scans liefen nur in für die Shell durchsuchbaren
|
||||
Bereichen; viele Executable-Labels verweigern bereits `getattr`. Es wurde
|
||||
kein nutzbarer SUID-/SGID-Pfad gefunden, eine mathematisch vollständige
|
||||
Abwesenheitsbehauptung ist aber nicht zulässig.
|
||||
- Kein echter Exploit und kein schreibender IOCTL-Test wurde durchgeführt.
|
||||
- Das späte Framework-SPL ist ein starkes nominelles Patchindiz, aber kein
|
||||
Binärnachweis des proprietären Kernel-/Mali-/MediaTek-Backportstands.
|
||||
76
diagnostics/vendor-bugs/BUG-001-epg-stale-cleanup.md
Normal file
76
diagnostics/vendor-bugs/BUG-001-epg-stale-cleanup.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# EPG stale-data cleanup logs deletion but performs none
|
||||
|
||||
## Klassifikation
|
||||
|
||||
**CONFIRMED BUG – High.** Betroffen: Philips 43PUS7394/12,
|
||||
`TPM191E_R.211.000.103.000`, Android TV 12/API 31; Komponente
|
||||
`/system/priv-app/channels/channels.apk`, Klasse
|
||||
`org.droidtv.channels.epg.bcepg.EpgProvider`, Methode/Runnable
|
||||
`EpgProvider$deleteStaleDataRunnable.run()`.
|
||||
|
||||
## Benutzerwirkung
|
||||
|
||||
Die EPG-DB wuchs auf 1.742.053.376 Byte und verdrängte auf der nur rund
|
||||
4,85-GiB-großen `/data`-Partition andere Appdaten. Philips warnte deshalb vor
|
||||
fast vollem Speicher. Der kontrollierte Neuaufbau gewann 1.701.400 KiB auf
|
||||
`/data`; die Belegung sank von 73 % auf 39 %. Nach realem Betrieb war die DB
|
||||
~806.912 Byte groß und enthielt 1.041 aktuelle Schedule-Einträge. Guide,
|
||||
Now/Next, Sender, Favoriten, Aufnahmen, Timer und Reminder funktionierten; die
|
||||
Speicherwarnung war verschwunden.
|
||||
|
||||
## Erwartetes und tatsächliches Verhalten
|
||||
|
||||
Erwartet: der nach Providerstart angekündigte Cleanup entfernt Schedule-Daten
|
||||
außerhalb eines definierten Retentionsfensters. Tatsächlich berechnet das
|
||||
Runnable lediglich `now-1800` und optional einen weiteren Grenzwert, schreibt
|
||||
Logs und kehrt ohne `delete`, `execSQL`, `ContentResolver.delete`, `applyBatch`
|
||||
oder Aufruf der vorhandenen Zeit-Löschmethoden zurück.
|
||||
|
||||
## Reproduktion
|
||||
|
||||
1. Provider starten und zwei Minuten warten.
|
||||
2. Logs `Posting stale data deletion...`, `Triggering stale data cleanup` und
|
||||
`Deleting stale data for time ...` beobachten.
|
||||
3. DB-Zeilenzahl und Größe davor/danach vergleichen.
|
||||
|
||||
Live am 25.08.2026 wiederholt: Logs um 21:09/21:11; keine DB-Änderung.
|
||||
|
||||
## Technischer Beweis
|
||||
|
||||
- Snapshot: 3.071.250 `scheduletable`-Zeilen, **100 % abgelaufen**, 97,81 %
|
||||
älter als 30 Tage, 79,03 % älter als ein Jahr.
|
||||
- Tabelle ca. 1.436,98 MiB, vier Indizes ca. 222,06 MiB; `freelist_count=0`,
|
||||
`auto_vacuum=FULL`, `integrity_check=ok`.
|
||||
- Bytecode: [epg-analysis.md](../epg-analysis.md) und
|
||||
[epg-reverse-agent.md](../deep-forensics/philips/epg-reverse-agent.md).
|
||||
- Aggregat-/Reparaturbeleg: [epg-repair.md](../phase5/epg-repair.md).
|
||||
|
||||
## Root Cause
|
||||
|
||||
Das Runnable ist unvollständig verdrahtet. Funktionsfähige Methoden
|
||||
`EpgProgramPump.deleteChannelProgrambytime(long)` und
|
||||
`deleteScheduleProgramsByTime(long)` existieren, werden von ihm aber nicht
|
||||
aufgerufen. Zusätzlich fehlt eine robuste periodische Retention. Phase 6 hat
|
||||
die exakte Methode in `classes4.dex` bei `code_off=0xe298c` bestätigt.
|
||||
|
||||
## Workaround und dauerhafter Fix
|
||||
|
||||
Workaround: kontrollierter kompakter DB-Neuaufbau mit Erhalt von Index/Mapping.
|
||||
Dauerhaft: Clock in Millisekunden lesen, einmal in die tatsächlich gespeicherten
|
||||
Epoch-Sekunden konvertieren und `scheduletable` indexgestützt in höchstens
|
||||
2.000-Zeilen-Transaktionen mit sieben Tagen Retention bereinigen. Der Offline-
|
||||
Test auf einer Kopie des großen Snapshots löschte 3.063.863 Zeilen, erhielt
|
||||
7.387, bestand `integrity_check` und schrumpfte die FULL-auto-vacuum DB auf
|
||||
4.059.136 Byte; der zweite Lauf änderte nichts. Details:
|
||||
[IMPLEMENTATION.md](../../patches/epg-stale-cleanup/IMPLEMENTATION.md) und
|
||||
[TEST-RESULTS.md](../../patches/epg-stale-cleanup/TEST-RESULTS.md).
|
||||
|
||||
## Regression Tests / Kompatibilität
|
||||
|
||||
0/7/30-Tage-Daten, Lock, Stromausfall, Doppellauf, Senderwechsel und EPG-Refresh
|
||||
testen. Sender-, Favoriten- und Recording-Provider dürfen nicht mutiert werden.
|
||||
|
||||
## Evidenz-Hashes
|
||||
|
||||
`channels.apk` SHA-256:
|
||||
`3A44BC648624F0CC9CD9FEB4CFC3FD33F41323597718E4FCCFC25FE35504D34F`.
|
||||
53
diagnostics/vendor-bugs/BUG-002-gel-workmanager-growth.md
Normal file
53
diagnostics/vendor-bugs/BUG-002-gel-workmanager-growth.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# GEL WorkManager database grows into a long serial history
|
||||
|
||||
## Klassifikation
|
||||
|
||||
**CONFIRMED BUG / CONFIRMED enqueue-policy root cause – Medium.** Gerät und
|
||||
Firmware wie BUG-001. Komponente `/system/app/gel/gel.apk`, AndroidX
|
||||
WorkManager, `GELWorker`, Unique-Name `GEL_WORK`.
|
||||
|
||||
## Benutzerwirkung
|
||||
|
||||
`androidx.work.workdb` erreichte 229.453.824 Byte plus 524.288-Byte-WAL und
|
||||
32.768-Byte-SHM. Der feste Unique-Work-Graph benötigte Minuten, um seine
|
||||
Abhängigkeiten zu traversieren; neue GEL-Intents wurden merklich verzögert.
|
||||
|
||||
## Erwartetes und tatsächliches Verhalten
|
||||
|
||||
Abgeschlossene Telemetriearbeiten sollten begrenzt und prune-fähig bleiben.
|
||||
Tatsächlich bestand eine jahrelange serielle `GEL_WORK`-Kette. Nach dem
|
||||
autorisierten Paketdatenreset begann die DB bei 4.096 Byte; später blieb sie bei
|
||||
ca. 102.400 Byte (WAL ca. 461 KiB). Der offizielle DiagnosticsWorker zeigte nur
|
||||
elf sichtbare, erfolgreiche GEL-Arbeiten und keinen Rückstau.
|
||||
|
||||
## Reproduktion / Beweis
|
||||
|
||||
`dumpsys dbinfo org.droidtv.gel -v`, WorkManager-Diagnosebroadcast und GEL-Logs
|
||||
verwenden; keine privaten Payloads ausgeben. Belege:
|
||||
[phase3-storage-repair.md](../storage/phase3-storage-repair.md); der interne
|
||||
Live-Validierungsbericht ist aus der kuratierten Veröffentlichung ausgeschlossen.
|
||||
|
||||
## Root Cause
|
||||
|
||||
**CONFIRMED:** `GatheringService.onStartCommand` in `classes.dex`,
|
||||
`code_off=0x50bf0`, baut aus jedem Intent einen OneTimeWorkRequest, lädt
|
||||
`GEL_WORK`, Policy-Integer `4` und ruft `WorkContinuationImpl.enqueue()` auf.
|
||||
Der eingebettete AndroidX-Code bestätigt Wert 4 als `APPEND_OR_REPLACE`: neue
|
||||
Arbeit hängt an den aktuellen Blättern. Das erzeugt die jahrelange serielle
|
||||
Abhängigkeitsgeschichte und verteuert die Blatttraversierung.
|
||||
|
||||
## Workaround und Fix
|
||||
|
||||
Workaround: gezielter GEL-Datenreset nach Backup/Risikoabwägung. Ein bloßes
|
||||
Ändern von Policy 4 auf KEEP ist nicht sicher, weil `GELWorker` unterschiedliche
|
||||
Intents verarbeitet und dabei Ereignisse verloren gingen. Fix: persistente,
|
||||
begrenzte FIFO/Outbox, Koaleszierung nur bewiesener State-Alarme, ein KEEP-
|
||||
Drain-Worker und KEEP-Watchdog, Ack erst nach Dispatch. Siehe
|
||||
[ROOT-CAUSE.md](../../patches/gel-workmanager/ROOT-CAUSE.md).
|
||||
|
||||
## Regression Tests / Hash
|
||||
|
||||
Das Phase-6-Modell bestand 10.000 koaleszierbare Alarme und 10.000 FIFO-
|
||||
Ereignisse bei O(1) aktivem WorkSpec, begrenzter Queue und Crash-Retry.
|
||||
`gel.apk` SHA-256:
|
||||
`29A68E6B09700ED4CCDF312C0B99EEE0C3F67E329A537FE87ED4341A71D383D7`.
|
||||
45
diagnostics/vendor-bugs/BUG-003-usb-free-size-zero.md
Normal file
45
diagnostics/vendor-bugs/BUG-003-usb-free-size-zero.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# UsbVolumeLister reports zero free bytes for mounted USB volumes
|
||||
|
||||
## Klassifikation
|
||||
|
||||
**CONFIRMED BUG – Medium.** Komponente `/system/framework/tvfw.jar`, Klasse
|
||||
`UsbVolumeListerImpl`, Methode `getVolumes(String)`.
|
||||
|
||||
## Benutzerwirkung
|
||||
|
||||
Philips-Komponenten erhalten für ein öffentliches USB-Volume und das private
|
||||
adopted Volume `mFreeSize=0`, obwohl `df` 44,2 bzw. 114,8 Millionen KiB frei
|
||||
meldet. Dadurch sind falsche Speicheranzeigen und fehlerhafte Entscheidungen
|
||||
möglich.
|
||||
|
||||
## Erwartetes / tatsächliches Verhalten
|
||||
|
||||
Für jedes `MOUNTED`-Volume muss die nutzbare Kapazität seines realen Pfades
|
||||
geliefert werden. Der Code setzt `mFreeDiskSize` jedoch nur, wenn
|
||||
`SystemProperties.get("sys.mtk.dmremoteservice.ready")` exakt `"1"` ist.
|
||||
Live ist diese Property leer; vorhanden ist
|
||||
`vendor.mtk.dmremoteservice.ready=0`. Der Java-Initialwert 0 wird ungeprüft als
|
||||
echter Messwert weitergegeben.
|
||||
|
||||
## Reproduktion und Beweis
|
||||
|
||||
Ein public- und ein adopted-USB-Volume mounten, Philips-UsbVolumeLister-Log mit
|
||||
`df`/`StatFs` desselben Pfades vergleichen. Details und Bytecodekette:
|
||||
[usb-volume-uid1000-analysis.md](../phase4/usb-volume-uid1000-analysis.md).
|
||||
|
||||
## Root Cause / Fix
|
||||
|
||||
Eine optionale Remote-Service-Bereitschaft wird fälschlich zum einzigen
|
||||
Messpfad. Exakt lokalisiert: `classes.dex`, `getVolumes(String)`,
|
||||
`code_off=0x17b5e0`, Property-Branch PC `0x0133`. Der MediaTek-Produzent liefert
|
||||
KiB; `UsbVolume` publiziert nach weiterer Division MiB. Robust ist:
|
||||
DeviceManager-Wert nur verwenden, wenn ready und plausibel; sonst
|
||||
`StatFs(volumeInfo.path)` in MiB. Ein unbekannter Wert darf nicht als echte Null
|
||||
erscheinen. Details: [IMPLEMENTATION.md](../../patches/usb-freesize/IMPLEMENTATION.md).
|
||||
|
||||
## Regression Tests / Hash
|
||||
|
||||
Der Offline-Test bestand ready/unready, plausible/unplausible DM-Werte,
|
||||
StatFs-Fallback, >2 TiB, echte Null und unknown. Live folgen public/adopted,
|
||||
Mount/Unmount/Reinsert und Permission-Fälle. `tvfw.jar` SHA-256:
|
||||
`a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad`.
|
||||
11
diagnostics/vendor-bugs/BUG-MATRIX.md
Normal file
11
diagnostics/vendor-bugs/BUG-MATRIX.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Philips / TP Vision issue matrix
|
||||
|
||||
| ID | Problem | Status | Component | Root Cause | User Impact | Workaround | Permanent Fix | Complexity | Regression | Report ready? |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| BUG-001 | EPG stale cleanup | CONFIRMED + live repair validated | channels.apk | Runnable mutiert nicht | High: 1,62 GiB Leak | kompakter DB-Neuaufbau | 7-Tage/2.000er Retention | Medium | offline full snapshot PASS | **yes** |
|
||||
| BUG-002 | GEL WorkDB growth | CONFIRMED exact callsite | gel.apk/WorkManager | `APPEND_OR_REPLACE` unter `GEL_WORK` | Medium: 219 MiB + Latenz | gezielter GEL reset | bounded FIFO/outbox + KEEP drain | Medium | 10k+10k model PASS | **yes** |
|
||||
| BUG-003 | USB free size zero | CONFIRMED exact method | tvfw.jar | optionales Property-Gate ohne Fallback | Medium: falsche Kapazität | `df` statt Philips-Anzeige | validated DM + StatFs fallback | Low–Medium | offline matrix PASS | **yes** |
|
||||
| DESIGN-001 | 1-GiB storage warning + lifetime | DESIGN ISSUE, code path confirmed | TPTvService.apk | Kapazität und Wear werden vermischt | Medium: alarmierende Meldung | Leaks beseitigen | Android thresholds, hysteresis, separate health | Medium | policy model PASS | **yes as design report** |
|
||||
|
||||
Separate confidential security finding available. It is intentionally excluded
|
||||
from this public issue matrix and vendor functional-bug package.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Philips storage-warning policy diverges from Android
|
||||
|
||||
## Klassifikation
|
||||
|
||||
**DESIGN ISSUE / QUESTIONABLE POLICY – Medium user impact, not classified as a
|
||||
confirmed implementation bug.** Komponente
|
||||
`org.droidtv.tv.tptvservice.tvstoragemonitor.TVStorageMonitorService` in
|
||||
`TPTvService.apk`.
|
||||
|
||||
## Verhalten und Beweis
|
||||
|
||||
Android `DeviceStorageMonitor` war `NORMAL`; seine `/data`-Low-Grenze beträgt
|
||||
260.603.904 Byte. Philips verwendet zusätzlich etwa 1 GiB frei und koppelt die
|
||||
Warnstufe an `persist.odm.tpv.sys.emmc_lifetime=0x0a 0x0a`. Der zweite Wert wird
|
||||
als 90–100 % der geschätzten Lebensdauer verbraucht klassifiziert. Bei nur rund
|
||||
4,85 GiB `/data` ist 1 GiB eine hohe absolute Schwelle. Adopted Storage mit
|
||||
über 100 GiB frei beseitigt die interne `/data`-Knappheit nicht.
|
||||
|
||||
Nach der EPG-Reparatur meldete Android weiterhin `NORMAL`; `/data` war nur noch
|
||||
39 % belegt. Die visuelle Prüfung bestätigte, dass die Speicherwarnung
|
||||
verschwunden ist.
|
||||
|
||||
Phase 6 lokalisierte die exakte Runnable-Methode bei `code_off=0x2ad380` und die
|
||||
Konstanten 524.288.000/1.073.741.824 Byte. Der Code koppelt Kapazität und
|
||||
eMMC-Lifetime tatsächlich an Cleanup/UI. Außerdem überschreibt er eine
|
||||
300-Sekunden-Zuweisung unmittelbar mit 30 Sekunden.
|
||||
|
||||
## Empfehlung
|
||||
|
||||
Androids `StorageManager`-Schwellen als Basis, zusätzliche Prozent-/Reserve-
|
||||
Grenze mit Hysterese, reclaimable Bytes und Ursache anzeigen. eMMC-Lifetime als
|
||||
separate Health-Warnung behandeln, nicht als Multiplikator einer Kapazitäts-
|
||||
Meldung. EPG-/GEL-Leaks komponentennah begrenzen. Adopted freien Speicher nur
|
||||
für tatsächlich dort platzierte Daten anrechnen.
|
||||
Der getestete Zustandsentwurf trennt beide Achsen und ergänzt Debounce,
|
||||
Hysterese, Übergangsbenachrichtigung und Cooldown; siehe
|
||||
[POLICY.md](../../patches/storage-monitor/POLICY.md).
|
||||
|
||||
## Tests / Hash
|
||||
|
||||
Grenzübergänge, Hysterese, Reboot, adopted vorhanden/entfernt, Lifetime-Buckets
|
||||
und Android/Philips-Zustände gegeneinander testen. `TPTvService.apk` SHA-256:
|
||||
`75F492EE89906C8C267D685D2B7D0CE02BB09F3A6B4CC8456F8B096B9E441ECD`.
|
||||
63
diagnostics/vendor-bugs/PHILIPS-PATCH-SUBMISSION.md
Normal file
63
diagnostics/vendor-bugs/PHILIPS-PATCH-SUBMISSION.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Philips/TPVision patch submission – TPM191E
|
||||
|
||||
Target: Philips 43PUS7394/12, `TPM191E_R.211.000.103.000`, Android TV 12/API
|
||||
31. This submission contains functional reliability bugs only. Confidential
|
||||
security material is deliberately excluded.
|
||||
|
||||
## BUG-001 – EPG stale cleanup is a no-op
|
||||
|
||||
| Field | Detail |
|
||||
|---|---|
|
||||
| Affected binary | `/system/priv-app/channels/channels.apk`, SHA-256 `3a44bc648624f0cc9cd9feb4cfc3fd33f41323597718e4fccfc25fe35504d34f` |
|
||||
| Exact target | `classes4.dex`, `org.droidtv.channels.epg.bcepg.EpgProvider$deleteStaleDataRunnable.run()V`, `code_off=0xe298c` |
|
||||
| Root cause | Runnable computes/logs `now-1800` (and optional `-604800`) but performs no delete/applyBatch/execSQL or call to existing time-delete routines |
|
||||
| Proposed fix | normalize milliseconds to epoch seconds once; delete `scheduletable` entries older than 7 days using indexed predicates in transactions of at most 2,000 rows; schedule bounded periodic cleanup |
|
||||
| Patch specification | `patches/epg-stale-cleanup/PATCH.diff`, `IMPLEMENTATION.md`, `SMALI-PATCH.md` |
|
||||
| Full snapshot test | 1,742,053,376-byte DB; 3,063,863 stale rows deleted, 7,387 retained, 1,532 bounded transactions; result 4,059,136 bytes; `integrity_check=ok` |
|
||||
| Idempotency | second run deletes zero rows and leaves identical counts/size |
|
||||
| Real repair evidence | prior repair reclaimed 1,701,400 KiB on `/data`, 73%→39%; subsequent live EPG 59 Now/48 Next/1,041 Schedule and normal Guide/recording/timer/favorite behavior |
|
||||
|
||||
Required regression tests: 0/7/30-day boundaries, seconds/milliseconds inputs,
|
||||
clock change, duplicate run, concurrent EPG ingest/lock retry, abrupt power loss,
|
||||
channel rescan, standby wake, all tuner sources, favorites, recordings, reminders
|
||||
and no mutations outside EPG schedule rows.
|
||||
|
||||
## BUG-002 – GEL WorkManager dependency history is unbounded
|
||||
|
||||
| Field | Detail |
|
||||
|---|---|
|
||||
| Affected binary | `/system/app/gel/gel.apk`, SHA-256 `29a68e6b09700ed4ccdf312c0b99eee0c3f67e329a537fe87ed4341a71d383d7` |
|
||||
| Exact target | `classes.dex`, `GatheringService.onStartCommand`, `code_off=0x50bf0` |
|
||||
| Root cause | unique name `GEL_WORK` uses policy integer 4 = `APPEND_OR_REPLACE`; each event extends a serial dependency history |
|
||||
| Observed effect | WorkManager DB 229,453,824 bytes plus WAL/SHM; dependency traversal delays |
|
||||
| Proposed fix | bounded persistent FIFO/outbox, coalesce only proven state events, one KEEP drain worker plus watchdog, acknowledge after dispatch; do not blindly switch to KEEP and lose intents |
|
||||
| Patch specification | `patches/gel-workmanager/PATCH.diff`, `ROOT-CAUSE.md`, `TEST-PLAN.md` |
|
||||
| Test evidence | 10,000 coalescible and 10,000 FIFO events pass with bounded queue, O(1) active WorkSpec and crash retry |
|
||||
|
||||
Required regression tests: ordering/loss/duplication across crash and reboot,
|
||||
network offline/online, queue cap/backpressure, WorkManager prune/upgrade, all
|
||||
intent classes and telemetry privacy/consent semantics.
|
||||
|
||||
## BUG-003 – USB free capacity incorrectly reported as zero
|
||||
|
||||
| Field | Detail |
|
||||
|---|---|
|
||||
| Affected binary | `/system/framework/tvfw.jar`, SHA-256 `a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad` |
|
||||
| Exact target | `classes.dex`, `UsbVolumeListerImpl.getVolumes(String)`, `code_off=0x17b5e0`, property branch PC `0x0133` |
|
||||
| Root cause | size is assigned only when `sys.mtk.dmremoteservice.ready == "1"`; live property is empty while `vendor.mtk.dmremoteservice.ready=0`, so constructor default zero is returned as a real measurement |
|
||||
| Proposed fix | accept DeviceManager KiB only when service-ready and `total>0`, `0<=free<=total`; otherwise use `StatFs(VolumeInfo.path).getAvailableBytes()/1048576` with `long`; keep inaccessible/racy path UNKNOWN, not zero |
|
||||
| Patch specification | `patches/usb-freesize/PATCH.diff`, `IMPLEMENTATION.md`, `SMALI-PATCH.md` |
|
||||
| Test evidence | ready/unready, valid/invalid DeviceManager, StatFs fallback, >2 TiB, genuine zero and unknown all pass offline |
|
||||
|
||||
Required device tests: public FAT/exFAT and adopted private ext4, mount/unmount/
|
||||
reinsertion, permission denial/removal race, truly full media, >2 TiB arithmetic,
|
||||
UI/service consumers and class-loader/preopt compatibility.
|
||||
|
||||
## Integration/signing request
|
||||
|
||||
All three APK targets share the Philips Platform signer and UID1000; `tvfw.jar`
|
||||
requires a coherent system image and ART preopt. Please build these changes from
|
||||
the matching `TPM191E_R` source tree, sign with the existing approved keys, run
|
||||
the tests above, and ship as an official A/B update or supply a separately
|
||||
Platform-signed Channels test APK plus full model-specific rollback procedure.
|
||||
|
||||
67
diagnostics/vendor-bugs/PHILIPS-TECHNICAL-REPORT-DE.md
Normal file
67
diagnostics/vendor-bugs/PHILIPS-TECHNICAL-REPORT-DE.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Technischer Bericht – Speichererschöpfung auf Philips TPM191E
|
||||
|
||||
## Kurzfassung
|
||||
|
||||
Gerät: Philips 43PUS7394/12, Firmware `TPM191E_R.211.000.103.000`, Android TV
|
||||
12/API 31. Drei reproduzierbare Funktionsfehler und eine problematische
|
||||
Warnpolicy wurden bis zu den konkreten Binärmethoden isoliert. Der dominante
|
||||
EPG-Fehler ist durch eine kontrollierte Live-Reparatur und einen vollständigen
|
||||
Offline-Patchtest besonders stark belegt. Ein separater vertraulicher
|
||||
Security-Befund ist auf Anfrage verfügbar und nicht Bestandteil dieses Pakets.
|
||||
|
||||
## Wirkung und erfolgreicher Live-Nachweis
|
||||
|
||||
Vor der Reparatur war `EpgDatabase.db` 1.742.053.376 Byte groß und enthielt
|
||||
3.071.250 abgelaufene Schedule-Einträge. `/data` war zu 73 % belegt und Philips
|
||||
zeigte die Warnung „interner Speicher fast voll“.
|
||||
|
||||
Nach dem schema-identischen kompakten Replacement war die DB zunächst 159.744
|
||||
Byte und nach echter EPG-Neuerfassung etwa 806.912 Byte groß. Sie enthielt 59
|
||||
Now-, 48 Next- und 1.041 Schedule-Einträge vom 25.08.2026 19:00 bis 01.09.2026
|
||||
02:50; `integrity_check=ok`. Freier `/data`-Platz stieg um 1.701.400 KiB
|
||||
(1,62258 GiB), Belegung sank auf 39 %. Live TV, Bild/Ton, Senderwechsel,
|
||||
Senderliste, Favoriten, Guide/Now-Next, Aufnahmen, Timer und Reminder wurden
|
||||
visuell bestätigt; die Warnung verschwand.
|
||||
|
||||
## Befunde und dauerhafte Fixes
|
||||
|
||||
1. **BUG-001, EPG stale cleanup (High, CONFIRMED).**
|
||||
`EpgProvider$deleteStaleDataRunnable.run()` in `channels.apk!/classes4.dex`
|
||||
(`code_off=0xe298c`) berechnet/loggt eine Grenze, führt aber keine Mutation
|
||||
aus. Der Phase-6-Fix konvertiert die Millisekunden-Clock einmal in die real
|
||||
gespeicherten Epoch-Sekunden und löscht `scheduletable` mit sieben Tagen
|
||||
Retention in indexgestützten 2.000er-Transaktionen. Auf einer Kopie der
|
||||
1,62-GiB-DB wurden 3.063.863 Zeilen gelöscht, 7.387 erhalten, die DB auf
|
||||
4.059.136 Byte reduziert und ein identischer zweiter Lauf bestätigt.
|
||||
2. **BUG-002, GEL WorkManager (Medium, CONFIRMED).**
|
||||
`GatheringService.onStartCommand` (`code_off=0x50bf0`) hängt jeden Intent
|
||||
unter `GEL_WORK` mit Policy 4 `APPEND_OR_REPLACE` an den bestehenden
|
||||
Blattgraphen. Das erklärt 229.453.824 Byte WorkDB und lange Enqueue-Latenz.
|
||||
KEEP allein würde unterschiedliche Intents verlieren. Empfohlen ist eine
|
||||
begrenzte persistente FIFO/Outbox, gezielte Koaleszierung, ein KEEP-
|
||||
Drain-Worker und Watchdog. Das Modell bestand 10.000+10.000 Ereignisse.
|
||||
3. **BUG-003, USB free size zero (Medium, CONFIRMED).**
|
||||
`UsbVolumeListerImpl.getVolumes(String)` in `tvfw.jar` (`code_off=0x17b5e0`)
|
||||
setzt Größen nur bei `sys.mtk.dmremoteservice.ready=1` und publiziert sonst
|
||||
den Default Null. Der Fix validiert den KiB-DeviceManager-Wert und verwendet
|
||||
sonst `StatFs` am echten Mountpfad, um MiB zu liefern; Messfehler bleiben
|
||||
UNKNOWN. Die Offline-Matrix einschließlich >2 TiB bestand.
|
||||
4. **DESIGN-001, Speicherwarnpolicy (Medium).**
|
||||
`TVStorageMonitorService$2.run()` (`code_off=0x2ad380`) verwendet feste
|
||||
500-MB/1-GiB-Grenzen und vermischt freien Platz mit eMMC-Lifetime. Der
|
||||
Ersatz nutzt Androids Volume-Schwellen, Debounce/Hysterese/Cooldown und
|
||||
komponentennahe Bereinigung; Flash-Health bekommt eine separate Meldung.
|
||||
|
||||
## Auslieferungsgrenze
|
||||
|
||||
Channels, GEL und TPTvService verwenden `android.uid.system` und denselben
|
||||
Philips-Plattform-Signer. Ein `/data/app`-Update benötigt dessen privaten
|
||||
Schlüssel/akzeptierte Signierlinie. `tvfw.jar` ist eine registrierte Shared-
|
||||
Library und benötigt kohärenten DEX-/ART-/Client- und AVB-Systemimage-Build.
|
||||
Das Gerät ist locked/green. Deshalb liegen validierte Patchspezifikationen und
|
||||
Tests, aber bewusst keine zufällig signierten APKs oder unsicheren Images bei.
|
||||
Empfohlen wird zunächst ein Philips-signiertes Channels-Update, danach USB,
|
||||
GEL und Warnpolicy jeweils einzeln mit geprüftem Rollback.
|
||||
|
||||
Datenschutzbereinigte Einzelberichte und Hashes stehen in diesem Verzeichnis;
|
||||
Implementierung und Tests unter `patches/`.
|
||||
66
diagnostics/vendor-bugs/PHILIPS-TECHNICAL-REPORT-EN.md
Normal file
66
diagnostics/vendor-bugs/PHILIPS-TECHNICAL-REPORT-EN.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Technical report – storage exhaustion on Philips TPM191E
|
||||
|
||||
## Executive summary
|
||||
|
||||
Device: Philips 43PUS7394/12, firmware `TPM191E_R.211.000.103.000`, Android TV
|
||||
12/API 31. Three reproducible functional defects and one problematic warning
|
||||
policy were isolated to concrete binary methods. The dominant EPG defect has
|
||||
exceptionally strong evidence from a controlled live repair and a complete
|
||||
offline patch test. A separate confidential security finding is available on
|
||||
request and is not part of this package.
|
||||
|
||||
## Impact and successful live validation
|
||||
|
||||
Before repair, `EpgDatabase.db` was 1,742,053,376 bytes and contained 3,071,250
|
||||
expired schedule records. `/data` was 73% used and the Philips low-internal-
|
||||
storage warning was visible.
|
||||
|
||||
After a schema-identical compact replacement, the DB was initially 159,744
|
||||
bytes and about 806,912 bytes after real EPG reacquisition. It contained 59
|
||||
Now, 48 Next and 1,041 Schedule rows spanning 25 Aug 2026 19:00 through 1 Sep
|
||||
2026 02:50; `integrity_check=ok`. Free `/data` increased by 1,701,400 KiB
|
||||
(1.62258 GiB), reducing use to 39%. Live TV, picture/audio, zapping, channels,
|
||||
favorites, Guide/Now-Next, recordings, timers and reminders were visually
|
||||
confirmed; the warning disappeared.
|
||||
|
||||
## Findings and durable fixes
|
||||
|
||||
1. **BUG-001, EPG stale cleanup (High, CONFIRMED).**
|
||||
`EpgProvider$deleteStaleDataRunnable.run()` in `channels.apk!/classes4.dex`
|
||||
(`code_off=0xe298c`) calculates/logs a boundary but performs no mutation.
|
||||
The Phase 6 fix converts the millisecond clock once to the database's actual
|
||||
epoch-second fields and deletes `scheduletable` in indexed 2,000-row
|
||||
transactions with seven-day retention. A copy of the 1.62-GiB DB lost
|
||||
3,063,863 rows, retained 7,387, shrank to 4,059,136 bytes and produced an
|
||||
identical no-op second run.
|
||||
2. **BUG-002, GEL WorkManager (Medium, CONFIRMED).**
|
||||
`GatheringService.onStartCommand` (`code_off=0x50bf0`) appends every Intent
|
||||
below unique name `GEL_WORK` with policy 4, `APPEND_OR_REPLACE`. This explains
|
||||
the 229,453,824-byte WorkDB and long enqueue traversal. KEEP alone would lose
|
||||
distinct Intents. Use a bounded persistent FIFO/outbox, verified selective
|
||||
coalescing, one KEEP drain worker and a watchdog. The model passed
|
||||
10,000+10,000 events.
|
||||
3. **BUG-003, USB free size zero (Medium, CONFIRMED).**
|
||||
`UsbVolumeListerImpl.getVolumes(String)` in `tvfw.jar` (`code_off=0x17b5e0`)
|
||||
assigns size only when `sys.mtk.dmremoteservice.ready=1`, otherwise
|
||||
publishing default zero. Validate the KiB DeviceManager result and fall back
|
||||
to `StatFs` on the real mount path, publishing MiB; failures remain UNKNOWN.
|
||||
The offline matrix, including >2 TiB, passed.
|
||||
4. **DESIGN-001, storage warning policy (Medium).**
|
||||
`TVStorageMonitorService$2.run()` (`code_off=0x2ad380`) uses fixed 500-MB/
|
||||
1-GiB thresholds and mixes capacity with eMMC lifetime. Replace it with
|
||||
Android volume thresholds, debounce/hysteresis/cooldown and owner-specific
|
||||
cleanup. Flash health must have separate maintenance wording.
|
||||
|
||||
## Delivery boundary
|
||||
|
||||
Channels, GEL and TPTvService use `android.uid.system` and the same Philips
|
||||
platform signer. A `/data/app` update needs its private key/accepted lineage.
|
||||
`tvfw.jar` is a registered shared library requiring coherent DEX, ART/client
|
||||
and AVB system-image integration. The device is locked/green. Therefore the
|
||||
package contains validated specifications and tests, deliberately not randomly
|
||||
signed APKs or unsafe images. Recommended order: Philips-signed Channels update,
|
||||
then USB, GEL and warning policy separately with proven rollback.
|
||||
|
||||
Privacy-safe individual reports and hashes are in this directory; implementation
|
||||
and tests are under `patches/`.
|
||||
42
patches/BUILD-TOOLCHAIN.md
Normal file
42
patches/BUILD-TOOLCHAIN.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Reproducible Phase 6 build toolchain
|
||||
|
||||
## Installed and missing
|
||||
|
||||
Present: Temurin OpenJDK 21.0.11 (`java`, `javac`, `jar`, `keytool`), Python
|
||||
3.12.6 and ADB 37.0.1. Missing from PATH: apktool, jadx, smali/baksmali, d8/r8,
|
||||
zipalign and apksigner. No Philips source tree, Android 12 API-31 build stubs,
|
||||
vendor build configuration, platform private key, AVB private key or accepted
|
||||
firmware packer is available. Tools must be version-pinned and hashed before a
|
||||
binary build; none should be downloaded or trusted blindly.
|
||||
|
||||
## Per-component pipeline
|
||||
|
||||
| Target | Golden input | Controlled pipeline | Output gate |
|
||||
|---|---|---|---|
|
||||
| Channels | `diagnostics/raw/storage/channels.apk` | verify hash → decode four DEX/resources → apply EPG source/smali change → assemble DEX → rebuild preserving manifest/resources → zipalign → sign with exact accepted platform signer | APK/DEX parse, only target methods changed, signer lineage exact, install/image test, regenerate ART |
|
||||
| GEL | `diagnostics/raw/storage/gel.apk` | verify hash → implement outbox and schema migration in source/DEX → rebuild → align → platform-sign | migration/action matrix, bounded WorkDB, signer/shared UID, ART |
|
||||
| TPTvService | `diagnostics/raw/TPTvService.apk` | verify hash → replace monitor state machine/resources → rebuild → align → platform-sign | core/persistent start, policy tests, UI strings, ART |
|
||||
| tvfw | `diagnostics/phase4/reference-binaries/tvfw.jar` | verify hash → unpack one DEX → patch exact method → rebuild JAR deterministically | DEX reparse, registered-library client linkage, image integration, regenerated preopt |
|
||||
|
||||
Never overwrite golden files. Build in a new versioned directory and record
|
||||
tool hashes, command lines, input/output hashes and SOURCE_DATE_EPOCH. Java
|
||||
pseudodiffs are specifications; a release build must integrate them into
|
||||
Philips source or a fully verified DEX assembly.
|
||||
|
||||
## Signing, ART and image integration
|
||||
|
||||
All target APKs use `android.uid.system` and the same Philips platform signer.
|
||||
Only that accepted private key/signing lineage can produce an updated package.
|
||||
Signing with a new key or copying META-INF/signature blocks does not work: any
|
||||
content change invalidates v1/v2/v3 cryptographic digests.
|
||||
|
||||
Do not ship golden ODEX/VDEX next to changed DEX. Use the matching vendor build
|
||||
to regenerate ARM32 preopt artifacts, or an explicitly tested image policy that
|
||||
omits them and allows ART compilation. For `tvfw`, validate every client
|
||||
class-loader context. It is a registered shared library, not bootclasspath.
|
||||
|
||||
System-image changes require filesystem image regeneration and an AVB/vbmeta
|
||||
chain signed with a key accepted by the locked/green device. A root shell alone
|
||||
does not solve verified boot or platform signing. No binary was built in Phase
|
||||
6 because the required private material and vendor build/recovery path are
|
||||
absent; producing a randomly signed artifact would be misleading and unsafe.
|
||||
27
patches/DEPLOYMENT-MATRIX.md
Normal file
27
patches/DEPLOYMENT-MATRIX.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Deployment-path comparison
|
||||
|
||||
| Path | Privilege / signer | AVB and SELinux | Persistence / rollback | Rating |
|
||||
|---|---|---|---|---|
|
||||
| A. ordinary APK update | exact Philips platform signer; no root | no AVB change; existing app domain | persistent in `/data/app`; OEM-tested uninstall-system-update rollback | **BEST FIRST TEST** for APK targets when signed |
|
||||
| B. updated-system-app in `/data/app` | same as A; Package Manager-managed | no image change; shared UID/signer still mandatory | OTA/factory-reset interactions testable; base APK remains | **BEST FIRST TEST**; technically the mechanism behind A |
|
||||
| C. runtime instrumentation | root/engineering/instrumentation authority | may hit SELinux; no durable image | temporary, fragile, timing-dependent | **HIGH RISK**, diagnostic prototype only |
|
||||
| D. bind mount changed file | root every boot plus compatible binary | underlying AVB unchanged, but labels/ART/package signature remain problems | nonstandard persistence; remove mount/reboot rollback | **NOT PRACTICAL** for APK/shared library release |
|
||||
| E. system partition/image patch | vendor build or root+unlocked accepted image; platform signer for APKs | filesystem and AVB/vbmeta must be regenerated; labels exact | persistent; A/B image rollback if proven | **GOOD LONG-TERM**, currently blocked/high risk |
|
||||
| F. vendor partition patch | accepted vendor image and policy coordination | cannot correctly relocate current `/system` targets; split-policy risk | OTA-coupled | **NOT PRACTICAL** for these four targets |
|
||||
| G. complete modified firmware | Philips source, platform/AVB/container keys and recovery | coherent SELinux, preopt and AVB | strongest reproducibility and A/B rollback when OEM-supported | **GOOD LONG-TERM**, unavailable today |
|
||||
|
||||
## Exact root boundary
|
||||
|
||||
| Operation | UID1000 enough? | Root? | Bootloader/AVB? | Reason |
|
||||
|---|---:|---:|---:|---|
|
||||
| one-time EPG data repair | yes | no | no | provider-owned app data; already validated |
|
||||
| install accepted platform-signed APK update | Package Manager path | no | no | `/data/app` overlay mechanism |
|
||||
| persistent Channels/GEL/TPTv replacement without OEM signer | no | not sufficient | image/accepted trust chain | shared UID signature remains enforced |
|
||||
| replace `tvfw.jar` temporarily | no | yes | no for volatile bind; still unsafe | root mount plus shared-library/ART restart |
|
||||
| replace `tvfw.jar` persistently | no | image path | yes/accepted AVB | verified `/system` and preopt coupling |
|
||||
| bind mount | no | yes | no if volatile | mount capability and process restart |
|
||||
| modify system/vendor image | no | build/flash authority | yes | locked green verified boot |
|
||||
| selective adopted OEM directory/policy | app code plus vold/SELinux integration | engineering image, not runtime root as design goal | accepted image | new policy and lifecycle code |
|
||||
|
||||
Root is a deployment tool, not a fix. It neither reconstructs Philips private
|
||||
keys nor makes a modified image acceptable to locked AVB.
|
||||
53
patches/MODIFIED-FIRMWARE-ROADMAP.md
Normal file
53
patches/MODIFIED-FIRMWARE-ROADMAP.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Modified firmware roadmap (design only – no flashing performed)
|
||||
|
||||
## Phase 6 status
|
||||
|
||||
Exact stock methods and root causes are now confirmed for EPG, GEL, USB and the
|
||||
storage monitor. Source-level patch specifications and offline harnesses pass.
|
||||
No deployable APK/JAR/image exists: Philips platform signing, vendor build
|
||||
configuration, accepted AVB signing and a proven recovery path remain blockers.
|
||||
See `PHASE6-DECISION.md`, `DEPLOYMENT-MATRIX.md` and `VALIDATION.md`. The next
|
||||
permitted live candidate is a Philips-signed Channels updated-system APK only;
|
||||
no system modification was made in Phase 6.
|
||||
|
||||
| Target | Original path / hash | Class/method | Patch | Layer | Signing / AVB / risk |
|
||||
|---|---|---|---|---|---|
|
||||
| EPG | `/system/priv-app/channels/channels.apk`; `3A44BC...04D34F` | `EpgProvider$deleteStaleDataRunnable.run`, helper/pump | typed 7-day batched retention | APK-internal + system | platform signature/shared UID; rebuild APK/DEX/odex; system AVB; medium boot risk, high TV-function regression risk |
|
||||
| GEL queue | `/system/app/gel/gel.apk`; `29A68E...D383D7` | enqueue site for `GELWorker`/`GEL_WORK` | bounded/coalesced unique work + prune | APK-internal + system | platform shared UID; system AVB; medium |
|
||||
| USB size | `/system/framework/tvfw.jar`; `a0061795...8d11ad` | `UsbVolumeListerImpl.getVolumes` | property-compatible `StatFs` fallback | registered shared Java library | DEX/oat and client class-loader consistency; system AVB; **high functional/boot risk** |
|
||||
| Storage policy | TPTvService APK; `75F492...41ECD` | `TVStorageMonitorService` | API-based threshold, hysteresis, separate health warning | privileged APK | platform signature/system AVB; medium |
|
||||
| Selective adopted data | Channels APK plus optional vold-aware helper | DB-open/migration layer | private-volume DB with internal fallback | APK/platform integration | SELinux policy and lifecycle changes; high functional risk, must not become boot dependency |
|
||||
|
||||
The shortened hashes above are display labels; full hashes are in the
|
||||
individual vendor reports.
|
||||
|
||||
## Build/repack sequence
|
||||
|
||||
1. Reproduce the exact Android 12 32-bit ARM DEX/minSdk environment and preserve
|
||||
package names, sharedUserId and manifest permissions.
|
||||
2. Patch from decompiled Java only as a specification; implement against smali
|
||||
or source, rebuild, align and sign with the firmware's platform key. A random
|
||||
replacement signature breaks shared UID and privileged grants.
|
||||
3. For `tvfw.jar`, update matching shared-library preopt artifacts or force
|
||||
correct first-boot compilation in a test image. Verify all `uses-library`
|
||||
client class-loader contexts and hidden-API/class linkage.
|
||||
4. Integrate into inactive-slot system image, recompute filesystem/AVB metadata
|
||||
and sign vbmeta with an accepted key. The current locked/green production
|
||||
chain does not accept arbitrary images.
|
||||
5. Test on a sacrificial board or fully recoverable OEM engineering path before
|
||||
this TV. Do not mix binary changes with storage migration in the first build.
|
||||
|
||||
## SELinux and OTA
|
||||
|
||||
The three functional fixes need no new SELinux access. A selective private-
|
||||
volume design does: define a narrow type for reconstructible EPG data and allow
|
||||
only Channels database operations after vold readiness. Never grant generic
|
||||
`mnt_expand_file` access. Every system/vendor change invalidates stock AVB and
|
||||
may be overwritten by OTA; maintain reproducible patches per firmware build.
|
||||
|
||||
## Root boundary
|
||||
|
||||
The completed EPG repair and diagnosis required no root. Root/engineering image
|
||||
access is genuinely required for replacing APK/JAR files, policy/fstab/init,
|
||||
bind mounts, private-volume ownership outside app APIs, compiled-artifact
|
||||
management and AVB-compatible image deployment. None was attempted.
|
||||
59
patches/PHASE6-DECISION.md
Normal file
59
patches/PHASE6-DECISION.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Phase 6 decision
|
||||
|
||||
## Outcome
|
||||
|
||||
The four defects have actionable fixes and reproducible offline evidence. Three
|
||||
exact source-level patch specifications and a storage-policy implementation
|
||||
plan were produced; all local harnesses pass. No deployable APK/JAR/firmware
|
||||
binary was built because the cryptographic and vendor-build prerequisites do
|
||||
not exist in the workspace. This is the technically correct stopping point.
|
||||
|
||||
## Answers to the Phase 6 decision questions
|
||||
|
||||
1. **Real patchability:** BUG-001, BUG-002 and BUG-003 are patchable at their
|
||||
exact methods; DESIGN-001 is patchable in TPTvService. BUG-002 requires a
|
||||
bounded durable outbox, not a one-byte KEEP edit.
|
||||
2. **Successfully built:** guarded EPG cleanup implementation, exact diffs,
|
||||
GEL/USB/storage models, static verifiers, baseline and central harness.
|
||||
All pass. No signed binary was built.
|
||||
3. **Signing blocker:** Channels, GEL and TPTvService share
|
||||
`android.uid.system` and the Philips platform signer. Its private key or an
|
||||
accepted signer service is required even for `/data/app` updates.
|
||||
4. **AVB blocker:** persistent `tvfw` or `/system` changes require a regenerated
|
||||
image/vbmeta accepted by the locked green AVB chain. Root alone is not
|
||||
enough.
|
||||
5. **UID1000 capability:** one-time app-data EPG repair is sufficient and was
|
||||
proven. UID1000 cannot sign/update its own APK or alter verified `/system`.
|
||||
6. **Root need:** temporary instrumentation/bind mount and loose system-file
|
||||
experiments need root. Those are not production deployment solutions.
|
||||
7. **Bootloader/firmware access:** needed for persistent system/shared-library
|
||||
images unless Philips supplies signed OTA/firmware packaging. A/B/recovery
|
||||
capability exists architecturally but an accessible accepted restore path
|
||||
is not proven.
|
||||
8. **First live patch:** BUG-001 as a Philips-platform-signed Channels
|
||||
updated-system APK, one patch only, after rollback is proven. It has the
|
||||
clearest measured benefit. No live test should begin today.
|
||||
9. **Selective adopted storage:** not presently justified. Correct retention
|
||||
changed the live EPG DB from 1.62 GiB to ~0.8 MiB and restored 1.62 GiB of
|
||||
`/data` capacity without USB dependency. Architecture C (internal retained
|
||||
DB) wins.
|
||||
10. **Robust long-term architecture:** Philips source-integrated fixes, exact
|
||||
platform signing, rebuilt ART artifacts, narrow/no new SELinux permissions,
|
||||
and a complete OEM A/B image/OTA signed by accepted AVB keys. Use bounded
|
||||
retention/outboxes and keep capacity and eMMC health separate.
|
||||
|
||||
## Status by patch
|
||||
|
||||
| Patch | Root cause | Offline implementation | Tests | Deployment |
|
||||
|---|---|---|---|---|
|
||||
| EPG stale cleanup | CONFIRMED exact Runnable logs without mutation | READY as source/SQL spec | PASS including 1.62-GiB snapshot | BLOCKED on signed Channels build/recovery |
|
||||
| GEL WorkManager | CONFIRMED exact APPEND_OR_REPLACE callsite | READY as bounded-outbox design/model | PASS | BLOCKED on source/schema integration and signer |
|
||||
| USB free size | CONFIRMED property-gated zero default | READY as exact method spec | PASS | BLOCKED on tvfw rebuild, clients/preopt and AVB |
|
||||
| Storage monitor | CONFIRMED fixed thresholds + health conflation | READY as state-machine plan | PASS | BLOCKED on TPTvService build/signer |
|
||||
|
||||
## Final recommendation
|
||||
|
||||
Send the vendor package with exact evidence and request either Philips-signed
|
||||
updated APKs (Channels first) or a complete TPM191E maintenance firmware. Keep
|
||||
the current repaired television state unchanged. Preserve the phase-5 rollback
|
||||
snapshots until normal use and a longer observation period remain clean.
|
||||
74
patches/PHASE7-DECISION.md
Normal file
74
patches/PHASE7-DECISION.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Phase 7 decision – Signing, OTA, recovery and deployment
|
||||
|
||||
## 1. Can the three APKs be updated under `/data/app`?
|
||||
|
||||
Android's updated-system-app mechanism can in principle update Channels, GEL and
|
||||
TPTvService while retaining their stock `/system` APKs. The device already uses
|
||||
this mechanism for other packages. None of the three is currently updated.
|
||||
|
||||
## 2–3. What exact signature blocks us?
|
||||
|
||||
All three share UID1000 and the exact Platform signer SHA-256
|
||||
`D8:FF:E0:AF:40:4E:AB:8E:45:5A:00:74:D4:17:B0:DA:12:D0:67:26:E5:05:B0:2B:7F:DB:18:31:29:C8:90:68`.
|
||||
It is also the framework-res signer. The matching private key or OEM signing
|
||||
service is unavailable; the signer is therefore mandatory and currently
|
||||
blocking. The separate OTA certificate cannot replace it.
|
||||
|
||||
## 4. tvfw.jar
|
||||
|
||||
No useful isolated deployment exists. It is DEX-bearing, preoptimized and
|
||||
loaded as a Philips shared library by many clients. Although not on
|
||||
BOOTCLASSPATH/SYSTEMSERVERCLASSPATH, it requires coherent ART/client/system-
|
||||
image integration and accepted AVB.
|
||||
|
||||
## 5. AVB boundary
|
||||
|
||||
The device is locked/green with AVB 1.2 and enforcing dm-verity. `system`,
|
||||
`vendor`, `linux_rootfs`, `3rd` and `factory` have device-proven AVB mappings/
|
||||
fstab flags. Exact vbmeta descriptors, keys, chains and rollback indexes are
|
||||
unread. A content-changed system image is not trusted without OEM AVB signing.
|
||||
|
||||
## 6. Recovery paths actually proven
|
||||
|
||||
Proven: PackageManager fallback for a removable updated-system APK while normal
|
||||
Android/ADB runs; static A/B architecture, UpdateEngine, Boot-Control HAL and
|
||||
UpdateVerifier. Not proven: physical recovery key combo, recovery ADB/sideload,
|
||||
USB emergency mode, manual slot control or end-to-end bad-slot fallback. No
|
||||
separate recovery partition exists.
|
||||
|
||||
## 7. Bad Channels update
|
||||
|
||||
Likely the TV still boots and the update can be removed with PackageManager,
|
||||
revealing the untouched stock APK. This is not yet cold-boot tested, and a data
|
||||
schema migration could make the old APK unusable. Hence it is not a present GO.
|
||||
|
||||
## 8. Bad framework/tvfw patch
|
||||
|
||||
Many Philips TV clients may crash; ADB/UI availability is uncertain. There is
|
||||
no PM fallback. Recovery depends on A/B rollback or an official full USB
|
||||
firmware path, neither end-to-end proven. High risk.
|
||||
|
||||
## 9–10. Safest permanent path and live status
|
||||
|
||||
Safest production path: Philips/TPVision integrates, signs and ships the fixes
|
||||
through its official build/OTA process. Safest first engineering candidate:
|
||||
OEM-Platform-signed Channels updated-system APK after a lab rollback drill.
|
||||
**First live patch remains NO-GO.** No installation was performed in Phase 7.
|
||||
|
||||
## 11. What Philips/TPVision must provide
|
||||
|
||||
- matching source/build integration and approved Platform signing;
|
||||
- preferably an official A/B firmware containing the fixes;
|
||||
- full `autorun*.upg` recovery firmware for 43PUS7394/12 / `TPM191E_R`;
|
||||
- exact cold-boot USB recovery/key procedure;
|
||||
- documented payload/AVB signing and rollback policy;
|
||||
- or a Platform-signed Channels test APK plus verified uninstall/data rollback.
|
||||
|
||||
## 12. Maintenance fallback
|
||||
|
||||
Use `tools/epg-health-check/health_check.py`. Its default ADB mode reads only
|
||||
`dumpsys dbinfo` and `df`; offline snapshot mode uses SQLite read-only. It never
|
||||
repairs automatically. If maintenance is recommended, repeat only the already
|
||||
validated supervised repair workflow after a fresh backup and explicit
|
||||
authorization.
|
||||
|
||||
52
patches/RECOVERY-PLAN.md
Normal file
52
patches/RECOVERY-PLAN.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Recovery plan before any future live patch
|
||||
|
||||
## Current gate
|
||||
|
||||
**NO-GO for deployment today.** No patched binary, accepted Philips signer,
|
||||
verified firmware packer, proven bootloader entry, or independently tested
|
||||
recovery/USB restore route exists. The commands below are procedural templates,
|
||||
not instructions executed in Phase 6.
|
||||
|
||||
## Golden component records
|
||||
|
||||
| Component | TV path | SHA-256 | owner:group / mode / label | Runtime impact |
|
||||
|---|---|---|---|---|
|
||||
| Channels | `/system/priv-app/channels/channels.apk` | `3a44bc...04d34f` | `root:root 0644 system_file` | EPG/TV provider |
|
||||
| GEL | `/system/app/gel/gel.apk` | `29a68e...d383d7` | same | GEL actions/WorkManager |
|
||||
| TPTvService | `/system/app/TPTvService/TPTvService.apk` | `75f492...41ecd` | same | persistent TV/storage service |
|
||||
| tvfw | `/system/framework/tvfw.jar` | `a00617...8d11ad` | same | registered TV shared library |
|
||||
|
||||
Full APK/JAR and ARM ODEX/VDEX hashes are in `baseline/hashes.sha256`. Local
|
||||
golden copies are immutable. Before a test, also record package dump, slot,
|
||||
AVB state, process state, current APK path, data DB hashes/sizes, owners, modes,
|
||||
SELinux labels and a bootable OEM firmware copy.
|
||||
|
||||
## Rollback by deployment type
|
||||
|
||||
- **Platform-signed `/data/app` update:** use only a previously lab-validated
|
||||
Package Manager “uninstall system update” procedure for that exact Android
|
||||
build, then restart the affected process or reboot through the approved test
|
||||
plan. Do not assume a command spelling until it is confirmed read-only/help
|
||||
output and tested on a recoverable device. Base `/system` remains intact.
|
||||
- **Volatile instrumentation/bind mount:** stop the affected process, remove the
|
||||
test mechanism from a root/engineering environment, restore its mount
|
||||
namespace, then restart. If that cannot be done, controlled reboot returns to
|
||||
the verified base only if no persistent boot hook was added.
|
||||
- **A/B image:** keep the known-good slot untouched. The new slot must have a
|
||||
boot-success timeout and automatic/manual slot fallback already proven.
|
||||
Never mark it successful before TV, ADB and storage validation.
|
||||
- **Full firmware:** use only Philips-signed recovery media and a procedure
|
||||
demonstrated on the same platform. Preserve the original firmware and all
|
||||
hashes offline.
|
||||
|
||||
## Crash and bootloop response
|
||||
|
||||
For an app crash with working ADB, collect logs/package state first, then invoke
|
||||
the prevalidated rollback. For a `tvfw` linkage failure or system_server/boot
|
||||
loop, ADB may never become available; rollback must therefore be possible from
|
||||
boot control, recovery or signed USB media without Android userspace. Existing
|
||||
init/recovery hooks and A/B layout are evidence of capability, not proof that a
|
||||
publicly accessible restore path accepts our image.
|
||||
|
||||
Do not start any system/shared-library test until cold-boot recovery, power-loss
|
||||
recovery and return-to-stock hashes have been demonstrated end-to-end.
|
||||
51
patches/REGRESSION-TEST-PLAN.md
Normal file
51
patches/REGRESSION-TEST-PLAN.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Regression test plan
|
||||
|
||||
## EPG
|
||||
|
||||
- Empty, current, seven-day and 30-day datasets; simulated rows older than
|
||||
cutoff; time-zone/DST and explicit millisecond-unit tests.
|
||||
- Verify current/future EPG, Now/Next, guide, channel change, refresh, search,
|
||||
favorites, reminders, manual/event recordings and series metadata.
|
||||
- Cleanup twice (idempotence), while DB locked, during acquisition, at low
|
||||
space, and with injected power loss between batches/commit.
|
||||
- 2.000-row batching, endtime-index plan, lock duration and auto-vacuum writes.
|
||||
- Daily measurements for 30 days: DB bytes, pages, rows, oldest/newest;
|
||||
historical data never exceeds policy plus one batch.
|
||||
|
||||
## GEL
|
||||
|
||||
- 1/100/10.000 service intents and 60-second alarms; reboot during enqueue and
|
||||
worker execution.
|
||||
- WorkSpec/dependency counts, maximum chain depth, oldest completion, DB/WAL
|
||||
size, enqueue-to-run latency and `pruneWork` behavior.
|
||||
- Idempotent coalescing and guaranteed delivery of classified non-idempotent
|
||||
events; no backlog after network/service outage.
|
||||
|
||||
## USB free space
|
||||
|
||||
- Public VFAT and private adopted ext4; DMRemote property old/new name,
|
||||
ready=1/0/missing; service exception.
|
||||
- Mounted, genuinely full, read-only, removed during query, reinserted with new
|
||||
path, >2-GiB values. Compare long-byte result to `StatFs`/`df`.
|
||||
|
||||
## Storage monitor
|
||||
|
||||
- Just above/below Android and OEM thresholds; fill/reclaim cycles verify
|
||||
hysteresis and notification dismissal.
|
||||
- adopted present/missing/full, but internal `/data` independently full/free.
|
||||
- eMMC lifetime buckets and PRE_EOL independently represented; no capacity
|
||||
false positive solely from health data.
|
||||
|
||||
## Selective adopted EPG
|
||||
|
||||
- Cold boot with USB present, absent, slow, corrupt and removed during write;
|
||||
vold delayed; user unlock; factory reset; OTA; UUID replacement.
|
||||
- SELinux enforcing throughout; correct owner/label; no path traversal;
|
||||
SQLite integrity and locking across all fallback transitions.
|
||||
|
||||
## Phase-5 repair acceptance
|
||||
|
||||
Software gates: ADB/network alive, Android volumes NORMAL, Channels/EPGData/
|
||||
PlayTV/TVInput running, DB integrity OK, no SQLite exception/crashloop, 358
|
||||
channel mirrors and 203 mappings retained. Visual gates are listed in
|
||||
`diagnostics/phase5/epg-repair.md` and require the owner.
|
||||
43
patches/SAFE-LIVE-TEST-PLAN.md
Normal file
43
patches/SAFE-LIVE-TEST-PLAN.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Safe future live-test plan
|
||||
|
||||
No deployment is authorized by this document. Begin only after a matching
|
||||
platform-signed artifact, static validation and the recovery gates in
|
||||
`RECOVERY-PLAN.md` exist. Test one patch per build.
|
||||
|
||||
## Candidate order
|
||||
|
||||
1. EPG cleanup (clearest root cause and measurable result).
|
||||
2. USB free-size.
|
||||
3. GEL bounded outbox.
|
||||
4. Storage monitor policy.
|
||||
|
||||
The first binary candidate should be a Philips-signed Channels updated-system
|
||||
APK in `/data/app`, not a `/system` overwrite. Until that exists, the already
|
||||
validated UID1000 one-time EPG data repair is the only proven live mechanism.
|
||||
|
||||
## Per-patch protocol
|
||||
|
||||
**BEFORE:** preserve golden binaries/data, hashes, current package paths,
|
||||
signer/version, storage/EPG/GEL/USB metrics, logs, slot/AVB state and user-visible
|
||||
TV baseline. Prove rollback after a cold boot.
|
||||
|
||||
**DEPLOY:** verify input and output hashes/signature, install through the single
|
||||
approved path, and make no other change. Never pair changed DEX with golden
|
||||
ODEX/VDEX manually.
|
||||
|
||||
**RESTART:** stop/start only the minimal affected package when supported; a
|
||||
shared-library image requires the vendor-defined reboot. Record downtime and
|
||||
all startup/linkage errors.
|
||||
|
||||
**VALIDATE:** run software gates plus Live TV, picture/audio, zapping, channel
|
||||
list, favorites, Guide/Now-Next, recordings/timers/reminders, standby/wake,
|
||||
network/ADB and low-storage UI. Add target metrics: EPG cutoff/plateau; USB vs
|
||||
`df`; GEL graph/outbox bounds; storage hysteresis/health separation.
|
||||
|
||||
**ROLLBACK:** trigger rollback on signature/linkage failure, crashloop,
|
||||
boot-time regression, missing TV function, data corruption, wrong units, event
|
||||
loss, unexpected storage UI or inability to collect evidence. Restore the
|
||||
known-good artifact/slot, validate hashes and rerun the baseline.
|
||||
|
||||
Minimum release soak is 30 days including power interruption and USB removal;
|
||||
EPG must cross several daily cleanup cycles.
|
||||
38
patches/SIGNING-ANALYSIS.md
Normal file
38
patches/SIGNING-ANALYSIS.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Platform-signing and shared-UID analysis
|
||||
|
||||
## Proven baseline
|
||||
|
||||
Channels, GEL and TPTvService carry the same RSA-2048 certificate, SHA-256
|
||||
`D8:FF:E0:AF:40:4E:AB:8E:45:5A:00:74:D4:17:B0:DA:12:D0:67:26:E5:05:B0:2B:7F:DB:18:31:29:C8:90:68`,
|
||||
and APK Signature Schemes v1, v2 and v3. Their manifests request the system
|
||||
shared UID; Channels is privileged and TPTvService is core/persistent. The
|
||||
public certificate exists in each APK, but the private key does not exist in
|
||||
the workspace.
|
||||
|
||||
## Package Manager consequences
|
||||
|
||||
An updated system app can normally live under `/data/app` and override its
|
||||
read-only `/system` base while preserving system-app identity. For these
|
||||
packages it must have the same package name, compatible version and—decisively—
|
||||
the exact accepted signer/signing lineage. The shared UID also requires signer
|
||||
compatibility with the other members. A debug/test key, a certificate with the
|
||||
same subject text, or copied signature files is rejected or invalid.
|
||||
|
||||
Therefore:
|
||||
|
||||
- a Philips-platform-signed Channels/GEL/TPTvService APK is potentially the
|
||||
safest first deployment because it need not alter `/system` or AVB;
|
||||
- a locally rebuilt but differently signed APK is not an installable update;
|
||||
- changing sharedUserId/package name creates a different app and cannot replace
|
||||
privileged providers/services or inherit signature permissions;
|
||||
- no “signature bypass” is proposed.
|
||||
|
||||
`tvfw.jar` is not an APK and has no package update route. Its integrity is
|
||||
controlled by the verified system image and ART/shared-library integration.
|
||||
|
||||
## What is still unknown
|
||||
|
||||
Philips signer custody/lineage, whether Philips will sign an external patch,
|
||||
their current build-tools versions, OTA payload keys and firmware container
|
||||
signing are unknown. They must be supplied by Philips or a documented accepted
|
||||
engineering/recovery chain; they cannot be inferred from public certificates.
|
||||
72
patches/STORAGE-REDESIGN.md
Normal file
72
patches/STORAGE-REDESIGN.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Recommended storage redesign
|
||||
|
||||
## Decision
|
||||
|
||||
Keep `/data` on internal eMMC. Repair retention/pruning leaks first. Use adopted
|
||||
USB only for large, reconstructible and non-boot-critical content through
|
||||
Android's private-volume APIs, always with an internal fallback. Do not make
|
||||
boot, Package Manager, user unlock or core TV playback depend on USB.
|
||||
|
||||
## Architecture
|
||||
|
||||
1. Internal: settings, credentials, TV channel source, favorites,
|
||||
recordings/reminders metadata, small EPG index/mapping and a minimal empty
|
||||
EPG DB.
|
||||
2. Adopted private ext4: optional EPG schedule payload/cache and downloadable
|
||||
assets only after an OEM platform integration. Android has no ordinary
|
||||
public API that allocates one arbitrary SQLite file directly in another
|
||||
app-private adopted-volume root. `getExternalFilesDirs()` may expose a
|
||||
secondary/emulated path but is not the desired direct private-ext4 SQLite
|
||||
contract; PackageManager movement moves the package/data as a unit and is
|
||||
not selective. Never use `/storage/emulated` FUSE for this database.
|
||||
3. At boot, Channels opens its internal minimal DB immediately. An OEM helper
|
||||
creates/types an app-owned directory below the adopted private volume. A
|
||||
volume callback activates it only after vold reports `MOUNTED` and an
|
||||
app-owned sentinel/schema/hash check passes. This needs a narrow SELinux
|
||||
type; it is not available on the stock image.
|
||||
4. If USB disappears or I/O fails, close adopted SQLite connections, fall back
|
||||
to internal Now/Next/minimal schedule, notify without crashloop, and rebuild
|
||||
adopted cache after reinsertion.
|
||||
5. Split the current monolithic DB at the provider layer: internal
|
||||
channel/mapping/Now/Next authority, optional external schedule cache. Do not
|
||||
use cross-volume SQLite `ATTACH` as an atomicity assumption. DB operations
|
||||
use a private ext4 directory, normal SQLite locking, fsync/atomic rename and
|
||||
bounded retention. No symlink or global bind mount.
|
||||
|
||||
## Why this order
|
||||
|
||||
The EPG and GEL fixes already recover about 1,84 GiB combined without changing
|
||||
boot architecture. Official app movement offers little here because the large
|
||||
listed packages are updated system apps. A whole-`/data` USB design intersects
|
||||
early init, vold timing, FBE/fscrypt, SELinux, recovery, factory reset, OTA and
|
||||
USB power; removal could make the TV unbootable.
|
||||
|
||||
## Failure and migration protocol
|
||||
|
||||
- Feature flag default off; preflight performance and free space.
|
||||
- Copy only reconstructible schedule rows with SQLite backup into a versioned
|
||||
temp directory, run `integrity_check`, fsync file and directory, then
|
||||
atomically switch an app-level generation marker; retain internal fallback.
|
||||
- Never migrate recording/reminder DB or channel/favorite authority blindly.
|
||||
- Maintain last-known-good schema version and garbage-collect old adopted
|
||||
generations only after successful reopen.
|
||||
- USB missing/slow/corrupt is a tested normal state, not a boot failure.
|
||||
- Removal mid-query/write must close the external pool, discard the incomplete
|
||||
generation and continue with internal Now/Next. Reinsertion never reuses a
|
||||
DB until UUID, schema, integrity and generation checks pass.
|
||||
- Adopted media is device-bound/encrypted and not a portable backup. Factory
|
||||
reset, reformat, UUID change and OTA migration are explicit test cases.
|
||||
|
||||
## Classification
|
||||
|
||||
Leak fixes: **SUPPORTED APP/FIRMWARE FIX**. Movable ordinary apps: **ANDROID
|
||||
PLATFORM BEHAVIOR**. Selective EPG private-volume path: **POSSIBLE WITH PATCH**.
|
||||
Bind mount/symlink: **HIGH-RISK HACK**. Whole `/data`: **NOT PRACTICAL**.
|
||||
|
||||
## Release gates
|
||||
|
||||
Random-write latency and fsync p95 must remain within an OEM-defined TV UI
|
||||
budget; 1,000 removal/reinsert cycles, standby power cuts, full-volume writes,
|
||||
corruption, factory reset and OTA must leave Live TV and the internal provider
|
||||
usable. Without OEM source, SELinux policy and a recoverable image build this
|
||||
remains architecture documentation, not a deployable patch.
|
||||
49
patches/VALIDATION.md
Normal file
49
patches/VALIDATION.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Phase 6 validation report
|
||||
|
||||
Validation date: 2026-08-26. Scope is offline engineering only; no patched
|
||||
binary was installed and no TV state was changed during the validation run.
|
||||
|
||||
## Automated result
|
||||
|
||||
`python patches/tests/run_all.py --write-hashes` completed successfully:
|
||||
|
||||
- all five golden inputs matched their pinned SHA-256 hashes;
|
||||
- EPG synthetic tests passed empty, boundary, current/future, 30-day,
|
||||
>1-year, multi-batch and idempotent cases;
|
||||
- the GEL stock DEX verifier matched the exact `GEL_WORK` policy-4 enqueue
|
||||
instructions;
|
||||
- bounded-outbox tests passed 10,000 coalescible and 10,000 FIFO events, O(1)
|
||||
active WorkSpec, caps and crash retry;
|
||||
- USB tests passed DeviceManager validation, StatFs fallback, >2-TiB, true
|
||||
zero and unknown states;
|
||||
- storage policy tests passed low debounce, critical, hysteresis, notification
|
||||
deduplication, unknown measurement and health/capacity separation.
|
||||
|
||||
Hashes of the central small artifacts are in `artifacts/hashes.sha256`.
|
||||
|
||||
## Historic EPG snapshot result
|
||||
|
||||
On a disposable byte-identical copy of the 1,742,053,376-byte DB, the seven-day
|
||||
algorithm deleted 3,063,863 of 3,071,250 schedule rows in 1,532 transactions
|
||||
(maximum 2,000 rows), retained 7,387, and shrank the FULL-auto-vacuum DB to
|
||||
4,059,136 bytes. `integrity_check=ok`; the oldest retained end time equaled the
|
||||
cutoff, future data remained, and an immediate second run changed neither bytes
|
||||
nor hash. Runtime was 141.609 seconds; slowest batch 1,391 ms. See
|
||||
`epg-stale-cleanup/phase6-full-snapshot-result.json`.
|
||||
|
||||
## Binary static-validation status
|
||||
|
||||
Golden APK/JAR archives and DEX callsites were parseable; manifests, DEX counts,
|
||||
certificates, v2/v3 signing blocks and preopt inventory were recorded. There is
|
||||
no patched APK/JAR to re-decompile, compare, align or signature-verify. Those
|
||||
gates are **NOT RUN / BLOCKED**, not passed: Philips source/build configuration,
|
||||
platform private key, AVB key and proven recovery route are absent. Emitting a
|
||||
randomly signed package would not satisfy Package Manager or shared UID.
|
||||
|
||||
## Remaining release gates
|
||||
|
||||
Vendor-source integration, rebuilt-DEX comparison, unchanged manifest/resource
|
||||
diff, exact signer-lineage verification, ARM preopt/client linkage, recoverable
|
||||
image or accepted `/data/app` deployment, acquisition-lock contention,
|
||||
standby/power-loss, 30-day soak and full user-visible TV regression all remain.
|
||||
The status is **offline patch design validated; live firmware binary NO-GO**.
|
||||
19
patches/adopted-storage/FAILURE-MATRIX.md
Normal file
19
patches/adopted-storage/FAILURE-MATRIX.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Selective adopted-storage failure matrix
|
||||
|
||||
| Event | Required behavior | Forbidden outcome |
|
||||
|---|---|---|
|
||||
| USB absent at boot | Open internal channel/Now/Next DB; schedule cache empty/reacquires | boot/provider crashloop |
|
||||
| Removal during read | cancel cursor, close external pool, retry against internal minimum | stale FD loop or TV UI ANR |
|
||||
| Removal during write | rollback/discard incomplete external generation | promote partial DB |
|
||||
| Slow/failing fsync | trip latency/I/O circuit breaker and fall back internal | block Live TV thread |
|
||||
| External full | stop cache acquisition, retain internal authority | internal low-space warning caused by USB |
|
||||
| Corrupt DB/sentinel | quarantine generation logically and rebuild later | attach/open as authoritative |
|
||||
| Reinsert same UUID | verify schema, integrity and generation before use | blind reopen |
|
||||
| Different/reformatted UUID | initialize a new cache | trust old pointer |
|
||||
| Factory reset | forget pointer/key and recreate internal minimum | require removed USB to boot |
|
||||
| OTA/schema change | migrate a copy or discard/reacquire cache | in-place irreversible migration without fallback |
|
||||
| Standby/power loss | recover last committed generation | cross-volume atomicity assumption |
|
||||
|
||||
The external payload is reconstructible cache only. Channel numbering,
|
||||
favorites, recordings, timers, reminders, credentials and the minimal provider
|
||||
schema remain internal.
|
||||
18
patches/artifacts/MANIFEST.md
Normal file
18
patches/artifacts/MANIFEST.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Phase 6 artifact manifest
|
||||
|
||||
No patched APK, JAR, ODEX, VDEX, system image or firmware container is emitted.
|
||||
The required Philips platform/AVB signing material, vendor source/build config
|
||||
and proven recovery path are unavailable. An unsigned or randomly signed APK
|
||||
would fail the shared-UID update gate; a loose changed JAR would not be a safe
|
||||
firmware artifact.
|
||||
|
||||
Successfully built and tested artifacts are instead:
|
||||
|
||||
- exact source-level `PATCH.diff` specifications for EPG, GEL and USB;
|
||||
- storage policy and selective adopted-storage design;
|
||||
- executable guarded EPG cleanup reference and four offline test harnesses;
|
||||
- stock-callsite/signing-block verifiers;
|
||||
- golden hashes and the machine-readable full-snapshot test result.
|
||||
|
||||
Their final hashes are generated in `patches/artifacts/hashes.sha256` by the
|
||||
central validation harness. Binary output remains **BLOCKED**, not failed.
|
||||
7
patches/artifacts/hashes.sha256
Normal file
7
patches/artifacts/hashes.sha256
Normal file
@@ -0,0 +1,7 @@
|
||||
6b4e70f384b6296166d2695b745b82957b0ce6a5aa9fda7cea7d2c98739fa559 patches/epg-stale-cleanup/PATCH.diff
|
||||
1af44bd2d1792569a993f31c2314cb769fae1289dcff84d77f0fddb86104b99f patches/epg-stale-cleanup/tools/epg_retention.py
|
||||
a1cf5ad08d16962bb15290c6bd5dcf2e9b6f715c0f0eebc9a3d177db0da445f7 patches/gel-workmanager/PATCH.diff
|
||||
2c0b1e9cde0cc244bc530bb5bfef32a7750871a1d285e450ac0140268ba4fd87 patches/gel-workmanager/tools/verify_stock_callsite.py
|
||||
be82f55e864d127d5ad8f0eacd2748438d6eefd7cdbb986c4c5480b1d21b41c4 patches/usb-freesize/PATCH.diff
|
||||
9cf6295eef02b9efcefb2e22c76034bea05b83cbaf0bcfcdc2bc8111b7b7edaf patches/storage-monitor/POLICY.md
|
||||
c3de6303e22e33bbcda3270b178f166018a3bad929d7eb7821c29d98841f6193 patches/storage/SELECTIVE-ADOPTED-DESIGN.md
|
||||
62
patches/baseline/BASELINE.md
Normal file
62
patches/baseline/BASELINE.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Phase 6 golden baseline
|
||||
|
||||
Captured 2026-08-26 for `TPM191E_R.211.000.103.000`. The four local binaries
|
||||
are immutable reference inputs. No build step may overwrite them; patched
|
||||
outputs must use a new path and filename. Full hashes are in `hashes.sha256`.
|
||||
|
||||
| Component | TV path | Local reference | Bytes | Package / SDK | DEX | Runtime importance |
|
||||
|---|---|---|---:|---|---:|---|
|
||||
| Channels | `/system/priv-app/channels/channels.apk` | `diagnostics/raw/storage/channels.apk` | 14,047,493 | `org.droidtv.channels`, v1/0.0.0.1, min 31, target 30, compile 31 | 4 | privileged UID 1000, `android.uid.system`, `system_app`; core TV/EPG |
|
||||
| GEL | `/system/app/gel/gel.apk` | `diagnostics/raw/storage/gel.apk` | 398,392 | `org.droidtv.gel`, v58724408/7.2.0.56, min 28, target/compile 33 | 1 | system UID 1000, `android.uid.system`, `system_app`; telemetry/WorkManager |
|
||||
| TV framework | `/system/framework/tvfw.jar` | `diagnostics/phase4/reference-binaries/tvfw.jar` | 3,275,657 | registered Java shared library `tvfw`, no APK manifest | 1 | broad TV client surface; highest deployment risk |
|
||||
| TP TV service | `/system/app/TPTvService/TPTvService.apk` | `diagnostics/raw/TPTvService.apk` | 5,471,048 | `org.droidtv.tv.tptvservice`, v31/12, min 31, target 30, compile 31, `coreApp` | 1 | persistent system UID 1000, `android.uid.system`, `system_app`; storage warning service |
|
||||
|
||||
## Signing baseline
|
||||
|
||||
All three APKs carry the same v1 signer certificate and APK Signature Scheme
|
||||
v2 and v3 blocks (plus verity padding):
|
||||
|
||||
- subject/issuer: `EMAILADDRESS=android@android.com, CN=Android, OU=Android, O=Android, L=Mountain View, ST=California, C=US`
|
||||
- certificate SHA-256: `D8:FF:E0:AF:40:4E:AB:8E:45:5A:00:74:D4:17:B0:DA:12:D0:67:26:E5:05:B0:2B:7F:DB:18:31:29:C8:90:68`
|
||||
- certificate SHA-1: `43:80:01:76:AC:53:BF:E9:FD:33:4A:EB:86:1D:40:A1:2A:00:AF:C0`
|
||||
- algorithm: SHA1withRSA, RSA-2048; validity 2015-08-10 through 2042-12-26.
|
||||
|
||||
The public certificate is present; the private platform signing key is not.
|
||||
Matching the subject name is not sufficient—Package Manager requires the same
|
||||
cryptographic signer for the shared UID. `tvfw.jar` has no JAR signature.
|
||||
|
||||
## Compiled artifacts on the stock image
|
||||
|
||||
All APK/JAR and OAT/VDEX files are `root:root`, mode `0644`, SELinux
|
||||
`u:object_r:system_file:s0`, timestamp 2009-01-01. The device has 32-bit ARM
|
||||
compiled artifacts:
|
||||
|
||||
| Input | ODEX bytes | VDEX bytes |
|
||||
|---|---:|---:|
|
||||
| Channels | 66,212 | 131,484 |
|
||||
| GEL | 21,156 | 7,292 |
|
||||
| TPTvService | 49,828 | 105,692 |
|
||||
| tvfw | 37,540 | 51,572 |
|
||||
|
||||
Their hashes are recorded in `hashes.sha256`. A replacement image must either
|
||||
ship matching regenerated artifacts or deliberately use a tested image-build
|
||||
path that causes ART to regenerate them. Mixing a modified DEX with stock
|
||||
ODEX/VDEX is not an acceptable deployment artifact.
|
||||
|
||||
## Trust and deployment boundary
|
||||
|
||||
The active `/system` is AVB/dm-verity protected and the device is locked/green.
|
||||
The APKs use the system shared UID; Channels is also privileged and TPTvService
|
||||
is persistent/core. Therefore a normally installed APK, an ADB overwrite, or a
|
||||
randomly signed rebuild is not a valid replacement. The viable production
|
||||
artifact is an OEM/platform-signed APK integrated into a correctly generated
|
||||
and AVB-signed firmware image. `tvfw.jar` is not in `BOOTCLASSPATH`; it is the
|
||||
registered `tvfw` shared Java library and still has preoptimization and client
|
||||
class-loader-context coupling.
|
||||
|
||||
## Evidence boundary
|
||||
|
||||
Local SHA-256, ZIP/DEX layout, manifests and certificates were inspected
|
||||
offline. TV paths, labels, sizes and compiled-artifact hashes were captured by
|
||||
read-only `pm path`, `ls`, `stat`, `ps` and `sha256sum`; no TV state was
|
||||
changed. The EPG snapshot is a test input only and remains untouched.
|
||||
15
patches/baseline/hashes.sha256
Normal file
15
patches/baseline/hashes.sha256
Normal file
@@ -0,0 +1,15 @@
|
||||
3a44bc648624f0cc9cd9feb4cfc3fd33f41323597718e4fccfc25fe35504d34f diagnostics/raw/storage/channels.apk
|
||||
29a68e6b09700ed4ccdf312c0b99eee0c3f67e329a537fe87ed4341a71d383d7 diagnostics/raw/storage/gel.apk
|
||||
a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad diagnostics/phase4/reference-binaries/tvfw.jar
|
||||
75f492ee89906c8c267d685d2b7d0ce02bb09f3a6b4cc8456f8b096b9e441ecd diagnostics/raw/TPTvService.apk
|
||||
71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba diagnostics/phase4/epg-snapshot/EpgDatabase.db
|
||||
|
||||
# Read-only hashes captured on the TV; files were not pulled or modified.
|
||||
a440f6914a3d6010e9482cc0be962978ab2db73eb2384fb5dc2f5c1e72a25215 /system/priv-app/channels/oat/arm/channels.odex
|
||||
be8923c2aa6232ed44dc81b141e24ed9dc92d84ba346cdb9625da338cd86a29b /system/priv-app/channels/oat/arm/channels.vdex
|
||||
587cd8b7a1a3b6861bd0fb577a0fc0b9e101bf8899c38acae6a83c30d77895d1 /system/app/gel/oat/arm/gel.odex
|
||||
f3ba405a06d1129b19e197207cde94842703acb39663d251f34156778ae867f9 /system/app/gel/oat/arm/gel.vdex
|
||||
8dade47e3181a2906bc7485096d5d62b25da7ee7888bd878180bb1e0f949513d /system/app/TPTvService/oat/arm/TPTvService.odex
|
||||
7a29519398c8a136a1bf7872470e51085e9688af33500f4e707c90fdee63016e /system/app/TPTvService/oat/arm/TPTvService.vdex
|
||||
a4af432c54153c9325d92f338f93b2b0a6cfa6b618a5d2c31d587e93eceff531 /system/framework/oat/arm/tvfw.odex
|
||||
abd0f4ce99404199332a779b195c53ca57d2238d0368910b0936cd55a8572509 /system/framework/oat/arm/tvfw.vdex
|
||||
35
patches/baseline/inspect_apk_signing_block.py
Normal file
35
patches/baseline/inspect_apk_signing_block.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""List APK Signing Block pair IDs without changing the APK."""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
NAMES = {
|
||||
0x7109871A: "APK Signature Scheme v2",
|
||||
0xF05368C0: "APK Signature Scheme v3",
|
||||
0x1B93AD61: "APK Signature Scheme v3.1",
|
||||
0x6DFF800D: "Source stamp",
|
||||
0x42726577: "verity padding",
|
||||
}
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
path = Path(arg)
|
||||
data = path.read_bytes()
|
||||
eocd = data.rfind(b"PK\x05\x06")
|
||||
if eocd < 0:
|
||||
raise SystemExit(f"{path}: EOCD not found")
|
||||
central = struct.unpack_from("<I", data, eocd + 16)[0]
|
||||
size, magic = struct.unpack_from("<Q16s", data, central - 24)
|
||||
if magic != b"APK Sig Block 42":
|
||||
print(f"{path}: no APK Signing Block")
|
||||
continue
|
||||
start = central - size - 8
|
||||
assert struct.unpack_from("<Q", data, start)[0] == size
|
||||
pos, end, ids = start + 8, central - 24, []
|
||||
while pos < end:
|
||||
length = struct.unpack_from("<Q", data, pos)[0]
|
||||
pair_id = struct.unpack_from("<I", data, pos + 8)[0]
|
||||
ids.append((pair_id, NAMES.get(pair_id, "unknown")))
|
||||
pos += 8 + length
|
||||
print(f"{path}: " + ", ".join(f"0x{i:08x} {n}" for i, n in ids))
|
||||
52
patches/epg-stale-cleanup/IMPLEMENTATION.md
Normal file
52
patches/epg-stale-cleanup/IMPLEMENTATION.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# EPG stale-cleanup implementation
|
||||
|
||||
## Exact target and lifecycle
|
||||
|
||||
The stock target is `channels.apk!/classes4.dex`,
|
||||
`Lorg/droidtv/channels/epg/bcepg/EpgProvider$deleteStaleDataRunnable;->run()V`
|
||||
at `code_off=0xe298c`. The Runnable executes under the provider monitor, reads
|
||||
the Philips TV clock, calculates and logs a cutoff, but never calls a delete,
|
||||
batch, `applyBatch`, or SQLite mutation. The production integration must move
|
||||
the potentially long legacy cleanup outside that provider-wide monitor and
|
||||
serialize it with EPG acquisition writes.
|
||||
|
||||
## Unit contract and retention
|
||||
|
||||
The clock API returns epoch **milliseconds**. Physical `scheduletable.starttime`
|
||||
and `endtime` values are epoch **seconds**, proven from both the historic and
|
||||
reacquired databases. Therefore the only supported conversion is:
|
||||
|
||||
```java
|
||||
long nowMillis = clock.currentTimeMillis();
|
||||
long cutoffSeconds = TimeUnit.MILLISECONDS.toSeconds(nowMillis)
|
||||
- TimeUnit.DAYS.toSeconds(7);
|
||||
```
|
||||
|
||||
Seven past days preserve backward-guide behavior while all future rows remain.
|
||||
The first fix intentionally touches only `scheduletable`; Now/Next are tiny and
|
||||
FVP expiry semantics require separate product tests.
|
||||
|
||||
## Database operation
|
||||
|
||||
Use `scheduletable(endtime)` for the ordered candidate scan. Each non-exclusive
|
||||
transaction removes at most 2,000 IDs selected by `endtime, _id`, commits, then
|
||||
yields briefly. Abort cleanly on interruption or sustained busy/IO failure.
|
||||
Log the unit, cutoff, batch count, rows, elapsed time, DB size, and error class.
|
||||
Schedule one daily maintenance invocation with unique/KEEP semantics.
|
||||
|
||||
SQLite journaling makes each batch atomic; an interrupted run merely resumes
|
||||
at the next oldest row. The predicate is idempotent. The stock database has
|
||||
`auto_vacuum=FULL`, so continuous small retention also returns tail pages
|
||||
without a separate `VACUUM`.
|
||||
|
||||
## Deliverables and evidence
|
||||
|
||||
- `PATCH.diff`: source-level production specification.
|
||||
- `SMALI-PATCH.md`: exact DEX boundary and integration constraints.
|
||||
- `tools/epg_retention.py`: guarded offline reference implementation.
|
||||
- `tools/test_epg_retention.py`: synthetic regression harness.
|
||||
- `TEST-RESULTS.md`: complete old-snapshot result.
|
||||
|
||||
This is not a signed APK. Philips source or a controlled smali/DEX rebuild,
|
||||
platform signing, ART/preopt handling, and a recoverable image path are still
|
||||
required for firmware deployment.
|
||||
76
patches/epg-stale-cleanup/PATCH-PLAN.md
Normal file
76
patches/epg-stale-cleanup/PATCH-PLAN.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Patch plan – bounded EPG retention
|
||||
|
||||
## Target
|
||||
|
||||
`/system/priv-app/channels/channels.apk`, DEX class
|
||||
`org.droidtv.channels.epg.bcepg.EpgProvider$deleteStaleDataRunnable` plus a
|
||||
small helper in `EpgDatabaseHelper`. Original APK SHA-256:
|
||||
`3A44BC648624F0CC9CD9FEB4CFC3FD33F41323597718E4FCCFC25FE35504D34F`.
|
||||
|
||||
## Policy
|
||||
|
||||
- Keep the clock API explicitly in **epoch milliseconds**, then convert once to
|
||||
**epoch seconds** before touching the database. The physical `starttime` and
|
||||
`endtime` values in both the old and freshly reacquired databases are epoch
|
||||
seconds. Use `TimeUnit.MILLISECONDS.toSeconds(nowMillis)` and
|
||||
`TimeUnit.DAYS.toSeconds(7)`; never compare a millisecond cutoff to the DB.
|
||||
- Retain seven past days for backward-guide/recording metadata compatibility;
|
||||
retain all future events. Make the value OEM-configurable in 1–30 days.
|
||||
- Now/Next may be deleted after `endtime < now-2h`; FVP rows must also respect
|
||||
their expiry semantics.
|
||||
|
||||
## Algorithm
|
||||
|
||||
Ensure `scheduletable(endtime)` remains indexed. Once per day, initially 5–15
|
||||
minutes after provider startup and only while no acquisition transaction is
|
||||
active, delete at most 2.000 rows per transaction:
|
||||
|
||||
```java
|
||||
long nowMillis = clock.millis();
|
||||
long cutoffSeconds = TimeUnit.MILLISECONDS.toSeconds(nowMillis)
|
||||
- TimeUnit.DAYS.toSeconds(retentionDays);
|
||||
do {
|
||||
int n = db.deleteByIdBatch(
|
||||
"scheduletable", "endtime < ?", cutoffSeconds, "endtime", 2000);
|
||||
metrics.addDeleted(n);
|
||||
if (n == 2000) SystemClock.sleep(100);
|
||||
} while (n == 2000 && powerAndLoadPolicy.permitsMaintenance());
|
||||
```
|
||||
|
||||
`deleteByIdBatch` must run one transaction containing an index-ordered ID
|
||||
selection with `LIMIT ?` and a delete by those IDs. Do not build a 2.000-
|
||||
placeholder SQL string; use a temp-ID table or
|
||||
`DELETE ... WHERE _id IN (SELECT _id ... ORDER BY endtime LIMIT 2000)` with
|
||||
bound cutoff. Commit each batch so rollback journal/lock duration is bounded.
|
||||
|
||||
The fixed Runnable should call this helper and log cutoff, unit, rows, duration,
|
||||
DB bytes and remaining old-row count—not the misleading word “Deleting” before
|
||||
any mutation. Schedule a unique daily WorkManager job with `KEEP`, not a chain.
|
||||
|
||||
Phase 6 deliberately limits the first production change to `scheduletable`.
|
||||
Now/Next are tiny and FVP expiry semantics are not sufficiently proven for a
|
||||
shared delete rule. They can be added only after table-specific retention tests.
|
||||
|
||||
## Auto-vacuum / power / locks
|
||||
|
||||
The DB is `auto_vacuum=FULL`; small daily batches prevent the massive page-move
|
||||
storm seen after years of accumulation. Use normal SQLite transactions and
|
||||
`busy_timeout`/bounded retry with jitter; never delete while acquisition owns a
|
||||
write transaction. A power loss can roll back one batch only. Do not `VACUUM`
|
||||
during Live TV. For a one-time legacy migration, offer the already validated
|
||||
compact-rebuild workflow with backup and atomic rename.
|
||||
|
||||
## Smali plan
|
||||
|
||||
Replace the Runnable body after cutoff computation with a typed call to the new
|
||||
helper; do not inline a shell or raw concatenated SQL payload. If adding DEX
|
||||
methods is impractical, redirect to the already present
|
||||
`EpgProgramPump.deleteScheduleProgramsByTime(long)` only after correcting its
|
||||
unit and adding batching; its current unbounded delete is unsuitable for a
|
||||
3-million-row legacy DB.
|
||||
|
||||
## Acceptance
|
||||
|
||||
See `patches/REGRESSION-TEST-PLAN.md`. Key gate: after a 30-day synthetic soak,
|
||||
oldest row >= cutoff (within one batch), DB growth plateaus, current/future EPG,
|
||||
favorites and recording/reminder tables remain correct.
|
||||
57
patches/epg-stale-cleanup/PATCH.diff
Normal file
57
patches/epg-stale-cleanup/PATCH.diff
Normal file
@@ -0,0 +1,57 @@
|
||||
--- a/src/org/droidtv/channels/epg/bcepg/EpgProvider.java
|
||||
+++ b/src/org/droidtv/channels/epg/bcepg/EpgProvider.java
|
||||
@@
|
||||
- long staleTime = System.currentTimeMillis() - 1800L;
|
||||
- if (epgInfo != null && epgInfo.isFreeViewAvailable()) {
|
||||
- staleTime -= 604800L;
|
||||
- }
|
||||
- Log.d(TAG, "Deleting stale data for time " + staleTime);
|
||||
+ final long nowMillis = System.currentTimeMillis();
|
||||
+ final long cutoffSeconds = TimeUnit.MILLISECONDS.toSeconds(nowMillis)
|
||||
+ - TimeUnit.DAYS.toSeconds(7);
|
||||
+ maintenanceExecutor.execute(() -> {
|
||||
+ EpgCleanupResult r = mDbHelper.deleteOldScheduleInBatches(
|
||||
+ cutoffSeconds, 2000);
|
||||
+ Log.i(TAG, "EPG cleanup unit=seconds cutoff=" + cutoffSeconds
|
||||
+ + " deleted=" + r.deleted + " batches=" + r.batches
|
||||
+ + " durationMs=" + r.durationMs);
|
||||
+ });
|
||||
|
||||
--- a/src/org/droidtv/channels/epg/bcepg/EpgDatabaseHelper.java
|
||||
+++ b/src/org/droidtv/channels/epg/bcepg/EpgDatabaseHelper.java
|
||||
@@
|
||||
+ EpgCleanupResult deleteOldScheduleInBatches(long cutoffSeconds, int batchSize) {
|
||||
+ if (batchSize < 1 || batchSize > 2000
|
||||
+ || cutoffSeconds < 1_000_000_000L
|
||||
+ || cutoffSeconds > 4_102_444_800L) {
|
||||
+ throw new IllegalArgumentException("invalid cleanup boundary");
|
||||
+ }
|
||||
+ SQLiteDatabase db = getWritableDatabase();
|
||||
+ int total = 0, batches = 0;
|
||||
+ long started = SystemClock.elapsedRealtime();
|
||||
+ for (;;) {
|
||||
+ int changed;
|
||||
+ db.beginTransactionNonExclusive();
|
||||
+ try {
|
||||
+ db.execSQL("DELETE FROM scheduletable WHERE _id IN ("
|
||||
+ + "SELECT _id FROM scheduletable WHERE endtime < ? "
|
||||
+ + "ORDER BY endtime, _id LIMIT ?)",
|
||||
+ new Object[]{cutoffSeconds, batchSize});
|
||||
+ changed = (int) DatabaseUtils.longForQuery(
|
||||
+ db, "SELECT changes()", null);
|
||||
+ db.setTransactionSuccessful();
|
||||
+ } finally {
|
||||
+ db.endTransaction();
|
||||
+ }
|
||||
+ total += changed;
|
||||
+ batches++;
|
||||
+ if (changed < batchSize || Thread.currentThread().isInterrupted()) break;
|
||||
+ SystemClock.sleep(100);
|
||||
+ }
|
||||
+ return new EpgCleanupResult(total, batches,
|
||||
+ SystemClock.elapsedRealtime() - started);
|
||||
+ }
|
||||
|
||||
# Reconstructed source-level specification. Do not apply blindly to a Philips
|
||||
# source tree. Acquisition locking and the existing provider monitor must be
|
||||
# integrated and tested in the vendor implementation.
|
||||
57
patches/epg-stale-cleanup/REFERENCE-PATCH.diff
Normal file
57
patches/epg-stale-cleanup/REFERENCE-PATCH.diff
Normal file
@@ -0,0 +1,57 @@
|
||||
--- a/src/org/droidtv/channels/epg/bcepg/EpgProvider.java
|
||||
+++ b/src/org/droidtv/channels/epg/bcepg/EpgProvider.java
|
||||
@@
|
||||
- long staleTime = System.currentTimeMillis() - 1800L;
|
||||
- if (epgInfo != null && epgInfo.isFreeViewAvailable()) {
|
||||
- staleTime -= 604800L;
|
||||
- }
|
||||
- Log.d(TAG, "Deleting stale data for time " + staleTime);
|
||||
+ // Contract: TV clock is milliseconds; the physical EPG columns are epoch seconds.
|
||||
+ final long nowMillis = System.currentTimeMillis();
|
||||
+ final long cutoffSeconds = TimeUnit.MILLISECONDS.toSeconds(nowMillis)
|
||||
+ - TimeUnit.DAYS.toSeconds(7);
|
||||
+ final EpgCleanupResult result = mDbHelper.deleteOldScheduleInBatches(
|
||||
+ cutoffSeconds, 2000);
|
||||
+ Log.i(TAG, "EPG cleanup unit=seconds cutoff=" + cutoffSeconds
|
||||
+ + " deleted=" + result.deleted + " batches=" + result.batches
|
||||
+ + " durationMs=" + result.durationMs);
|
||||
+ mEpgProviderHandler.removeCallbacks(mDeleteStaleDataRunnable);
|
||||
+ mEpgProviderHandler.postDelayed(mDeleteStaleDataRunnable,
|
||||
+ TimeUnit.DAYS.toMillis(1));
|
||||
|
||||
--- a/src/org/droidtv/channels/epg/bcepg/EpgDatabaseHelper.java
|
||||
+++ b/src/org/droidtv/channels/epg/bcepg/EpgDatabaseHelper.java
|
||||
@@
|
||||
+ EpgCleanupResult deleteOldScheduleInBatches(long cutoffSeconds, int batchSize) {
|
||||
+ if (cutoffSeconds < 1_000_000_000L || cutoffSeconds > 4_102_444_800L) {
|
||||
+ throw new IllegalArgumentException("cutoff is not plausible epoch seconds");
|
||||
+ }
|
||||
+ final SQLiteDatabase db = getWritableDatabase();
|
||||
+ int total = 0;
|
||||
+ int batches = 0;
|
||||
+ int n;
|
||||
+ final long started = SystemClock.elapsedRealtime();
|
||||
+ do {
|
||||
+ db.beginTransactionNonExclusive();
|
||||
+ try {
|
||||
+ db.execSQL("DELETE FROM scheduletable WHERE _id IN ("
|
||||
+ + "SELECT _id FROM scheduletable WHERE endtime < ? "
|
||||
+ + "ORDER BY endtime, _id LIMIT ?)",
|
||||
+ new Object[] { cutoffSeconds, batchSize });
|
||||
+ n = (int) DatabaseUtils.longForQuery(db, "SELECT changes()", null);
|
||||
+ db.setTransactionSuccessful();
|
||||
+ } finally {
|
||||
+ db.endTransaction();
|
||||
+ }
|
||||
+ total += n;
|
||||
+ batches++;
|
||||
+ if (n == batchSize) SystemClock.sleep(100);
|
||||
+ } while (n == batchSize && !Thread.currentThread().isInterrupted());
|
||||
+ return new EpgCleanupResult(total, batches,
|
||||
+ SystemClock.elapsedRealtime() - started);
|
||||
+ }
|
||||
|
||||
# This is a source-level implementation specification reconstructed from DEX.
|
||||
# It is not claimed to apply cleanly to an unavailable Philips source tree.
|
||||
# A production integration must also move the database work outside the
|
||||
# provider-wide monitor currently held by deleteStaleDataRunnable.run().
|
||||
39
patches/epg-stale-cleanup/SMALI-PATCH.md
Normal file
39
patches/epg-stale-cleanup/SMALI-PATCH.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Exact DEX patch boundary
|
||||
|
||||
## Stock method
|
||||
|
||||
The target is `channels.apk!/classes4.dex`:
|
||||
|
||||
`Lorg/droidtv/channels/epg/bcepg/EpgProvider$deleteStaleDataRunnable;->run()V`
|
||||
|
||||
Stock code item: `registers=6`, `ins=1`, `outs=4`, `code_off=0xe298c`.
|
||||
It enters the provider-global monitor, obtains `ITvClockManager`, calls
|
||||
`System.currentTimeMillis()`, subtracts literal `1800`, conditionally subtracts
|
||||
`604800`, logs “Deleting”, and returns without a mutation. The instruction
|
||||
offsets and full disassembly are preserved in
|
||||
`diagnostics/deep-forensics/philips/epg-runnable-context.txt`.
|
||||
|
||||
## Required implementation, not an unsafe in-place hex edit
|
||||
|
||||
An acceptable smali rebuild must:
|
||||
|
||||
1. calculate `nowMillis` as a `long`;
|
||||
2. invoke `TimeUnit.MILLISECONDS.toSeconds(nowMillis)` or divide by the explicit
|
||||
long `1000` with overflow-safe ordering;
|
||||
3. subtract `TimeUnit.DAYS.toSeconds(7)` (=604800 seconds);
|
||||
4. reject implausible cutoffs outside 2001-09-09 through 2100-01-01;
|
||||
5. call a new typed helper that executes ordered 2,000-row transactions;
|
||||
6. schedule the next daily run and log post-mutation metrics;
|
||||
7. perform database I/O after leaving the existing provider-global monitor.
|
||||
|
||||
Do **not** redirect directly to the existing
|
||||
`EpgProgramPump.deleteScheduleProgramsByTime(long)`: its DEX method performs one
|
||||
unbounded `ContentResolver.delete()` and would create an uncontrolled
|
||||
multi-million-row transaction. Do not pass the current millisecond value to
|
||||
that method: the database columns demonstrably contain epoch seconds.
|
||||
|
||||
Adding the helper changes method/string/type tables and code-item sizes, so a
|
||||
full classes4.dex rebuild is required. The absence of a local smali/apktool
|
||||
tool and, more importantly, of the Philips platform private key means no
|
||||
deployable signed APK is emitted in this phase. `REFERENCE-PATCH.diff` and the
|
||||
executable SQLite harness are the reviewable patch artifacts.
|
||||
43
patches/epg-stale-cleanup/TEST-RESULTS.md
Normal file
43
patches/epg-stale-cleanup/TEST-RESULTS.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Phase 6 EPG retention test results
|
||||
|
||||
All mutating tests ran offline. The golden 1,742,053,376-byte snapshot remained
|
||||
at SHA-256 `71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba`.
|
||||
The full test used a disposable byte-for-byte copy.
|
||||
|
||||
## Synthetic gates
|
||||
|
||||
`python patches/epg-stale-cleanup/tools/test_epg_retention.py`
|
||||
|
||||
PASS for empty DB, exact cutoff, just-expired row, current and future rows,
|
||||
30-day and >1-year rows, duplicate timestamps spanning multiple batches,
|
||||
integrity and a second idempotent run.
|
||||
|
||||
## Golden-snapshot workload
|
||||
|
||||
Fixed test clock: 2026-08-26 12:00:00 +02:00 (`1787738400` seconds).
|
||||
Seven-day cutoff: `1787133600` seconds.
|
||||
|
||||
| Metric | Before | After |
|
||||
|---|---:|---:|
|
||||
| Schedule rows | 3,071,250 | 7,387 |
|
||||
| Rows older than cutoff | 3,063,863 | 0 |
|
||||
| DB bytes | 1,742,053,376 | 4,059,136 |
|
||||
| SQLite pages | 425,306 | 991 |
|
||||
| Freelist pages | 0 | 0 |
|
||||
|
||||
The implementation committed 1,532 transactions of at most 2,000 rows. Total
|
||||
runtime was 141.609 s on this PC; the slowest batch was 1,391 ms. SQLite
|
||||
`integrity_check` returned `ok`. Because the stock DB has
|
||||
`auto_vacuum=FULL`, the file shrank by 1,737,994,240 bytes without a VACUUM.
|
||||
|
||||
The immediate second run deleted zero rows, took effectively zero seconds and
|
||||
left both size and SHA-256 unchanged. Detailed machine-readable results are in
|
||||
`phase6-full-snapshot-result.json`.
|
||||
|
||||
## Interpretation and release gate
|
||||
|
||||
This proves SQL correctness and bounded transactions for this database, not
|
||||
TV-runtime scheduling or lock safety. A real firmware build still needs an
|
||||
acquisition-contention test, standby/power-loss test, 30-day soak and the full
|
||||
Live TV/Guide/recording/reminder regression matrix. The production patch must
|
||||
not hold the provider-global monitor during the 2–3 minute legacy cleanup.
|
||||
46
patches/epg-stale-cleanup/phase6-full-snapshot-result.json
Normal file
46
patches/epg-stale-cleanup/phase6-full-snapshot-result.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"input_sha256": "71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba",
|
||||
"working_copy_only": true,
|
||||
"now_seconds": 1787738400,
|
||||
"cutoff_seconds": 1787133600,
|
||||
"retention_days": 7,
|
||||
"time_unit": "unix_epoch_seconds",
|
||||
"before": {
|
||||
"bytes": 1742053376,
|
||||
"page_size": 4096,
|
||||
"page_count": 425306,
|
||||
"freelist_count": 0,
|
||||
"auto_vacuum": 1,
|
||||
"schedule_rows": 3071250,
|
||||
"eligible": 3063863,
|
||||
"retained": 7387,
|
||||
"min_end_seconds": 0,
|
||||
"max_end_seconds": 1787608800
|
||||
},
|
||||
"maintenance": {
|
||||
"batch_size": 2000,
|
||||
"batches_including_final": 1532,
|
||||
"deleted": 3063863,
|
||||
"duration_seconds": 141.609,
|
||||
"max_batch_ms": 1391.0
|
||||
},
|
||||
"after": {
|
||||
"bytes": 4059136,
|
||||
"page_count": 991,
|
||||
"freelist_count": 0,
|
||||
"schedule_rows": 7387,
|
||||
"eligible": 0,
|
||||
"retained": 7387,
|
||||
"min_end_seconds": 1787133600,
|
||||
"max_end_seconds": 1787608800,
|
||||
"integrity_check": "ok",
|
||||
"sha256": "44c2da13ce17df9031bc83474fcf91a2f3c4d75fd68a2acdfd3674913e9e4cd4"
|
||||
},
|
||||
"second_run": {
|
||||
"deleted": 0,
|
||||
"duration_seconds": 0.0,
|
||||
"bytes_unchanged": true,
|
||||
"sha256_unchanged": true,
|
||||
"integrity_check": "ok"
|
||||
}
|
||||
}
|
||||
181
patches/epg-stale-cleanup/tools/epg_retention.py
Normal file
181
patches/epg-stale-cleanup/tools/epg_retention.py
Normal file
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reference implementation and offline verifier for bounded EPG retention.
|
||||
|
||||
The Philips schema stores EPG timestamps as Unix epoch *seconds*. This tool is
|
||||
read-only unless both --apply and --ack-working-copy are supplied. Mutating
|
||||
mode is restricted to the sibling work/ directory so the golden snapshot
|
||||
cannot be opened for writing by accident.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import time
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WORK_ROOT = (ROOT / "work").resolve()
|
||||
GOLDEN_SHA256 = "71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba"
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open("rb") as f:
|
||||
for block in iter(lambda: f.read(8 * 1024 * 1024), b""):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def parse_now(value: str | None) -> int:
|
||||
if value is None:
|
||||
return int(time.time())
|
||||
if value.isdecimal():
|
||||
n = int(value)
|
||||
if n > 10_000_000_000:
|
||||
raise ValueError("--now must be epoch seconds, not milliseconds")
|
||||
return n
|
||||
parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if parsed.tzinfo is None:
|
||||
raise ValueError("ISO --now requires an explicit timezone")
|
||||
return int(parsed.timestamp())
|
||||
|
||||
|
||||
def pragma_int(db: sqlite3.Connection, name: str) -> int:
|
||||
return int(db.execute(f"PRAGMA {name}").fetchone()[0])
|
||||
|
||||
|
||||
def table_exists(db: sqlite3.Connection, table: str) -> bool:
|
||||
return db.execute(
|
||||
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (table,)
|
||||
).fetchone() is not None
|
||||
|
||||
|
||||
def table_stats(db: sqlite3.Connection, table: str, cutoff: int) -> dict:
|
||||
if not table_exists(db, table):
|
||||
return {"present": False}
|
||||
row = db.execute(
|
||||
f"SELECT count(*), min(endtime), max(endtime), "
|
||||
f"sum(CASE WHEN endtime < ? THEN 1 ELSE 0 END), "
|
||||
f"sum(CASE WHEN endtime >= ? THEN 1 ELSE 0 END) FROM {table}",
|
||||
(cutoff, cutoff),
|
||||
).fetchone()
|
||||
return {
|
||||
"present": True,
|
||||
"rows": row[0],
|
||||
"min_end_seconds": row[1],
|
||||
"max_end_seconds": row[2],
|
||||
"eligible": row[3] or 0,
|
||||
"retained": row[4] or 0,
|
||||
}
|
||||
|
||||
|
||||
def snapshot(db: sqlite3.Connection, path: Path, cutoff: int) -> dict:
|
||||
return {
|
||||
"path": str(path),
|
||||
"bytes": path.stat().st_size,
|
||||
"page_size": pragma_int(db, "page_size"),
|
||||
"page_count": pragma_int(db, "page_count"),
|
||||
"freelist_count": pragma_int(db, "freelist_count"),
|
||||
"auto_vacuum": pragma_int(db, "auto_vacuum"),
|
||||
"scheduletable": table_stats(db, "scheduletable", cutoff),
|
||||
}
|
||||
|
||||
|
||||
def delete_batches(db: sqlite3.Connection, cutoff: int, batch_size: int) -> dict:
|
||||
sql = (
|
||||
"DELETE FROM scheduletable WHERE _id IN ("
|
||||
"SELECT _id FROM scheduletable WHERE endtime < ? "
|
||||
"ORDER BY endtime, _id LIMIT ?)"
|
||||
)
|
||||
total = batches = 0
|
||||
max_batch_ms = 0.0
|
||||
started = time.monotonic()
|
||||
while True:
|
||||
one = time.monotonic()
|
||||
db.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
cur = db.execute(sql, (cutoff, batch_size))
|
||||
deleted = cur.rowcount
|
||||
db.commit()
|
||||
except BaseException:
|
||||
db.rollback()
|
||||
raise
|
||||
elapsed_ms = (time.monotonic() - one) * 1000
|
||||
max_batch_ms = max(max_batch_ms, elapsed_ms)
|
||||
batches += 1
|
||||
total += deleted
|
||||
if deleted < batch_size:
|
||||
break
|
||||
return {
|
||||
"deleted": total,
|
||||
"batches_including_final": batches,
|
||||
"batch_size": batch_size,
|
||||
"max_batch_ms": round(max_batch_ms, 3),
|
||||
"duration_seconds": round(time.monotonic() - started, 3),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("database", type=Path)
|
||||
ap.add_argument("--now", help="epoch seconds or timezone-qualified ISO-8601")
|
||||
ap.add_argument("--retention-days", type=int, default=7)
|
||||
ap.add_argument("--batch-size", type=int, default=2000)
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
ap.add_argument("--ack-working-copy", action="store_true")
|
||||
ap.add_argument("--hash", action="store_true", help="include file SHA-256")
|
||||
args = ap.parse_args()
|
||||
|
||||
path = args.database.resolve()
|
||||
if not path.is_file():
|
||||
ap.error(f"not a file: {path}")
|
||||
if not 1 <= args.retention_days <= 30:
|
||||
ap.error("retention must be between 1 and 30 days")
|
||||
if not 100 <= args.batch_size <= 10_000:
|
||||
ap.error("batch size must be between 100 and 10000")
|
||||
|
||||
now_seconds = parse_now(args.now)
|
||||
cutoff = now_seconds - args.retention_days * 86_400
|
||||
result = {
|
||||
"time_unit": "unix_epoch_seconds",
|
||||
"now_seconds": now_seconds,
|
||||
"retention_days": args.retention_days,
|
||||
"cutoff_seconds": cutoff,
|
||||
"mode": "apply" if args.apply else "read_only",
|
||||
}
|
||||
|
||||
if args.apply:
|
||||
if not args.ack_working_copy:
|
||||
ap.error("--apply requires --ack-working-copy")
|
||||
if path.parent != WORK_ROOT:
|
||||
ap.error(f"writes are restricted to working copies directly in {WORK_ROOT}")
|
||||
db = sqlite3.connect(path, timeout=5.0, isolation_level=None)
|
||||
else:
|
||||
uri = f"file:{path.as_posix()}?mode=ro&immutable=1"
|
||||
db = sqlite3.connect(uri, uri=True)
|
||||
db.execute("PRAGMA query_only=ON")
|
||||
|
||||
try:
|
||||
result["before"] = snapshot(db, path, cutoff)
|
||||
if args.hash:
|
||||
result["sha256_before"] = sha256(path)
|
||||
if args.apply:
|
||||
db.execute("PRAGMA busy_timeout=5000")
|
||||
result["maintenance"] = delete_batches(db, cutoff, args.batch_size)
|
||||
result["integrity_check"] = db.execute("PRAGMA integrity_check").fetchone()[0]
|
||||
result["after"] = snapshot(db, path, cutoff)
|
||||
if args.hash:
|
||||
result["sha256_after"] = sha256(path)
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
55
patches/epg-stale-cleanup/tools/test_epg_retention.py
Normal file
55
patches/epg-stale-cleanup/tools/test_epg_retention.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Synthetic regression tests for epg_retention.delete_batches()."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from epg_retention import delete_batches
|
||||
|
||||
|
||||
NOW = 1_800_000_000
|
||||
CUTOFF = NOW - 7 * 86_400
|
||||
|
||||
|
||||
def rows(db: sqlite3.Connection) -> list[tuple[int, int]]:
|
||||
return db.execute("SELECT _id,endtime FROM scheduletable ORDER BY _id").fetchall()
|
||||
|
||||
|
||||
def run_case(values: list[int], expected: list[int], batch: int = 100) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "test.db"
|
||||
db = sqlite3.connect(path, isolation_level=None)
|
||||
db.execute("PRAGMA auto_vacuum=FULL")
|
||||
db.execute("CREATE TABLE scheduletable(_id INTEGER PRIMARY KEY, endtime INTEGER)")
|
||||
db.execute("CREATE INDEX indexschedendtime ON scheduletable(endtime)")
|
||||
db.executemany("INSERT INTO scheduletable(endtime) VALUES(?)", [(v,) for v in values])
|
||||
first = delete_batches(db, CUTOFF, batch)
|
||||
remaining = [v for _, v in rows(db)]
|
||||
assert remaining == expected, (remaining, expected)
|
||||
assert db.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
|
||||
second = delete_batches(db, CUTOFF, batch)
|
||||
assert second["deleted"] == 0, second
|
||||
assert first["deleted"] == len(values) - len(expected), first
|
||||
db.close()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# empty; exact cutoff retained; just-before deleted; recent/future retained
|
||||
run_case([], [])
|
||||
run_case(
|
||||
[NOW - 400 * 86_400, NOW - 30 * 86_400, CUTOFF - 1, CUTOFF, NOW - 1, NOW, NOW + 7 * 86_400],
|
||||
[CUTOFF, NOW - 1, NOW, NOW + 7 * 86_400],
|
||||
)
|
||||
# Multiple transactions and stable ordering across duplicate end times.
|
||||
old = [NOW - 365 * 86_400] * 251
|
||||
current = [NOW - day * 86_400 for day in range(7)]
|
||||
future = [NOW + day * 86_400 for day in range(1, 31)]
|
||||
run_case(old + current + future, current + future, batch=100)
|
||||
print("PASS: empty, boundary, current/future preservation, multi-batch and idempotence")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
49
patches/gel-workmanager/PATCH-PLAN.md
Normal file
49
patches/gel-workmanager/PATCH-PLAN.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Patch plan – bound GEL WorkManager history
|
||||
|
||||
## Target and evidence boundary
|
||||
|
||||
`/system/app/gel/gel.apk`, `GatheringService.onStartCommand()` feeding
|
||||
`GELWorker` under unique name `GEL_WORK`. SHA-256:
|
||||
`29A68E6B09700ED4CCDF312C0B99EEE0C3F67E329A537FE87ED4341A71D383D7`.
|
||||
The exact app callsite loads policy integer 4. The embedded AndroidX enqueue
|
||||
implementation proves this is the `APPEND_OR_REPLACE` leaf-dependency path.
|
||||
See `ROOT-CAUSE.md` and the executable verifier.
|
||||
|
||||
## Design
|
||||
|
||||
1. Classify events as idempotent/coalescible or durable.
|
||||
2. Persist all events in a bounded outbox; UPSERT only verified periodic/state
|
||||
families and keep durable events in monotonic FIFO order.
|
||||
3. Enqueue one unique `GEL_DRAIN` worker with `ExistingWorkPolicy.KEEP`, plus a
|
||||
single periodic KEEP watchdog for crash/race recovery.
|
||||
4. Acknowledge outbox rows only after dispatch; retry the same WorkSpec when
|
||||
work remains. Never represent the outbox as a dependency chain.
|
||||
5. Set a short result-retention period, call `pruneWork()` after idle batches,
|
||||
and expose counters: WorkSpec count, dependency depth, oldest completion,
|
||||
DB/WAL bytes and enqueue latency.
|
||||
|
||||
Pseudocode:
|
||||
|
||||
```java
|
||||
outbox.enqueue(classify(intent), serialize(intent));
|
||||
WorkRequest request = oneShot(GELDrainWorker.class);
|
||||
wm.enqueueUniqueWork("GEL_DRAIN", KEEP, request);
|
||||
// GELDrainWorker drains bounded persisted events FIFO, then succeeds
|
||||
```
|
||||
|
||||
Do not blindly replace every event with `REPLACE`: that could lose non-
|
||||
idempotent telemetry. The event classification is a release gate.
|
||||
|
||||
## Migration
|
||||
|
||||
At first patched start, if no RUNNING work exists and graph depth/DB size
|
||||
exceeds a conservative threshold, cancel only obsolete completed GEL work,
|
||||
prune, checkpoint, and retain any bounded outbox. Never delete the entire app
|
||||
data automatically. Log a privacy-safe migration result.
|
||||
|
||||
## Acceptance
|
||||
|
||||
10.000 alarms and service intents: dependency depth remains O(1), completed
|
||||
rows are pruned, DB remains under a vendor-defined cap (suggested <10 MiB),
|
||||
latest event latency remains seconds rather than minutes, reboot/power loss
|
||||
does not duplicate non-idempotent delivery.
|
||||
41
patches/gel-workmanager/PATCH.diff
Normal file
41
patches/gel-workmanager/PATCH.diff
Normal file
@@ -0,0 +1,41 @@
|
||||
--- a/src/org/droidtv/gel/gathering/GatheringService.java
|
||||
+++ b/src/org/droidtv/gel/gathering/GatheringService.java
|
||||
@@
|
||||
- OneTimeWorkRequest request = GELWorker.buildWorkRequest(intent);
|
||||
- workManager.enqueueUniqueWork("GEL_WORK",
|
||||
- ExistingWorkPolicy.APPEND_OR_REPLACE, request);
|
||||
+ eventOutbox.enqueue(EventRecord.from(intent), EventClassifier.forIntent(intent));
|
||||
+ OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(GELDrainWorker.class)
|
||||
+ .keepResultsForAtLeast(10, TimeUnit.MINUTES).build();
|
||||
+ workManager.enqueueUniqueWork("GEL_DRAIN", ExistingWorkPolicy.KEEP, request);
|
||||
|
||||
--- a/src/org/droidtv/gel/gathering/GELWorker.java
|
||||
+++ b/src/org/droidtv/gel/gathering/GELDrainWorker.java
|
||||
@@
|
||||
- Intent intent = intentFromWorkData(getInputData());
|
||||
- gatheringService.dispatch(intent);
|
||||
- return Result.success();
|
||||
+ long deadline = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(8);
|
||||
+ while (SystemClock.elapsedRealtime() < deadline) {
|
||||
+ EventRecord event = eventOutbox.peekOldest();
|
||||
+ if (event == null) return Result.success();
|
||||
+ gatheringService.dispatch(event.toIntent());
|
||||
+ eventOutbox.ack(event.sequence); // acknowledge only after dispatch
|
||||
+ }
|
||||
+ return Result.retry();
|
||||
|
||||
--- /dev/null
|
||||
+++ b/src/org/droidtv/gel/gathering/EventOutbox.sql
|
||||
@@
|
||||
+CREATE TABLE gel_event_outbox(
|
||||
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
+ action TEXT NOT NULL,
|
||||
+ payload BLOB NOT NULL,
|
||||
+ coalesce_key TEXT UNIQUE,
|
||||
+ created_ms INTEGER NOT NULL,
|
||||
+ attempts INTEGER NOT NULL DEFAULT 0
|
||||
+);
|
||||
|
||||
# Add a unique periodic GEL_DRAIN_WATCHDOG with KEEP. Bound the outbox to 4,096
|
||||
# rows / 8 MiB, retain a dropped-event metric, and coalesce only action classes
|
||||
# proven to be replaceable state/alarm snapshots.
|
||||
47
patches/gel-workmanager/REFERENCE-PATCH.diff
Normal file
47
patches/gel-workmanager/REFERENCE-PATCH.diff
Normal file
@@ -0,0 +1,47 @@
|
||||
--- a/src/org/droidtv/gel/gathering/GatheringService.java
|
||||
+++ b/src/org/droidtv/gel/gathering/GatheringService.java
|
||||
@@
|
||||
- OneTimeWorkRequest request = GELWorker.buildWorkRequest(intent);
|
||||
- workManager.enqueueUniqueWork("GEL_WORK",
|
||||
- ExistingWorkPolicy.APPEND_OR_REPLACE, request);
|
||||
+ eventOutbox.enqueue(EventRecord.from(intent), EventClassifier.forIntent(intent));
|
||||
+ OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(GELDrainWorker.class)
|
||||
+ .keepResultsForAtLeast(10, TimeUnit.MINUTES)
|
||||
+ .build();
|
||||
+ workManager.enqueueUniqueWork("GEL_DRAIN", ExistingWorkPolicy.KEEP, request);
|
||||
|
||||
--- a/src/org/droidtv/gel/gathering/GELWorker.java
|
||||
+++ b/src/org/droidtv/gel/gathering/GELDrainWorker.java
|
||||
@@
|
||||
- Intent intent = intentFromWorkData(getInputData());
|
||||
- gatheringService.dispatch(intent);
|
||||
- return Result.success();
|
||||
+ final long deadline = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(8);
|
||||
+ while (SystemClock.elapsedRealtime() < deadline) {
|
||||
+ EventRecord event = eventOutbox.peekOldest();
|
||||
+ if (event == null) {
|
||||
+ SystemClock.sleep(500);
|
||||
+ if (eventOutbox.peekOldest() == null) return Result.success();
|
||||
+ continue;
|
||||
+ }
|
||||
+ gatheringService.dispatch(event.toIntent());
|
||||
+ eventOutbox.ack(event.sequence); // only after successful dispatch
|
||||
+ }
|
||||
+ return Result.retry(); // same WorkSpec; no appended dependency
|
||||
|
||||
--- /dev/null
|
||||
+++ b/src/org/droidtv/gel/gathering/EventOutbox.sql
|
||||
@@
|
||||
+CREATE TABLE gel_event_outbox(
|
||||
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
+ action TEXT NOT NULL,
|
||||
+ payload BLOB NOT NULL,
|
||||
+ coalesce_key TEXT UNIQUE,
|
||||
+ created_ms INTEGER NOT NULL,
|
||||
+ attempts INTEGER NOT NULL DEFAULT 0
|
||||
+);
|
||||
|
||||
# Add one PeriodicWorkRequest named GEL_DRAIN_WATCHDOG with KEEP (15 minutes)
|
||||
# for crash/race recovery. Enforce a 4,096-row/8-MiB cap with an explicit
|
||||
# dropped-event counter. Only repetitive alarm/state snapshots may coalesce;
|
||||
# command/debug actions and events with delivery semantics may not.
|
||||
50
patches/gel-workmanager/ROOT-CAUSE.md
Normal file
50
patches/gel-workmanager/ROOT-CAUSE.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# GEL WorkManager root cause and exact patch boundary
|
||||
|
||||
## Confirmed callsite
|
||||
|
||||
APK: `diagnostics/raw/storage/gel.apk`, SHA-256
|
||||
`29A68E6B09700ED4CCDF312C0B99EEE0C3F67E329A537FE87ED4341A71D383D7`.
|
||||
Target: `classes.dex`,
|
||||
`Lorg/droidtv/gel/gathering/GatheringService;->onStartCommand(Intent,int,int)int`,
|
||||
code item `code_off=0x50bf0`, `registers=6`, `ins=4`, `outs=5`.
|
||||
|
||||
| DEX PC | Stock instruction |
|
||||
|---:|---|
|
||||
| `0x0000` | build `OneTimeWorkRequest` from the incoming Intent |
|
||||
| `0x000f` | load unique name `GEL_WORK` |
|
||||
| `0x0011` | load policy integer `4` |
|
||||
| `0x0012` | construct `WorkContinuationImpl(manager,name,policy,list)` |
|
||||
| `0x0015` | call `enqueue()` |
|
||||
|
||||
The embedded `EnqueueRunnable.run()` branches policy values 3 and 4 into the
|
||||
append path. It gathers current leaf WorkSpecs and inserts each as a prerequisite
|
||||
of the new request. For value 4, failed/cancelled leaves trigger replacement;
|
||||
otherwise it is the AndroidX `APPEND_OR_REPLACE` behavior. This directly
|
||||
explains the observed single-name dependency chain, expensive leaf traversal,
|
||||
delayed enqueue and 229,453,824-byte WorkDatabase.
|
||||
|
||||
## Patch decision
|
||||
|
||||
A one-instruction change from integer 4 to integer 2 would select KEEP and
|
||||
bound the WorkSpec graph, but it would silently discard every incoming Intent
|
||||
while another GEL job exists. The worker handles many unrelated actions and
|
||||
extras, so that binary micro-patch is **not release-safe**.
|
||||
|
||||
The release design is a bounded persistent FIFO/outbox plus one KEEP drain
|
||||
worker and one periodic KEEP watchdog. Repetitive sampling/state alarms may be
|
||||
coalesced; non-idempotent actions remain FIFO and are acknowledged only after
|
||||
dispatch. It preserves the current serial order without representing each
|
||||
event as a WorkManager dependency. Delivery is at-least-once after a crash, so
|
||||
event IDs or idempotent receivers are required.
|
||||
|
||||
## Validation
|
||||
|
||||
- `tools/verify_stock_callsite.py` asserts the APK hash and the five exact DEX
|
||||
instructions above.
|
||||
- `tools/test_bounded_outbox.py` covers 10,000 coalescible alarms, 10,000
|
||||
durable FIFO events, O(1) active WorkSpec, bounded depth, ordering and a crash
|
||||
between dispatch and acknowledgement.
|
||||
|
||||
This phase emits source/design and executable model artifacts, not a signed
|
||||
APK. A rebuilt GEL APK still needs the Philips platform private key and an
|
||||
AVB-integrated system image.
|
||||
27
patches/gel-workmanager/TEST-PLAN.md
Normal file
27
patches/gel-workmanager/TEST-PLAN.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# GEL bounded-outbox test plan
|
||||
|
||||
## Static gate
|
||||
|
||||
Run `tools/verify_stock_callsite.py`. It must match the golden APK hash and
|
||||
prove `GEL_WORK`, policy integer 4 (`APPEND_OR_REPLACE`), continuation
|
||||
construction, and enqueue in `GatheringService.onStartCommand`.
|
||||
|
||||
## Offline model gates
|
||||
|
||||
Run `tools/test_bounded_outbox.py` and require:
|
||||
|
||||
- 10,000 coalescible alarms keep bounded queue depth;
|
||||
- 10,000 non-coalescible commands preserve FIFO order;
|
||||
- active WorkManager graph remains O(1), never an APPEND chain;
|
||||
- queue caps and dropped-event telemetry are deterministic;
|
||||
- crash after dispatch/before acknowledgement produces at-least-once retry;
|
||||
- reboot watchdog resumes a non-empty outbox.
|
||||
|
||||
## Firmware integration gates
|
||||
|
||||
With a signed recoverable build, test every action family handled by
|
||||
`GELWorker.doWork`, airplane/network loss, process death at each durability
|
||||
boundary, standby/reboot, full disk, corrupt outbox, clock jumps, and a 30-day
|
||||
soak. Compare delivered action IDs and ordering to stock. Verify WorkDatabase
|
||||
rows plateau and old successful/cancelled specs prune. Do not ship the tempting
|
||||
single-byte `APPEND_OR_REPLACE` to `KEEP` edit: it can discard distinct Intents.
|
||||
102
patches/gel-workmanager/tools/test_bounded_outbox.py
Normal file
102
patches/gel-workmanager/tools/test_bounded_outbox.py
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Executable model tests for the proposed bounded GEL drain architecture."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
MAX_ROWS = 4096
|
||||
|
||||
|
||||
class Model:
|
||||
def __init__(self, path: Path):
|
||||
self.db = sqlite3.connect(path)
|
||||
self.db.executescript(
|
||||
"PRAGMA journal_mode=MEMORY; PRAGMA synchronous=OFF;"
|
||||
"CREATE TABLE outbox(seq INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" action TEXT NOT NULL, payload TEXT NOT NULL, coalesce_key TEXT UNIQUE);"
|
||||
"CREATE TABLE metrics(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||
"INSERT INTO metrics VALUES('dropped',0);"
|
||||
)
|
||||
self.worker_enqueued = False
|
||||
self.max_work_specs = 0
|
||||
self.max_outbox = 0
|
||||
|
||||
def enqueue(self, action: str, payload: dict, coalescible: bool) -> None:
|
||||
encoded = json.dumps(payload, sort_keys=True)
|
||||
key = action if coalescible else None
|
||||
if key is not None:
|
||||
self.db.execute(
|
||||
"INSERT INTO outbox(action,payload,coalesce_key) VALUES(?,?,?) "
|
||||
"ON CONFLICT(coalesce_key) DO UPDATE SET payload=excluded.payload",
|
||||
(action, encoded, key),
|
||||
)
|
||||
else:
|
||||
count = self.db.execute("SELECT count(*) FROM outbox").fetchone()[0]
|
||||
if count >= MAX_ROWS:
|
||||
self.db.execute("UPDATE metrics SET value=value+1 WHERE key='dropped'")
|
||||
self.db.commit()
|
||||
return
|
||||
self.db.execute(
|
||||
"INSERT INTO outbox(action,payload,coalesce_key) VALUES(?,?,NULL)",
|
||||
(action, encoded),
|
||||
)
|
||||
self.db.commit()
|
||||
self.worker_enqueued = True # enqueueUniqueWork(GEL_DRAIN, KEEP)
|
||||
self.max_work_specs = max(self.max_work_specs, int(self.worker_enqueued))
|
||||
self.max_outbox = max(self.max_outbox,
|
||||
self.db.execute("SELECT count(*) FROM outbox").fetchone()[0])
|
||||
|
||||
def drain(self, limit: int, crash_after_handle: bool = False) -> list[int]:
|
||||
handled = []
|
||||
selected = self.db.execute(
|
||||
"SELECT seq FROM outbox ORDER BY seq LIMIT ?", (limit,)
|
||||
).fetchall()
|
||||
for (seq,) in selected:
|
||||
handled.append(seq)
|
||||
if crash_after_handle:
|
||||
return handled # no ack: event remains for at-least-once retry
|
||||
self.db.execute("DELETE FROM outbox WHERE seq=?", (seq,))
|
||||
self.db.commit()
|
||||
if self.db.execute("SELECT count(*) FROM outbox").fetchone()[0] == 0:
|
||||
self.worker_enqueued = False
|
||||
return handled
|
||||
|
||||
def count(self) -> int:
|
||||
return self.db.execute("SELECT count(*) FROM outbox").fetchone()[0]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
m = Model(Path(tmp) / "outbox.db")
|
||||
for i in range(10_000):
|
||||
m.enqueue("GATHERING_ALARM_INTENT", {"tick": i}, coalescible=True)
|
||||
assert m.count() == 1 and m.max_work_specs == 1
|
||||
payload = json.loads(m.db.execute("SELECT payload FROM outbox").fetchone()[0])
|
||||
assert payload == {"tick": 9999}
|
||||
m.drain(100)
|
||||
assert m.count() == 0
|
||||
|
||||
seen = []
|
||||
for i in range(10_000):
|
||||
m.enqueue("DURABLE_EVENT", {"seq": i}, coalescible=False)
|
||||
if i % 32 == 31:
|
||||
seen.extend(m.drain(32))
|
||||
seen.extend(m.drain(MAX_ROWS))
|
||||
assert m.count() == 0 and m.max_outbox <= 32 and m.max_work_specs == 1
|
||||
assert seen == sorted(seen)
|
||||
|
||||
m.enqueue("DURABLE_EVENT", {"seq": "crash"}, coalescible=False)
|
||||
first = m.drain(1, crash_after_handle=True)
|
||||
assert m.count() == 1
|
||||
second = m.drain(1)
|
||||
assert first == second and m.count() == 0
|
||||
m.db.close()
|
||||
print("PASS: 10k coalescing, 10k FIFO, O(1) WorkSpec, bounded queue, crash retry")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
41
patches/gel-workmanager/tools/verify_stock_callsite.py
Normal file
41
patches/gel-workmanager/tools/verify_stock_callsite.py
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Assert the exact GEL unique-work callsite in the immutable stock APK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
HELPERS = ROOT / "diagnostics" / "deep-forensics" / "philips"
|
||||
sys.path.insert(0, str(HELPERS))
|
||||
from dex_disasm import Dex, fmt, iter_instructions # noqa: E402
|
||||
|
||||
APK = ROOT / "diagnostics" / "raw" / "storage" / "gel.apk"
|
||||
EXPECTED = "29a68e6b09700ed4ccdf312c0b99eee0c3f67e329a537fe87ed4341a71d383d7"
|
||||
METHOD = "Lorg/droidtv/gel/gathering/GatheringService;->onStartCommand(Landroid/content/Intent;II)I"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
assert hashlib.sha256(APK.read_bytes()).hexdigest() == EXPECTED
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
dex = Dex(zf.read("classes.dex"), str(APK) + "!/classes.dex")
|
||||
encoded = next(m for m in dex.encoded_methods if dex.methods[m.method_idx] == METHOD)
|
||||
instructions = {pc: fmt(dex, pc, op, units)
|
||||
for pc, op, units in iter_instructions(dex.data, encoded.code_off)}
|
||||
expected = {
|
||||
0x0000: "invoke-static {v3}, Lorg/droidtv/gel/gathering/GELWorker;->buildWorkRequest(Landroid/content/Intent;)Landroidx/work/OneTimeWorkRequest;",
|
||||
0x000F: "const-string v0, 'GEL_WORK'",
|
||||
0x0011: "const/4 v1, #4",
|
||||
0x0012: "invoke-direct {v5, v4, v0, v1, v3}, Landroidx/work/impl/WorkContinuationImpl;-><init>(Landroidx/work/impl/WorkManagerImpl;Ljava/lang/String;ILjava/util/List;)V",
|
||||
0x0015: "invoke-virtual {v5}, Landroidx/work/impl/WorkContinuationImpl;->enqueue()Landroidx/work/Operation;",
|
||||
}
|
||||
for pc, text in expected.items():
|
||||
assert instructions.get(pc) == text, (hex(pc), instructions.get(pc), text)
|
||||
print("PASS: stock GEL onStartCommand uses GEL_WORK with policy integer 4")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
48
patches/phase7/ART-PREOPT-ANALYSIS.md
Normal file
48
patches/phase7/ART-PREOPT-ANALYSIS.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Phase 7I – tvfw.jar ART/preopt analysis
|
||||
|
||||
## Inventory
|
||||
|
||||
| Artifact | Bytes | SHA-256 |
|
||||
|---|---:|---|
|
||||
| `/system/framework/tvfw.jar` | 3,275,657 | `a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad` |
|
||||
| `/system/framework/oat/arm/tvfw.odex` | 37,540 | `a4af432c54153c9325d92f338f93b2b0a6cfa6b618a5d2c31d587e93eceff531` |
|
||||
| `/system/framework/oat/arm/tvfw.vdex` | 51,572 | `abd0f4ce99404199332a779b195c53ca57d2238d0368910b0936cd55a8572509` |
|
||||
|
||||
No adjacent `.art` file was observed. `tvfw.jar` contains one DEX and is
|
||||
registered as the Java shared library `tvfw`.
|
||||
|
||||
## Boot criticality
|
||||
|
||||
Live environment inspection shows `tvfw` is in neither `BOOTCLASSPATH` nor
|
||||
`SYSTEMSERVERCLASSPATH`. It is therefore not itself part of the boot image and
|
||||
is not a direct system_server boot classpath input. This narrows, but does not
|
||||
remove, risk: numerous Philips TV applications load it through their
|
||||
PathClassLoader. Bugreport logs include a shared-library class-loader-context
|
||||
size mismatch involving `tvstrings`, `tvwidgets`, `tvfw` and `javax.obex`.
|
||||
|
||||
## Can ART regenerate it?
|
||||
|
||||
For a PackageManager-managed `/data/app` APK update, ART normally creates
|
||||
data-side compiled artifacts. `tvfw.jar` is different: it is system image
|
||||
shared-library content with stock ARM ODEX/VDEX and client class-loader
|
||||
contexts. `/data/dalvik-cache/arm` was inaccessible to shell, so a working
|
||||
fallback for a modified system JAR is not device-proven.
|
||||
|
||||
Possible build choices are:
|
||||
|
||||
1. regenerate matching ARM32 ODEX/VDEX and all affected class-loader contexts
|
||||
in the OEM build; or
|
||||
2. deliberately omit stale preopt outputs and validate boot-time/dexopt fallback
|
||||
in an otherwise correctly signed system image.
|
||||
|
||||
Blindly replacing the JAR while leaving stock ODEX/VDEX is unsafe. A checksum
|
||||
mismatch may cause rejection/recompilation, while a class-loader-context or API
|
||||
mismatch can crash many client processes. Neither outcome is a production plan.
|
||||
|
||||
## Conclusion
|
||||
|
||||
`tvfw.jar` is **not realistically deployable as an isolated file**. It needs a
|
||||
coherent system-image/OEM-build path, compatible clients/preopt, SELinux/file
|
||||
metadata and AVB. It is less likely than a bootclasspath JAR to stop
|
||||
system_server directly, but the blast radius across TV apps is high.
|
||||
|
||||
65
patches/phase7/AVB-TOPOLOGY.md
Normal file
65
patches/phase7/AVB-TOPOLOGY.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Phase 7H – AVB topology
|
||||
|
||||
## Verified boot state
|
||||
|
||||
- `ro.boot.avb_version=1.2`; vbmeta header reports AVB 1.0.
|
||||
- `ro.boot.vbmeta.device_state=locked`
|
||||
- `ro.boot.verifiedbootstate=green`
|
||||
- dm-verity mode: enforcing
|
||||
- active slot: `_a`
|
||||
- vbmeta device `179:8` = `/dev/mmcblk0p8` (`vbmeta_a`)
|
||||
- vbmeta digest: `66eae984496730f2339e5e5531f513645ce32263ac24b95cd80d4f787d701dc6`
|
||||
- digest algorithm SHA-256; reported vbmeta size 17,280 bytes
|
||||
|
||||
## Proven topology
|
||||
|
||||
```text
|
||||
locked boot ROM / bootloader trust root (key identity unread)
|
||||
-> vbmeta_a (p8, active; exact descriptors unread)
|
||||
-> system_a p15 -> dm-0 -> `/` system-as-root ext4, ro
|
||||
-> vendor_a p35 -> dm-1 -> `/vendor` ext4, ro
|
||||
-> linux_rootfs_a p17 -> dm-2 -> SquashFS
|
||||
-> 3rd_a p19 -> dm-3 -> SquashFS
|
||||
-> factory_a p39 -> AVB SquashFS when mounted
|
||||
-> boot/dtbo and other OTA partitions: boot-chain protection likely,
|
||||
but exact vbmeta descriptors/chains not readable
|
||||
```
|
||||
|
||||
The fstab marks `system`, `vendor`, `linux_rootfs`, `3rd` and `factory` with
|
||||
`avb,slotselect`. The exact active system dm table is a SHA-1 4-KiB hashtree
|
||||
with 602,165 data blocks, root hash
|
||||
`3f16afd...`, salt `d468...`, FEC and `restart_on_corruption`. Vendor's dm-verity
|
||||
mapping is confirmed, but its full descriptor/table was not captured.
|
||||
|
||||
## Partition status
|
||||
|
||||
| Logical role | Active physical partition | AVB evidence | Exact descriptor/key/rollback index |
|
||||
|---|---|---|---|
|
||||
| vbmeta | p8 `vbmeta_a` | locked green root metadata | raw content inaccessible |
|
||||
| system/root | p15 `system_a`, dm-0 | fstab AVB + live dm-verity | partial dm table known |
|
||||
| vendor | p35 `vendor_a`, dm-1 | fstab AVB + live dm-verity | unknown |
|
||||
| linux_rootfs | p17, dm-2 | fstab AVB + live mapping | unknown |
|
||||
| 3rd | p19, dm-3 | fstab AVB + live mapping | unknown |
|
||||
| factory | p39 (B: p16) | fstab AVB/slotselect | not mounted; unknown |
|
||||
| boot / dtbo | A/B raw partitions | locked green boot strongly implies authentication | descriptor/chaining unknown |
|
||||
| odm | p36/p57, not mounted in normal inventory | no AVB fstab proof | unknown |
|
||||
| product/system_ext | directories within system image | protected as system content | no separate partition |
|
||||
| recovery/super | absent | n/a | n/a |
|
||||
|
||||
There are no named chained-vbmeta partitions and no dynamic `super` partition.
|
||||
That is not proof that `vbmeta_a` contains no chain descriptor. Without reading
|
||||
and parsing raw vbmeta, public-key digest, descriptor algorithms, chained vbmeta
|
||||
and rollback indexes remain unknown.
|
||||
|
||||
## Consequence for patches
|
||||
|
||||
Changing `/system` content invalidates the hashtree. A bootable image would need
|
||||
a coherent new filesystem, hashtree/FEC/footer and vbmeta accepted by the locked
|
||||
boot chain. Disabling verification is forbidden and unnecessary for the
|
||||
legitimate goal. A correctly platform-signed `/data/app` update leaves AVB
|
||||
untouched.
|
||||
|
||||
References: Android Verified Boot
|
||||
<https://source.android.com/docs/security/features/verifiedboot> and AVB source
|
||||
<https://android.googlesource.com/platform/external/avb/>.
|
||||
|
||||
43
patches/phase7/COLD-BOOT-RECOVERY-MATRIX.md
Normal file
43
patches/phase7/COLD-BOOT-RECOVERY-MATRIX.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Phase 7G – Cold-boot recovery matrix
|
||||
|
||||
Ratings are conditional on the failure mode stated. “Likely” is not a tested
|
||||
cold-boot guarantee. No recovery command was executed.
|
||||
|
||||
| Scenario | Android boot / ADB | Primary rollback | Stock APK fallback | USB/full restore | Rating |
|
||||
|---|---|---|---|---|---|
|
||||
| A. bad Channels `/data/app` update | likely boots; package/provider may crash; ADB likely | `cmd package uninstall-system-updates org.droidtv.channels` if ADB works | yes by PM; data-schema compatibility unproven | official package not locally available | **RECOVERABLE**, conditional; cold boot untested |
|
||||
| B. bad GEL `/data/app` update | likely boots; GEL/candeebug process may loop | uninstall system update | yes; WorkManager DB rollback risk | same | **RECOVERABLE**, conditional |
|
||||
| C. bad TPTvService `/data/app` update | persistent/core service may loop and degrade boot/UI; ADB uncertain | uninstall update if ADB reaches stable shell | yes mechanically; migrated data unknown | same | **RECOVERABLE WITH RECOVERY / UNKNOWN** until tested |
|
||||
| D. bad `tvfw.jar` in system image | system_server not directly bootclasspath-dependent, but many TV clients can fail | A/B slot fallback if truly functional, else OEM full firmware | no PM fallback; JAR is system image content | needed if both slot/fallback fail | **HIGH RISK** |
|
||||
| E. system_server does not boot | UI absent; adbd availability unknown | inactive-slot fallback/boot control | n/a | OEM recovery required if fallback fails | **HIGH RISK / UNKNOWN** |
|
||||
| F. Android UI does not boot but system/adbd do | ADB may work | remove updated app or revert slot | for APK update yes | possible only with official file/procedure | **RECOVERABLE** if ADB proven in failure |
|
||||
| G. ADB does not start | no normal-shell rollback | boot-control automatic rollback only | cannot trigger PM rollback | requires known recovery/USB full firmware | **RECOVERABLE WITH USB FIRMWARE**, currently **UNKNOWN** |
|
||||
|
||||
## Safe mode and recovery deletion
|
||||
|
||||
- A model-specific safe-mode entry and behavior are not documented in local
|
||||
resources; do not rely on generic Android TV instructions.
|
||||
- Direct deletion of `/data/app` in recovery is not documented or approved.
|
||||
The proper normal-boot path is PackageManager's `uninstall-system-updates`.
|
||||
- A factory reset would remove data-side updates but also destroys user data;
|
||||
it is a last resort, not a validated rollback plan.
|
||||
|
||||
## A/B fallback strength
|
||||
|
||||
Static A/B partitions, Boot-Control HAL, UpdateEngine and `update_verifier`
|
||||
strongly support automatic rollback if the new slot never becomes successful.
|
||||
However `bootctl` is unavailable and current bootable/successful/retry counters
|
||||
could not be read. An end-to-end failed-slot test was neither authorized nor
|
||||
performed. Treat A/B recovery as **architecturally likely**, not proven for a
|
||||
manually modified image.
|
||||
|
||||
## Deployment gate
|
||||
|
||||
Before the first Channels live update:
|
||||
|
||||
1. OEM/platform-signed candidate.
|
||||
2. Equivalent-device install, reboot, crash and uninstall-update drill.
|
||||
3. Proved data rollback or restorable backup.
|
||||
4. Official full firmware and exact 43PUS7394/12 USB recovery procedure.
|
||||
5. Stable ADB access after intentional candidate failure in the lab.
|
||||
|
||||
25
patches/phase7/DEPLOYMENT-MATRIX.md
Normal file
25
patches/phase7/DEPLOYMENT-MATRIX.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Phase 7M – Final deployment matrix
|
||||
|
||||
| Path | Targets | Root | Signer / AVB | Cold-boot risk | Rollback / persistence | OTA compatibility | Rating |
|
||||
|---|---|---:|---|---|---|---|---|
|
||||
| 1. Philips-signed updated-system APK | Channels, GEL, TPTvService separately | no | exact Platform signer; no AVB change | low–medium for Channels/GEL, high for persistent TPTvService; must be lab-tested | persistent `/data/app`; PM fallback to stock `/system` APK; data rollback unproven | OEM OTA may replace/remove/supersede; test | **BEST** for first Channels candidate when gates pass |
|
||||
| 2. Official vendor firmware with patch | all APKs, tvfw, policy | no user root | OEM Platform, payload and AVB signing | lowest achievable with OEM QA/A/B recovery | vendor-supported full/incremental rollback | native | **BEST PRODUCTION** |
|
||||
| 3. Temporary runtime instrumentation | behavior prototype only | instrumentation/root authority likely | no durable signer/AVB change | process crash; normally reboot clears | temporary and fragile | not relevant | **TEST ONLY** |
|
||||
| 4. Root + bind mount | APK/JAR prototype, not coherent release | yes every boot | cannot satisfy shared UID; underlying AVB intact but ART/labels remain | high; boot-service ordering and client crash risk | nonstandard mount removal/reboot | conflicts with OTA assumptions | **HIGH RISK / NO-GO** |
|
||||
| 5. Modified system image | all system targets | build/flash authority | Platform signer plus accepted AVB/vbmeta required | very high without proven A/B/USB recovery | A/B only if accepted and tested | must regenerate payload/meta | **HIGH RISK / NO-GO today** |
|
||||
| 6. Full modified firmware | all components/partitions | OEM process | complete Platform/payload/AVB/container keys | high for independent build, low only under OEM QA | full A/B/vendor recovery possible if supplied | becomes the OTA | **GOOD only as official OEM build; otherwise NO-GO** |
|
||||
|
||||
## First live candidate
|
||||
|
||||
BUG-001 as a Platform-signed Channels updated-system APK remains technically the
|
||||
best first candidate because `/system` stays intact and PackageManager has a
|
||||
documented fallback. It is **not yet authorized or safe to install**. Missing:
|
||||
|
||||
- OEM Platform signature/lineage;
|
||||
- real data migration and uninstall-update rollback test;
|
||||
- cold boot/ADB failure drill on equivalent hardware;
|
||||
- official full firmware and model-specific USB recovery procedure.
|
||||
|
||||
Until all four exist, the production decision remains **NO-GO** and the manual
|
||||
read-only EPG health check is the operational fallback.
|
||||
|
||||
66
patches/phase7/OTA-COMPONENT-MAP.md
Normal file
66
patches/phase7/OTA-COMPONENT-MAP.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Phase 7D – Philips/TPVision OTA component map
|
||||
|
||||
## Proven update path
|
||||
|
||||
```text
|
||||
USB `/upgrades/autorun*.upg`
|
||||
-> org.droidtv.settings AutoRunUpgrade / SilentUpgradeService (UID1000)
|
||||
-> product/version filter (`TPM191E_R`, current SW version)
|
||||
-> ZIP parse
|
||||
├─ payload.bin
|
||||
├─ payload_properties.txt
|
||||
└─ META-INF/updateinfo/upg.xml
|
||||
-> android.os.UpdateEngine.applyPayload(URL, offset, size, properties)
|
||||
-> root update_engine daemon
|
||||
-> inactive A/B partitions
|
||||
-> boot-control / update_verifier after reboot
|
||||
```
|
||||
|
||||
This graph is reconstructed from the installed Settings DEX, init/fstab files,
|
||||
live services and bugreport logs. No update was initiated.
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Evidence / path | Role | Confidence |
|
||||
|---|---|---|---|
|
||||
| `org.droidtv.settings` | `/system/priv-app/settings/settings.apk` | Software-update UI and OEM orchestration | confirmed |
|
||||
| `SilentUpgradeService` | Settings manifest/DEX/logs | watches USB/staging; registers mounted-volume receiver | confirmed |
|
||||
| `AutoRunUpgrade` | Settings DEX `code_off=0x3b6ba0` | scans `/upgrades`, pattern `[aA]utorun.*.upg`, product/version checks | confirmed |
|
||||
| `UpdateParser.parse(File)` | Settings DEX `code_off=0x3c01ec` | opens `.upg` with `ZipFile`; locates payload and properties; computes payload offset/size | confirmed |
|
||||
| `UpdateUtils.installUpdate` | Settings DEX `code_off=0x3c71e4` | binds Android `UpdateEngine`, calls `applyPayload` | confirmed |
|
||||
| `UpdateUtils.getUpgXml` | Settings DEX `code_off=0x3c2e88` | extracts `META-INF/updateinfo/upg.xml` to staging | confirmed |
|
||||
| `android.os.UpdateEngineService` | live service list | Binder API for payload update | confirmed |
|
||||
| `/system/bin/update_engine` | init/process evidence | root A/B payload writer/verifier | confirmed |
|
||||
| `update_verifier` | init/fstab inventory | verifies post-boot slot and marks successful through boot control | confirmed architecture |
|
||||
| `android.hardware.boot@1.0` | vendor init/HAL inventory | A/B slot state/control | confirmed service definition; status query failed |
|
||||
| `/data/ota_package` | `/dev/mmcblk0p32`, label `upgrade`, ~1,352 MiB | update staging/history (`last_install`, `upg.xml`, payload metadata) | confirmed; shell contents inaccessible |
|
||||
| `/data/vendor/user_setting/rw/upgrade` | DEX strings / prior private-state report | OEM upgrade state/history | confirmed path reference |
|
||||
| `org.droidtv.oad` | `/system/priv-app/oad/oad.apk` | broadcast/OAD TV firmware feed integration, writes `autorun.upg` staging | confirmed; not the primary USB parser |
|
||||
| `com.easy.oad` | `/system/app/btota/btota.apk` | Bluetooth/remote-device OTA | confirmed distinct role |
|
||||
| `org.droidtv.usbbreakin` | `/system/app/USBService/USBService.apk` | USB service/trigger surface | confirmed installed; exact firmware-writer role not proven |
|
||||
| `uncrypt`, BCB services | `/system/etc/init/uncrypt.rc` | setup/clear bootloader-control-block paths used by Android recovery flows | confirmed definitions; not invoked |
|
||||
| RecoverySystem/system_update services | live service list | Android framework recovery/update coordination | confirmed present |
|
||||
|
||||
## Metadata and state names found
|
||||
|
||||
The installed update code references `/data/ota_package/build.prop`,
|
||||
`installstatus`, `last_install`, version/history files,
|
||||
`persist.odm.tpv.sys.upg_filename`, `persist.odm.tpv.sys.upg_source`,
|
||||
`reboot-ab-update` and native slot APIs. Logs show the Settings service starting
|
||||
and observing USB mounts; they do not show a staged current update.
|
||||
|
||||
## What was not proven
|
||||
|
||||
- No official `.upg` file is present locally, so outer-container signature,
|
||||
exact payload metadata and Philips private key identities remain unknown.
|
||||
- No call to `RecoverySystem.verifyPackage` was found on the current Settings
|
||||
A/B path. This does not prove the outer ZIP is unsigned; it proves only that
|
||||
the inspected path hands the inner payload/properties to UpdateEngine.
|
||||
- Factory/emergency USB modes, key combinations and recovery ADB were not found
|
||||
in local resources.
|
||||
- `upgrade_loader`, `upgrade.pkg`, Android non-A/B `update.zip` and sideload are
|
||||
not established as valid paths for this build.
|
||||
|
||||
Android's A/B OTA model writes the inactive slot and uses boot-control rollback:
|
||||
<https://source.android.com/docs/core/ota/ab>.
|
||||
|
||||
72
patches/phase7/PHILIPS-UPDATE-FORMAT.md
Normal file
72
patches/phase7/PHILIPS-UPDATE-FORMAT.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Phase 7E – Philips update format
|
||||
|
||||
## Local artifact search
|
||||
|
||||
No official Philips/TPVision firmware package, `payload.bin`, system image or
|
||||
`.upg` was found in the workspace. The only ZIPs are diagnostic/Codex bundles
|
||||
and a bugreport attachment. Therefore no vendor package was reverse-engineered
|
||||
or reconstructed, and no third-party image was used.
|
||||
|
||||
## Format accepted by the installed Settings path
|
||||
|
||||
The current `TPM191E_R` Settings DEX proves a **ZIP-compatible `.upg` outer
|
||||
container** with at least:
|
||||
|
||||
```text
|
||||
autorun*.upg (ZIP opened with java.util.zip.ZipFile)
|
||||
├─ payload.bin (A/B OTA payload)
|
||||
├─ payload_properties.txt (hash/size/signature properties for UpdateEngine)
|
||||
└─ META-INF/updateinfo/upg.xml (TPVision product/version metadata)
|
||||
```
|
||||
|
||||
`UpdateParser.parse(File)` locates the payload entry and calculates its URL,
|
||||
byte offset and compressed size. `UpdateUtils.installUpdate` passes those plus
|
||||
the property lines to `UpdateEngine.applyPayload`. This is consistent with an
|
||||
Android A/B payload embedded in a vendor wrapper.
|
||||
|
||||
`upg.xml` parsing references these tags:
|
||||
|
||||
- `productid`
|
||||
- `softwareversion`
|
||||
- `buildid`, `buildtype`
|
||||
- `dateofthebuild`, `timeofthebuild`
|
||||
- `tagofsource`, `typeofupg`, `creator`, `description`
|
||||
- `sourceswver`
|
||||
|
||||
The USB scanner filters the filename with `[aA]utorun.*.upg` and compares
|
||||
product/version data against `ro.odm.tpv.product.id=TPM191E_R` and
|
||||
`ro.odm.tpvision.product.swver`.
|
||||
|
||||
## Verification boundary
|
||||
|
||||
Confirmed:
|
||||
|
||||
- UpdateEngine receives `payload_properties.txt` and the binary payload.
|
||||
- Locked/green verified boot protects booting the resulting slot.
|
||||
- Device-side OTA and APK signing use different certificates.
|
||||
|
||||
Unknown until an official package is available:
|
||||
|
||||
- outer ZIP comment/JAR signature/custom signature or checksum;
|
||||
- payload manifest contents, partition list and payload signing certificate;
|
||||
- whether full and incremental packages differ in additional entries;
|
||||
- precise anti-rollback/source-version enforcement;
|
||||
- AVB public keys/descriptors shipped inside the package;
|
||||
- vendor container padding/alignment and any post-payload footer.
|
||||
|
||||
Absence of `RecoverySystem.verifyPackage` in the inspected Settings code must
|
||||
not be interpreted as permission to omit signatures. UpdateEngine performs its
|
||||
own payload verification and the final images must pass AVB.
|
||||
|
||||
## Official file required for completion
|
||||
|
||||
Obtain directly from Philips/TPVision an unmodified full USB recovery/update
|
||||
file named `autorun*.upg` for **43PUS7394/12, platform `TPM191E_R`**, preferably
|
||||
the exact current `TPM191E_R.211.000.103.000` or a newer explicitly compatible
|
||||
release. A full recovery image is preferable to an incremental OTA because it
|
||||
allows offline structure comparison and independent restoration. Record the
|
||||
vendor URL and SHA-256 before analysis.
|
||||
|
||||
No reconstructed container should be offered to the television without the OEM
|
||||
payload/AVB signatures and a proven recovery path.
|
||||
|
||||
50
patches/phase7/RECOVERY-INVENTORY.md
Normal file
50
patches/phase7/RECOVERY-INVENTORY.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Phase 7F – Recovery inventory
|
||||
|
||||
## Summary
|
||||
|
||||
This build has A/B update infrastructure and Android recovery coordination, but
|
||||
the exact user-accessible cold-boot recovery method is **not proven**. There is
|
||||
no separate recovery partition in the complete 63-partition inventory.
|
||||
|
||||
## Device-specific evidence
|
||||
|
||||
| Item | Finding | Classification |
|
||||
|---|---|---|
|
||||
| Separate `recovery` partition | absent from full p1–p63 layout; fstab recovery line commented | disproved |
|
||||
| A/B layout | static A/B, slot A active, `slotselect`; no `super` | confirmed |
|
||||
| Recovery-in-boot possibility | standard A/B design can place recovery ramdisk in boot | plausible, content not inspected |
|
||||
| `recovery` executable | not present under `/system/bin` in normal boot | confirmed absence at that path |
|
||||
| `fastbootd` / `bootctl` | not present under `/system/bin` | confirmed absence at that path |
|
||||
| Recovery framework service | `recovery` and `system_update` services present | confirmed |
|
||||
| BCB helpers | `uncrypt`, `setup-bcb`, `clear-bcb` definitions in `uncrypt.rc` | confirmed definitions |
|
||||
| Recovery persist/refresh | AOSP init scripts and helpers present | confirmed |
|
||||
| OTA cert store | `/system/etc/security/otacerts.zip` | confirmed |
|
||||
| Vendor recovery hook | `vendor_flash_recovery.rc` references `/vendor/bin/install-recovery.sh` | definition confirmed; binary absent in mounted vendor |
|
||||
| Factory image | A/B `factory` SquashFS, AVB/slotselect, not mounted normally | confirmed partition/path; contents unknown |
|
||||
| Factory service | `/factory/bin/factory_service`, oneshot, `vold` domain, `/tmp/factory` | init definition only; runtime/entry unknown |
|
||||
| USB `.upg` updater | Settings scans `/upgrades/autorun*.upg` | confirmed normal-Android USB OTA path |
|
||||
| Recovery sideload | no local evidence | unknown |
|
||||
| ADB in recovery | no local evidence | unknown |
|
||||
| Physical key combination | no local resource found | unknown |
|
||||
| Emergency upgrade | no confirmed trigger/protocol | unknown |
|
||||
| Public Fastboot/unlock | no evidence; device locked/green | unknown/unavailable in normal boot |
|
||||
| UART | `console=ttyMT0,115200n1`; Android console is UID shell/domain shell; `forbid_uart0=1` | configuration confirmed, pins/access/root unknown |
|
||||
|
||||
## What recovery can likely write
|
||||
|
||||
The A/B OTA partition list advertises `boot,system,vbmeta,vendor,dtbo,
|
||||
linux_rootfs,factory,3rd,tzbp,pq,aq,adsp,tpv_misc,uboot,uboot_env,ubootB,
|
||||
uboot_envB,dolby,r20,r21,3dlut`. This proves intended OTA coverage, not that a
|
||||
user-accessible recovery shell can freely write them. The trusted UpdateEngine
|
||||
path writes the inactive slot according to a signed payload.
|
||||
|
||||
## Practical conclusion
|
||||
|
||||
- If normal Android and ADB boot, a bad updated-system APK may be removable via
|
||||
PackageManager's system-update rollback.
|
||||
- If Android/ADB do not boot, neither a key-based recovery UI nor USB emergency
|
||||
restore has yet been device-proven. Do not perform a live system/framework
|
||||
patch on that assumption.
|
||||
- An OEM full `autorun*.upg` plus documented model-specific key/USB recovery
|
||||
procedure is required before high-risk deployment.
|
||||
|
||||
76
patches/phase7/SIGNER-MATRIX.md
Normal file
76
patches/phase7/SIGNER-MATRIX.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Phase 7A – Signing inventory
|
||||
|
||||
Stand: 2026-08-26, Gerät `TPM191E_R.211.000.103.000`. Die APK-Hashes
|
||||
wurden lokal berechnet und gegen die read-only Live-Pfade geprüft. Package-
|
||||
Metadaten stammen aus `dumpsys package`; Zertifikate aus den APK-Signaturdaten.
|
||||
|
||||
## Ergebnis
|
||||
|
||||
Die drei Patchziele Channels, GEL und TPTvService sowie Androids
|
||||
`framework-res.apk`, EUM, Settings, OAD, USBService und SettingsProxy sind mit
|
||||
demselben kryptografischen **Platform Signer** signiert. Der PackageManager
|
||||
meldet für sie dieselbe Signer-ID `[476d2253]` und Signing-Version 3. Das
|
||||
OTA-/Recovery-Zertifikat in `/system/etc/security/otacerts.zip` ist ein zweites,
|
||||
verschiedenes Zertifikat. Beide privaten Schlüssel fehlen.
|
||||
|
||||
## APK-Matrix
|
||||
|
||||
`Platform` bezeichnet den unten vollständig ausgewiesenen Zertifikat-Fingerprint.
|
||||
Alle geprüften APKs tragen JAR/v1-Signaturen sowie APK-Signing-Block v2 und v3.
|
||||
Eine v4-`.idsig`-Sidecar-Datei wurde an keinem Systempfad beobachtet; v4 ist
|
||||
daher **nicht belegt** (nicht gleichbedeutend mit einer eingebetteten v4-
|
||||
Signatur). `Priv.` bedeutet Installation unter `priv-app`.
|
||||
|
||||
| Package / Rolle | Aktiver APK-Pfad | APK SHA-256 | Signer | v1/v2/v3/v4 | UID / sharedUser | Priv. | Relevante Vertrauensrechte |
|
||||
|---|---|---|---|---|---|---:|---|
|
||||
| `android` | `/system/framework/framework-res.apk` | `d50a2ad1caa5c6e95ee1540d798150f40246f20c90d3a0d9371c58b541a27508` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | n/a | definiert Plattform-/Signaturgrenze |
|
||||
| `org.droidtv.channels` | `/system/priv-app/channels/channels.apk` | `3a44bc648624f0cc9cd9feb4cfc3fd33f41323597718e4fccfc25fe35504d34f` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | ja | EPG/TV-Provider, Shared-UID-Union, privileged |
|
||||
| `org.droidtv.gel` | `/system/app/gel/gel.apk` | `29a68e6b09700ed4ccdf312c0b99eee0c3f67e329a537fe87ed4341a71d383d7` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | nein | Shared-UID-Union, interne Philips-Schnittstellen |
|
||||
| `org.droidtv.tv.tptvservice` | `/system/app/TPTvService/TPTvService.apk` | `75f492ee89906c8c267d685d2b7d0ce02bb09f3a6b4cc8456f8b096b9e441ecd` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | nein | persistent/coreApp, Shared-UID-Union |
|
||||
| `org.droidtv.eum` | `/system/priv-app/eum/eum.apk` | `3b27806ac60b946c47ccac1be7edad46938cae10c43c3f2e1f3ef4f9d2d30b98` | Platform | ja/ja/ja/nicht belegt | 10119 / kein Shared UID | ja | privileged/signature Plattformrechte |
|
||||
| `org.droidtv.settings` | `/system/priv-app/settings/settings.apk` | `bf63a78bae8259032144598927c2fe8af19005662de2cc0053db2a1f7918a348` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | ja | SilentUpgrade/TVUpdate, persistent, privileged |
|
||||
| `org.droidtv.oad` | `/system/priv-app/oad/oad.apk` | `3d4ba64ce6d3e4b6c8c65ff5411b4058aeb239c95ac15dd806e15c1d991842f9` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | ja | OAD/TV-Upgrade-Integration |
|
||||
| `com.easy.oad` | `/system/app/btota/btota.apk` | `8fcd51ba44e1f3188c06d01615dde274c652f8caacfd14c3ae7db16a266e4101` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | nein | Bluetooth-/Remote-OTA, nicht Hauptfirmware |
|
||||
| `org.droidtv.usbbreakin` | `/system/app/USBService/USBService.apk` | `a34edfa8345ba4b7513ef648a7c0ffaf40945359e7bb951fbbef3c98d6c612bb` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | nein | USB-Service, persistent |
|
||||
| `org.droidtv.common.accesshiddenapi` | `/system/app/settingsproxyservice/settingsproxyservice.apk` | `e0e966babadd77f6fc8d71af05cd58c74e5a7a16a4ad6abd664b5625c0508b7e` | Platform | ja/ja/ja/nicht belegt | 1000 / `android.uid.system` | nein | Hidden-API-/Settings-Proxy |
|
||||
|
||||
## Platform-Signer-Zertifikat
|
||||
|
||||
- Subject/Issuer (self-signed): `EMAILADDRESS=android@android.com, CN=Android, OU=Android, O=Android, L=Mountain View, ST=California, C=US`
|
||||
- Serial: `9e71a5645c4fc22f`
|
||||
- SHA-256: `D8:FF:E0:AF:40:4E:AB:8E:45:5A:00:74:D4:17:B0:DA:12:D0:67:26:E5:05:B0:2B:7F:DB:18:31:29:C8:90:68`
|
||||
- SHA-1: `43:80:01:76:AC:53:BF:E9:FD:33:4A:EB:86:1D:40:A1:2A:00:AF:C0`
|
||||
- Schlüssel/Signatur: RSA-2048, SHA1withRSA, X.509 v3
|
||||
- Gültigkeit: 2015-08-10 05:01:32 CEST bis 2042-12-26 04:01:32 CET
|
||||
|
||||
Der generische Subject-Text `Android` ist kein Identitätsbeweis. Entscheidend
|
||||
ist der vollständige kryptografische Fingerprint beziehungsweise eine vom
|
||||
PackageManager akzeptierte Signer-Lineage. Ein gleichnamiges neues Zertifikat
|
||||
ist inkompatibel.
|
||||
|
||||
## OTA-/Recovery-Zertifikat
|
||||
|
||||
`/system/etc/security/otacerts.zip` enthält `releasekey.x509.pem` mit SHA-256
|
||||
`E0:18:AB:2F:96:83:8E:B4:B7:B9:1A:D1:12:0B:C6:74:90:F7:E1:01:0F:D4:0C:1A:E5:1E:C4:F8:B1:2A:A0:F8`.
|
||||
Es ist **nicht** das APK-Platform-Zertifikat. Details stehen in
|
||||
`certificates.txt`. Das Zertifikat allein erlaubt weder APK- noch OTA-Signing;
|
||||
der jeweilige private Schlüssel ist erforderlich und wurde nicht gesucht.
|
||||
|
||||
## tvfw.jar
|
||||
|
||||
`/system/framework/tvfw.jar` (SHA-256
|
||||
`a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad`)
|
||||
ist kein APK und besitzt keine JAR-Signatur. Seine Vertrauensgrenze entsteht
|
||||
durch die AVB-geschützte Systempartition, SELinux/Dateimetadaten, den
|
||||
registrierten Java-Shared-Library-Pfad und ART-Preopt. Die oben gelisteten
|
||||
Platform-signed Philips-Clients laden die Library; ein unsigniertes JAR ist
|
||||
deshalb nicht frei austauschbar.
|
||||
|
||||
## Grenzen
|
||||
|
||||
- Kein privater Schlüssel wurde gesucht, extrahiert oder rekonstruiert.
|
||||
- Eine PM-Signer-ID ist geräteintern; die X.509-SHA-256 ist der portable Beleg.
|
||||
- Nicht jede einzelne Permission aller 58 UID1000-Pakete wird hier einem Paket
|
||||
zugerechnet: Shared-UID-Prozesse wirken über die gemeinsame UID- und
|
||||
Signaturgrenze. Die vollständige Beziehung steht in `TRUST-GRAPH.md`.
|
||||
|
||||
35
patches/phase7/SIGNING-OPTIONS.md
Normal file
35
patches/phase7/SIGNING-OPTIONS.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Phase 7K – Signed-update reality
|
||||
|
||||
## Direct answer
|
||||
|
||||
No legitimate local method currently exists to produce an APK, OTA payload or
|
||||
AVB image that this locked television will accept. Public certificates were
|
||||
found; the private keys and OEM signing service were not. Public X.509 data
|
||||
cannot generate signatures.
|
||||
|
||||
| Option | What it would provide | Feasibility | Result |
|
||||
|---|---|---|---|
|
||||
| A. Official Philips/TPVision build and signing process | coherent source build, Platform-signed APK, possibly OTA/AVB integration | technically ideal; no access yet | **BEST / REQUEST** |
|
||||
| B. Manufacturer ships its own patched update | OEM controls source, tests, Platform/OTA/AVB keys and support recovery | realistic vendor remediation path | **BEST PRODUCTION** |
|
||||
| C. Manufacturer signs a supplied patch/artifact | could enable `/data/app` APK if OEM accepts code review and exact build inputs | possible organizationally; no service known | **GOOD**, requires OEM |
|
||||
| D. Official firmware already includes fix | avoids third-party modification entirely | no newer verified package inspected | **GOOD**, verify release |
|
||||
| E. Local signing | arbitrary new key is rejected by shared UID/PackageManager/AVB | cryptographically incompatible | **NO-GO** |
|
||||
|
||||
## Keys are separate
|
||||
|
||||
- APK Platform signer: SHA-256 `D8:FF:E0:AF:...:90:68`.
|
||||
- OTA/recovery certificate: SHA-256 `E0:18:AB:2F:...:A0:F8`.
|
||||
- AVB root/public-key identity: unread/unknown.
|
||||
- Payload signing key: unknown until an official `.upg` is analyzed.
|
||||
|
||||
One key does not substitute for another. Even OEM-signed APKs do not authorize
|
||||
a modified system image, and an OTA certificate does not satisfy the APK
|
||||
shared-UID check.
|
||||
|
||||
## Ethical/security boundary
|
||||
|
||||
No leaked keys, private-key extraction, forged certificate, PackageManager or
|
||||
recovery check patch, AVB disablement or signature-verification bypass is part
|
||||
of this plan. The only acceptable signing paths terminate in Philips/TPVision
|
||||
or another explicitly authorized owner of the device trust keys.
|
||||
|
||||
44
patches/phase7/SYSTEMIMAGE-BUILD-ANALYSIS.md
Normal file
44
patches/phase7/SYSTEMIMAGE-BUILD-ANALYSIS.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Phase 7J – Modified system image feasibility
|
||||
|
||||
## REPACK POSSIBLE
|
||||
|
||||
The system partitions are fixed-size (~2,390 MiB) ext4 images in a static A/B,
|
||||
system-as-root layout. With a complete source/target-files package, a build
|
||||
pipeline could theoretically:
|
||||
|
||||
1. stage the full root/system tree without mutating the baseline;
|
||||
2. integrate APK/JAR changes and compatible resources/manifests;
|
||||
3. regenerate ARM32 dexpreopt ODEX/VDEX and class-loader contexts;
|
||||
4. apply exact UID/GID/mode/capability metadata (`fs_config`);
|
||||
5. apply complete SELinux labels/xattrs and policy-compatible file contexts;
|
||||
6. create deterministic fixed-size ext4 images (sparse conversion only as a
|
||||
transport representation);
|
||||
7. regenerate dm-verity hashtree, FEC and AVB footer/descriptors;
|
||||
8. build/sign vbmeta and the Android A/B payload/container;
|
||||
9. validate inactive-slot install, boot, mark-successful and rollback.
|
||||
|
||||
This proves only that the format is technically buildable.
|
||||
|
||||
## BOOTABLE/TRUSTED – current status
|
||||
|
||||
**NO-GO.** Missing inputs include:
|
||||
|
||||
- exact Philips build/target-files tree and proprietary integrations;
|
||||
- private Platform signer or an OEM signing service for the APK targets;
|
||||
- AVB signing keys/root-of-trust compatibility and rollback indexes;
|
||||
- official OTA payload/container signing process;
|
||||
- authoritative SELinux contexts, fs_config and dexpreopt configuration;
|
||||
- exact firmware package and full recovery procedure;
|
||||
- a lab device or proven A/B failure recovery drill.
|
||||
|
||||
The device is locked and green. A well-formed ext4 image is not accepted merely
|
||||
because it mounts offline. Reusing stock vbmeta after a content change fails
|
||||
verity; signing with an arbitrary AVB key fails the locked root of trust.
|
||||
|
||||
## Why root is insufficient
|
||||
|
||||
Runtime root could mount or write files only by stepping outside the production
|
||||
trust model. It cannot create the OEM Platform/AVB signatures, make stale ART
|
||||
outputs coherent or guarantee cold-boot recovery. Persistent root/remount/
|
||||
verification bypasses are outside authorization and are not deployment paths.
|
||||
|
||||
86
patches/phase7/TRUST-GRAPH.md
Normal file
86
patches/phase7/TRUST-GRAPH.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Phase 7B – Shared-UID and signature trust graph
|
||||
|
||||
## Core graph
|
||||
|
||||
```text
|
||||
Philips/Android Platform Signer D8:FF:E0:AF:...:90:68
|
||||
├─ framework-res.apk (`android`)
|
||||
├─ Channels ───────────────┐
|
||||
├─ GEL ────────────────────┤
|
||||
├─ TPTvService ────────────┤
|
||||
├─ Settings/OAD/USBService ┤
|
||||
└─ 58 packages total ──────┴─> sharedUser `android.uid.system`
|
||||
└─ Linux UID 1000 / `system_app`
|
||||
├─ signature/platform permissions
|
||||
├─ privileged Binder services
|
||||
├─ Settings/Package/USB/user control
|
||||
└─ Philips TV providers/services
|
||||
|
||||
Separate trust domains:
|
||||
OTA release cert E0:18:AB:...:A0:F8 -> recovery/OTA certificate store
|
||||
AVB/vbmeta key -> public key not extracted; identity unknown
|
||||
```
|
||||
|
||||
## Exact UID1000 membership
|
||||
|
||||
The live PackageManager lists 58 packages in `android.uid.system`:
|
||||
|
||||
`com.mediatek.tunerservice`, `org.droidtv.alhuewizard`,
|
||||
`org.droidtv.tv.packageinfo`, `com.android.networkstack.inprocess`,
|
||||
`org.droidtv.lkb`, `org.droidtv.epgdata`, `org.droidtv.factory`,
|
||||
`org.droidtv.homeintentresolver`, `com.mediatek.backgrounddetection`,
|
||||
`com.android.tv.settings`, `com.mediatek.tv.agent`,
|
||||
`org.droidtv.euinstallersat`, `com.mediatek.network`,
|
||||
`org.droidtv.nettvbrowser`, `android`, `org.droidtv.channels`,
|
||||
`com.android.location.fused`, `org.droidtv.sunrise`,
|
||||
`org.droidtv.tv.frameworklogger`, `com.android.providers.settings`,
|
||||
`com.android.inputdevices`, `org.droidtv.GlobalKey`, `org.droidtv.ci`,
|
||||
`org.droidtv.networkwizard`, `com.mediatek.tvinput`, `org.droidtv.xtv`,
|
||||
`org.droidtv.sam`, `org.droidtv.oad`, `org.droidtv.teletext`,
|
||||
`org.droidtv.nettvapps`, `org.droidtv.tv.tptvservice`,
|
||||
`com.android.localtransport`, `org.droidtv.devicesetup`,
|
||||
`com.phorus.playfi.softap`, `org.droidtv.contentexplorer`,
|
||||
`com.android.wallpaperbackup`, `org.droidtv.tv.inactivityservice`,
|
||||
`org.droidtv.icp`, `org.droidtv.settings`, `org.droidtv.tvsystemui`,
|
||||
`org.droidtv.exoplayerservice`, `org.droidtv.gel`,
|
||||
`org.droidtv.euinstallertc`, `com.android.dynsystem`,
|
||||
`com.android.keychain`, `org.droidtv.recommendation`, `org.droidtv.csm`,
|
||||
`org.droidtv.dukservice`, `org.droidtv.playtv`,
|
||||
`org.droidtv.common.accesshiddenapi`, `com.easy.oad`,
|
||||
`org.droidtv.candeebug`, `com.android.providers.tv`,
|
||||
`com.android.networkstack.tethering.inprocess`,
|
||||
`org.droidtv.usbbreakin`, `org.droidtv.tvpowermanagerservice`.
|
||||
|
||||
## Permission and dependency edges
|
||||
|
||||
The shared UID's PackageManager grant union includes especially sensitive
|
||||
platform permissions such as `WRITE_SECURE_SETTINGS`, `INSTALL_PACKAGES`,
|
||||
`DELETE_PACKAGES`, `REBOOT`, `MASTER_CLEAR`, `MANAGE_USERS`,
|
||||
`INTERACT_ACROSS_USERS_FULL`, `MANAGE_USB`, `FORCE_STOP_PACKAGES`, `DUMP` and
|
||||
`TV_INPUT_HARDWARE`, plus Philips TV/EPG channel/provider permissions. Some are
|
||||
also gated by privileged allowlists or component permissions; shared UID alone
|
||||
must not be read as an unconditional API guarantee.
|
||||
|
||||
| Package | Shared boundary | Main dependency edges relevant to patching |
|
||||
|---|---|---|
|
||||
| Channels | UID1000 + Platform signer + privileged system app | EPG provider clients, channel/TV input data, Philips TV framework/library |
|
||||
| GEL | UID1000 + Platform signer | WorkManager DB, Philips internal services, platform/signature APIs |
|
||||
| TPTvService | UID1000 + Platform signer + persistent/core | boot-started TV service, storage monitor, Philips Binder/framework clients |
|
||||
| Settings | UID1000 + Platform signer + privileged/persistent | UpdateEngine binding, secure/global settings, USB update receiver |
|
||||
| OAD | UID1000 + Platform signer + privileged | OAD service and upgrade staging integration |
|
||||
| EUM | Platform signer + privileged, separate UID | privileged package/update management; not protected by shared UID membership |
|
||||
|
||||
## Consequence of a signer change
|
||||
|
||||
Android requires applications sharing a UID to be signed compatibly. A newly
|
||||
built APK with a different certificate cannot legitimately join the existing
|
||||
`android.uid.system` group and cannot update the installed package. Removing
|
||||
the shared UID or changing package identity would create a different app,
|
||||
losing UID1000-owned data, access to signature permissions and provider/Binder
|
||||
relationships. Patching PackageManager checks would be a trust bypass and is
|
||||
outside scope.
|
||||
|
||||
Android's official app-signing documentation describes the same-certificate
|
||||
requirement for shared UIDs and signature permissions:
|
||||
<https://source.android.com/docs/security/features/apksigning>.
|
||||
|
||||
86
patches/phase7/UPDATED-SYSTEM-APP-ANALYSIS.md
Normal file
86
patches/phase7/UPDATED-SYSTEM-APP-ANALYSIS.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Phase 7C – Updated-system-app feasibility
|
||||
|
||||
## Device facts
|
||||
|
||||
| Package | Base path | Version | Current state | `/data/app` update technically possible? |
|
||||
|---|---|---|---|---|
|
||||
| `org.droidtv.channels` | `/system/priv-app/channels/channels.apk` | 1 / `0.0.0.1` | base system app, not `UPDATED_SYSTEM_APP` | yes in principle, with accepted Platform signer and compatible higher version |
|
||||
| `org.droidtv.gel` | `/system/app/gel/gel.apk` | 58724408 / `7.2.0.56` | base system app, not updated | yes in principle, same constraints |
|
||||
| `org.droidtv.tv.tptvservice` | `/system/app/TPTvService/TPTvService.apk` | 31 / `12` | base system app, persistent/core, not updated | Android mechanism yes; operational risk materially higher |
|
||||
|
||||
The device already has other updated system applications under `/data/app`, so
|
||||
the Android mechanism is enabled. None of the three targets currently uses it.
|
||||
|
||||
## Hard acceptance conditions
|
||||
|
||||
1. Same package name.
|
||||
2. A PackageManager-compatible signing identity/lineage. Here that means the
|
||||
Philips/Android Platform signer SHA-256 `D8:FF:E0:AF:...:90:68`.
|
||||
3. `android.uid.system` compatibility; it cannot be dropped or replaced without
|
||||
breaking UID/data/permission semantics.
|
||||
4. Accepted version/update policy (normally a higher versionCode; downgrade is
|
||||
not a production plan).
|
||||
5. Compatible manifest, SDK, native ABI and provider/service contracts.
|
||||
6. Explicit data-schema migration and rollback testing.
|
||||
|
||||
The public certificate is available, the private signing key or an OEM signing
|
||||
service is not. Therefore no accepted update can currently be produced.
|
||||
|
||||
## Expected lifecycle for a correctly signed Channels candidate
|
||||
|
||||
This is a documentation-only sequence. No command below was executed.
|
||||
|
||||
### BEFORE
|
||||
|
||||
- Record base/current package paths, versions, signer, `/data` free space and
|
||||
EPG database schema/backup.
|
||||
- Prove a cold-boot recovery route and test on an equivalent lab device.
|
||||
- Verify the candidate's signer, manifest, version and hashes offline.
|
||||
|
||||
### INSTALL
|
||||
|
||||
- PackageManager validates the candidate and places the accepted update under
|
||||
`/data/app`; `/system/priv-app/channels/channels.apk` remains unchanged.
|
||||
- No AVB/system-image modification is required for this path.
|
||||
- A nonmatching signer/shared UID must be rejected; bypassing this check is not
|
||||
an option.
|
||||
|
||||
### PROCESS RESTART
|
||||
|
||||
- PackageManager stops/restarts the package as part of replacement.
|
||||
- The `/data/app` APK becomes active and ART compiles/caches artifacts in the
|
||||
data-side runtime locations. Stock `/system/.../oat/arm` files remain the
|
||||
immutable base artifacts.
|
||||
- Providers and clients must tolerate the restart and schema migration.
|
||||
|
||||
### VALIDATE
|
||||
|
||||
- Confirm active path/version/signer, process stability, channel guide,
|
||||
Now/Next, favorites, timers/reminders/recordings and EPG retention.
|
||||
- Reboot/standby/cold-boot tests remain mandatory before production approval.
|
||||
|
||||
### UNINSTALL UPDATE / FALLBACK
|
||||
|
||||
- Live `cmd package help` documents `uninstall-system-updates [<PACKAGE>]` as:
|
||||
“Removes updates to the given system application and falls back to its
|
||||
/system version.” This is the intended rollback mechanism, not an APK delete.
|
||||
- The stock `/system` APK becomes active again because it was never overwritten.
|
||||
- Data preservation and compatibility are **not guaranteed by this observation**.
|
||||
A newer update may have migrated DB/preferences beyond the old base's ability
|
||||
to read. Rollback must be tested with real data and an independent backup.
|
||||
- Factory reset would also remove `/data/app` updates but is destructive and is
|
||||
not a routine rollback.
|
||||
|
||||
The live help text is the strongest device-specific fallback evidence. AOSP's
|
||||
PackageManager shell implementation is available at
|
||||
<https://android.googlesource.com/platform/frameworks/base/+/698d5999954b3bc7483dedecc2d94a65d69bbd42/services/core/java/com/android/server/pm/PackageManagerShellCommand.java>.
|
||||
|
||||
## Risk ranking
|
||||
|
||||
- **Channels:** best first candidate if signing and recovery conditions are met;
|
||||
not persistent/core, clear measured benefit. Current status: **NO-GO**.
|
||||
- **GEL:** mechanism feasible but WorkManager schema/migration and telemetry
|
||||
semantics need source-level integration. Current status: **NO-GO**.
|
||||
- **TPTvService:** technically updateable but persistent/core boot-start role
|
||||
increases crashloop and recovery risk. Current status: **NO-GO**.
|
||||
|
||||
46
patches/phase7/certificates.txt
Normal file
46
patches/phase7/certificates.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
PHASE 7 CERTIFICATE INVENTORY
|
||||
Captured/verified: 2026-08-26
|
||||
Device build: TPM191E_R.211.000.103.000
|
||||
|
||||
CERTIFICATE 1: APK PLATFORM SIGNER
|
||||
Used by: framework-res.apk, Channels, GEL, TPTvService, EUM, Settings,
|
||||
OAD, btota, USBService, SettingsProxy (and many other OEM apps)
|
||||
PackageManager signer ID: [476d2253]
|
||||
PackageManager signing version: 3
|
||||
Subject: EMAILADDRESS=android@android.com, CN=Android, OU=Android,
|
||||
O=Android, L=Mountain View, ST=California, C=US
|
||||
Issuer: EMAILADDRESS=android@android.com, CN=Android, OU=Android,
|
||||
O=Android, L=Mountain View, ST=California, C=US
|
||||
Serial: 9e71a5645c4fc22f
|
||||
SHA-256: D8:FF:E0:AF:40:4E:AB:8E:45:5A:00:74:D4:17:B0:DA:12:D0:67:26:E5:05:B0:2B:7F:DB:18:31:29:C8:90:68
|
||||
SHA-1: 43:80:01:76:AC:53:BF:E9:FD:33:4A:EB:86:1D:40:A1:2A:00:AF:C0
|
||||
Public key: RSA 2048 bit
|
||||
Certificate signature: SHA1withRSA
|
||||
Validity: 2015-08-10 05:01:32 CEST to 2042-12-26 04:01:32 CET
|
||||
Schemes observed: v1/JAR + APK Signing Block v2 + v3
|
||||
v4: no adjacent .idsig observed; not proven
|
||||
Private key: NOT PRESENT / NOT SEARCHED
|
||||
|
||||
CERTIFICATE 2: OTA/RECOVERY RELEASE CERTIFICATE
|
||||
Source: /system/etc/security/otacerts.zip!/releasekey.x509.pem
|
||||
otacerts.zip SHA-256: cc26a1d15440b2990ccc465c3d6c3c800225fc88a23083724753ee1f3de5ec3c
|
||||
PEM file SHA-256: E20C85B3CE3B2E8B757C4C261C1F3595C6F7914332744F84C709494D9609E64A
|
||||
Subject: EMAILADDRESS=android@android.com, CN=Android, OU=Android,
|
||||
O=Android, L=Mountain View, ST=California, C=US
|
||||
Issuer: EMAILADDRESS=android@android.com, CN=Android, OU=Android,
|
||||
O=Android, L=Mountain View, ST=California, C=US
|
||||
Serial: dd7b52f76828c966
|
||||
SHA-256: E0:18:AB:2F:96:83:8E:B4:B7:B9:1A:D1:12:0B:C6:74:90:F7:E1:01:0F:D4:0C:1A:E5:1E:C4:F8:B1:2A:A0:F8
|
||||
SHA-1: 98:B1:DF:C5:2D:99:28:B2:2A:51:93:AB:10:86:7A:37:85:A4:1B:C0
|
||||
Public key: RSA 2048 bit
|
||||
Certificate signature: SHA1withRSA
|
||||
Validity: 2015-08-10 05:00:59 CEST to 2042-12-26 04:00:59 CET
|
||||
Private key: NOT PRESENT / NOT SEARCHED
|
||||
|
||||
CONCLUSION
|
||||
The two certificates are cryptographically different despite the same textual
|
||||
distinguished name. The OTA public certificate cannot sign PackageManager APK
|
||||
updates. Possession of the APK public certificate cannot create accepted APKs.
|
||||
No AVB public-key descriptor was readable, so neither certificate is asserted
|
||||
to be the AVB key.
|
||||
|
||||
40
patches/storage-monitor/DESIGN.md
Normal file
40
patches/storage-monitor/DESIGN.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Storage monitor redesign
|
||||
|
||||
## Stock behavior proven from DEX
|
||||
|
||||
`TPTvService.apk!/classes.dex`,
|
||||
`TVStorageMonitorService$2.run()`, `code_off=0x2ad380`, polls `/data` using
|
||||
legacy `getBlockSize/getAvailableBlocks/getBlockCount`. Static constants are
|
||||
exactly 524,288,000 bytes and 1,073,741,824 bytes. It also reads
|
||||
`persist.odm.tpv.sys.emmc_lifetime`; 80–90%, 90–100% and exceeded return true.
|
||||
|
||||
Capacity and health are then conflated:
|
||||
|
||||
- below 500,000 KiB-ish bytes: critical cleanup + UI;
|
||||
- below 1 GiB while health is not critical: low-space UI;
|
||||
- at or above 1 GiB while health is critical: `MEMORY_CRITICAL` cleanup anyway.
|
||||
|
||||
The same service contains a global cache sweep over all installed packages and
|
||||
a separate 150 MiB `/data/vendor/user_setting` cleanup branch. Polling normally
|
||||
runs every 30 seconds; a 300-second assignment is immediately overwritten by
|
||||
30 seconds at DEX PCs `0x02de`–`0x02e6`.
|
||||
|
||||
## Replacement policy
|
||||
|
||||
Use `StatFs.getAvailableBytes()` and Android `StorageManager` low/full
|
||||
thresholds for the actual `/data` filesystem. Enter LOW after two low samples;
|
||||
enter CRITICAL immediately at the full threshold. Clear only after three
|
||||
samples above `max(low+256 MiB, low*1.25)`. Notify/broadcast on state transition,
|
||||
not every poll, and rate-limit repeated user UI. Poll every ten minutes in OK,
|
||||
one minute in LOW/CRITICAL, plus platform storage broadcasts.
|
||||
|
||||
eMMC lifetime and PRE_EOL are a separate health state with separate wording,
|
||||
telemetry and escalation. A wear bucket must never claim that filesystem space
|
||||
is low. Unknown health data is not “healthy” or “critical”.
|
||||
|
||||
Do not automatically clear every application's cache. Preserve the
|
||||
`user_setting` check only as a separately named OEM-data policy, with its own
|
||||
threshold, metrics and user impact; it must not drive the internal-storage UI.
|
||||
|
||||
`tools/test_storage_state.py` exercises entry debounce, critical transition,
|
||||
hysteresis, deduplication, unknown measurements and health/capacity separation.
|
||||
30
patches/storage-monitor/PATCH-PLAN.md
Normal file
30
patches/storage-monitor/PATCH-PLAN.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Storage-monitor patch plan
|
||||
|
||||
## Exact stock target
|
||||
|
||||
`/system/app/TPTvService/TPTvService.apk`, SHA-256
|
||||
`75F492EE89906C8C267D685D2B7D0CE02BB09F3A6B4CC8456F8B096B9E441ECD`,
|
||||
`classes.dex`, `TVStorageMonitorService$2.run()`, `code_off=0x2ad380`.
|
||||
Stock constants are exactly 524,288,000 and 1,073,741,824 bytes. The code uses
|
||||
legacy StatFs block multiplication, reads `persist.odm.tpv.sys.emmc_lifetime`,
|
||||
and mixes capacity and wear into the same cleanup/warning path. Its intended
|
||||
300-second delay is overwritten with 30 seconds at PCs `0x02de`–`0x02e6`.
|
||||
|
||||
## Implementation
|
||||
|
||||
Replace the Runnable body with a small sampler feeding the state machine in
|
||||
`POLICY.md`. Use long byte APIs. Persist only the last notified state/timestamp,
|
||||
not a fabricated capacity. Emit structured metrics showing the source and
|
||||
unit. Route capacity transitions and health transitions to different message
|
||||
IDs and actions. Trigger owner-specific cleanup through bounded, idempotent
|
||||
jobs; remove the global package-cache sweep from the automatic path.
|
||||
|
||||
## Verification
|
||||
|
||||
Run the offline policy harness, then build/re-sign TPTvService and re-decompile
|
||||
the result. On a recoverable test image simulate values around both thresholds,
|
||||
measurement failure, rapid fill/reclaim, EPG/GEL cleanup success and failure,
|
||||
health-only warnings, adopted USB removal, reboot and a 30-day soak. Verify UI
|
||||
deduplication and that Android's own storage state agrees. This APK is
|
||||
platform-signed, shared-UID and persistent/core; no live replacement is
|
||||
authorized or safe without the OEM signer and recovery path.
|
||||
40
patches/storage-monitor/POLICY.md
Normal file
40
patches/storage-monitor/POLICY.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Storage-monitor policy
|
||||
|
||||
## Separate states
|
||||
|
||||
Capacity and flash health are independent signals:
|
||||
|
||||
1. `CAPACITY_OK`, `CAPACITY_LOW`, `CAPACITY_CRITICAL`, or `CAPACITY_UNKNOWN`
|
||||
comes from the filesystem containing `/data`.
|
||||
2. `HEALTH_NORMAL`, `HEALTH_WARNING`, `HEALTH_URGENT`, or `HEALTH_UNKNOWN`
|
||||
comes from eMMC lifetime/PRE_EOL evidence.
|
||||
|
||||
An eMMC wear bucket must never produce the text “internal storage almost
|
||||
full”. Health gets separate maintenance wording. Unknown is never silently
|
||||
mapped to healthy or critical.
|
||||
|
||||
## Capacity thresholds
|
||||
|
||||
Read `StorageManager`/platform low and full thresholds for `/data`, plus
|
||||
`StatFs.getAvailableBytes()`. Enter CRITICAL immediately at the platform full
|
||||
threshold. Enter LOW after two consecutive samples below the platform low
|
||||
threshold. Clear only after three samples above
|
||||
`max(lowThreshold + 256 MiB, lowThreshold * 1.25)`. This combines Android's
|
||||
device-specific absolute threshold with hysteresis; do not retain the stock
|
||||
unconditional 1-GiB warning.
|
||||
|
||||
Report total, available, reclaimable, recent growth rate and adopted-volume
|
||||
state separately. Reclaimable bytes inform the cleanup offer but are not
|
||||
pretended to be already free.
|
||||
|
||||
## Actions and timing
|
||||
|
||||
- OK: poll every 10 minutes and react to platform storage broadcasts.
|
||||
- LOW/CRITICAL: poll every minute until stable recovery.
|
||||
- Show UI only on state transition and apply a user-visible cooldown.
|
||||
- First run bounded owner-specific cleanup (EPG retention, GEL pruning), then
|
||||
user-approved cache cleanup. Never sweep all package caches automatically.
|
||||
- Keep `/data/vendor/user_setting` as a separately named OEM policy with its
|
||||
own metrics and user-impact review.
|
||||
|
||||
The state-machine harness is `tools/test_storage_state.py`.
|
||||
30
patches/storage-monitor/REFERENCE-PATCH.diff
Normal file
30
patches/storage-monitor/REFERENCE-PATCH.diff
Normal file
@@ -0,0 +1,30 @@
|
||||
--- a/src/org/droidtv/tv/tptvservice/tvstoragemonitor/TVStorageMonitorService.java
|
||||
+++ b/src/org/droidtv/tv/tptvservice/tvstoragemonitor/TVStorageMonitorService.java
|
||||
@@
|
||||
- long blockSize = statFs.getBlockSize();
|
||||
- long availableBlocks = statFs.getAvailableBlocks();
|
||||
- long free = blockSize * availableBlocks;
|
||||
- boolean emmcCritical = getEmmcLifetime();
|
||||
- if (free < under_500MB) { ... MEMORY_CRITICAL ... }
|
||||
- else if (free < under_1GB && !emmcCritical) { ... low-storage UI ... }
|
||||
- else if (free >= under_1GB && emmcCritical) { ... MEMORY_CRITICAL ... }
|
||||
+ final File data = Environment.getDataDirectory();
|
||||
+ final StatFs fs = new StatFs(data.getAbsolutePath());
|
||||
+ final long freeBytes = fs.getAvailableBytes();
|
||||
+ final StorageManager sm = getSystemService(StorageManager.class);
|
||||
+ final long lowBytes = sm.getStorageLowBytes(data);
|
||||
+ final long fullBytes = sm.getStorageFullBytes(data);
|
||||
+ final long clearBytes = Math.max(lowBytes + 256L * 1024 * 1024,
|
||||
+ lowBytes * 5L / 4L);
|
||||
+ CapacityState next = capacityState.update(freeBytes, lowBytes, fullBytes,
|
||||
+ clearBytes, 2, 3);
|
||||
+ if (next.changed()) publishCapacityTransition(next, freeBytes, lowBytes);
|
||||
+ // Health is a separate diagnostic channel. It must never synthesize a
|
||||
+ // low-storage notification or CLEANUP_APPS broadcast.
|
||||
+ EmmcHealth health = readEmmcHealthIfAvailable();
|
||||
+ publishHealthTransitionOnly(health);
|
||||
@@
|
||||
- for (PackageInfo pkg : pm.getInstalledPackages(0))
|
||||
- pm.deleteApplicationCacheFiles(pkg.packageName, null);
|
||||
+ // Removed: global asynchronous cache sweeping is not a monitor action.
|
||||
+ // Offer an explicit user/OEM cleanup workflow with attribution instead.
|
||||
64
patches/storage-monitor/tools/test_storage_state.py
Normal file
64
patches/storage-monitor/tools/test_storage_state.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pure state-machine tests for the proposed storage monitor hysteresis."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
MIB = 1024 * 1024
|
||||
|
||||
|
||||
@dataclass
|
||||
class Monitor:
|
||||
state: str = "OK"
|
||||
low_streak: int = 0
|
||||
good_streak: int = 0
|
||||
notifications: int = 0
|
||||
|
||||
def sample(self, free: int | None, low: int, full: int) -> str:
|
||||
if free is None:
|
||||
return self.state
|
||||
clear = max(low + 256 * MIB, low * 5 // 4)
|
||||
old = self.state
|
||||
if free <= full:
|
||||
self.state, self.low_streak, self.good_streak = "CRITICAL", 0, 0
|
||||
elif free <= low:
|
||||
self.low_streak += 1
|
||||
self.good_streak = 0
|
||||
if self.low_streak >= 2:
|
||||
self.state = "LOW"
|
||||
elif free >= clear:
|
||||
self.good_streak += 1
|
||||
self.low_streak = 0
|
||||
if self.good_streak >= 3:
|
||||
self.state = "OK"
|
||||
else:
|
||||
self.low_streak = self.good_streak = 0
|
||||
if self.state != old:
|
||||
self.notifications += 1
|
||||
return self.state
|
||||
|
||||
|
||||
def main():
|
||||
low, full = 800 * MIB, 100 * MIB
|
||||
m = Monitor()
|
||||
assert m.sample(700 * MIB, low, full) == "OK"
|
||||
assert m.sample(700 * MIB, low, full) == "LOW"
|
||||
assert m.notifications == 1
|
||||
for _ in range(10):
|
||||
assert m.sample(750 * MIB, low, full) == "LOW"
|
||||
assert m.notifications == 1
|
||||
assert m.sample(90 * MIB, low, full) == "CRITICAL"
|
||||
assert m.notifications == 2
|
||||
clear = max(low + 256 * MIB, low * 5 // 4)
|
||||
assert m.sample(clear, low, full) == "CRITICAL"
|
||||
assert m.sample(clear, low, full) == "CRITICAL"
|
||||
assert m.sample(clear, low, full) == "OK"
|
||||
assert m.notifications == 3
|
||||
assert m.sample(None, low, full) == "OK"
|
||||
# eMMC health is intentionally absent from the capacity input/state.
|
||||
health_warning = True
|
||||
assert health_warning and m.state == "OK"
|
||||
print("PASS: entry debounce, critical, hysteresis, dedupe, unknown, health separation")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
55
patches/storage/SELECTIVE-ADOPTED-DESIGN.md
Normal file
55
patches/storage/SELECTIVE-ADOPTED-DESIGN.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Selective adopted-storage design
|
||||
|
||||
## Candidate matrix
|
||||
|
||||
| Candidate | Observed size | Owner / label | Opener and boot timing | Reconstructible | USB failure / fallback | Decision |
|
||||
|---|---:|---|---|---|---|---|
|
||||
| EPG `EpgDatabase.db` schedule payload | 1,742,053,376 B before repair; ~806,912 B after reacquisition | `system:system`, `system_app_data_file` | Channels UID1000 provider; needed for Guide, not Android boot | schedule yes; channel/mapping/favorite/recording relations not assumed so | Live TV must retain an internal minimal DB; reacquire schedule | Architecture C now; optional A only with OEM integration |
|
||||
| GEL WorkManager DB | 229,453,824 B pathological snapshot; tiny after repair | `system:system`, `system_app_data_file` | GEL/WorkManager, system-app startup | derived work history partly; undelivered actions are not disposable | internal bounded outbox required | Fix queue; do not move |
|
||||
| Downloads/media cache | not measured as a dominant `/data` cause | package-specific | consumer-specific, normally post-boot | only explicitly cached/downloadable objects | missing-volume UI and internal metadata | Eligible case-by-case after inventory |
|
||||
| Updated-system APK code/data | package-dependent | Package Manager types | package/runtime dependent | generally no | Android package move is whole-package, not selective | Not a DB-path solution |
|
||||
| Accounts, Settings, PM, boot/FBE metadata | not relevant to recovered leak | core platform types | early/boot-critical | no | no safe USB fallback | Excluded |
|
||||
|
||||
## Architecture A — configurable EPG path
|
||||
|
||||
Channels keeps authoritative channel, mapping, favorites, Now/Next and
|
||||
recording/reminder links internally. After vold reports the expected adopted
|
||||
UUID mounted, an OEM helper opens a narrowly typed private-ext4 schedule-cache
|
||||
directory. It validates UUID, schema, generation and integrity before switching
|
||||
the provider's schedule backend. Removal closes that pool and immediately
|
||||
returns to the internal minimal provider. This needs Philips source, a narrow
|
||||
SELinux type, explicit volume callbacks and OTA/schema migration tests. It must
|
||||
not use emulated/FUSE storage, symlinks or a boot bind mount.
|
||||
|
||||
## Architecture B — internal live DB plus archive
|
||||
|
||||
Keep current guide data internal and place optional old/archive payloads on
|
||||
adopted storage. This reduces neither the repaired seven-day working set nor
|
||||
the need for retention, complicates queries and cross-volume consistency, and
|
||||
has little demonstrated product value. Never rely on cross-volume SQLite
|
||||
`ATTACH` for atomic transactions. Classification: possible but unjustified.
|
||||
|
||||
## Architecture C — retention only
|
||||
|
||||
Keep EPG internal and deploy BUG-001's bounded cleanup. Phase 5 reduced the DB
|
||||
from 1.62 GiB to about 0.8 MiB while the Guide, Live TV, favorites and timers
|
||||
remained functional; the seven-day offline test retained 7,387 rows in a
|
||||
4.06-MiB database. This is the preferred architecture because it removes the
|
||||
actual leak without USB power, mount-order, locking, recovery or OTA coupling.
|
||||
|
||||
## Migration and failure gates
|
||||
|
||||
If A is ever built, default the feature off. Copy only reconstructible schedule
|
||||
rows into a versioned temporary generation, check integrity, fsync file and
|
||||
directory, then atomically switch an app-level marker. Retain the internal
|
||||
fallback. A missing, slow, full, removed, corrupt, reformatted or UUID-changed
|
||||
USB device is a normal tested state. Factory reset, OTA, standby power loss,
|
||||
1,000 remove/reinsert cycles and fsync latency must never impair Android boot
|
||||
or Live TV. Adopted media is encrypted/device-bound and is not a backup.
|
||||
|
||||
## Decision
|
||||
|
||||
Select C for this television. Reconsider A only if a future measured,
|
||||
reconstructible payload remains large after retention and Philips can ship the
|
||||
required source, SELinux and recoverable firmware changes. Reject moving whole
|
||||
`/data`.
|
||||
84
patches/tests/run_all.py
Normal file
84
patches/tests/run_all.py
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run all Phase 6 offline gates from any working directory."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TESTS = [
|
||||
ROOT / "patches/epg-stale-cleanup/tools/test_epg_retention.py",
|
||||
ROOT / "patches/gel-workmanager/tools/verify_stock_callsite.py",
|
||||
ROOT / "patches/gel-workmanager/tools/test_bounded_outbox.py",
|
||||
ROOT / "patches/usb-freesize/tools/test_usb_size_policy.py",
|
||||
ROOT / "patches/storage-monitor/tools/test_storage_state.py",
|
||||
]
|
||||
GOLDEN = {
|
||||
"diagnostics/raw/storage/channels.apk": "3a44bc648624f0cc9cd9feb4cfc3fd33f41323597718e4fccfc25fe35504d34f",
|
||||
"diagnostics/raw/storage/gel.apk": "29a68e6b09700ed4ccdf312c0b99eee0c3f67e329a537fe87ed4341a71d383d7",
|
||||
"diagnostics/phase4/reference-binaries/tvfw.jar": "a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad",
|
||||
"diagnostics/raw/TPTvService.apk": "75f492ee89906c8c267d685d2b7d0ce02bb09f3a6b4cc8456f8b096b9e441ecd",
|
||||
"diagnostics/phase4/epg-snapshot/EpgDatabase.db": "71403743206d5082aae56c4a947f3429f5c3d54b908c4abe2eb0ca66e06694ba",
|
||||
}
|
||||
ARTIFACTS = [
|
||||
"patches/epg-stale-cleanup/PATCH.diff",
|
||||
"patches/epg-stale-cleanup/tools/epg_retention.py",
|
||||
"patches/gel-workmanager/PATCH.diff",
|
||||
"patches/gel-workmanager/tools/verify_stock_callsite.py",
|
||||
"patches/usb-freesize/PATCH.diff",
|
||||
"patches/storage-monitor/POLICY.md",
|
||||
"patches/storage/SELECTIVE-ADOPTED-DESIGN.md",
|
||||
]
|
||||
|
||||
|
||||
def digest(path: Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open("rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
omitted_baselines: list[str] = []
|
||||
for rel, expected in GOLDEN.items():
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
omitted_baselines.append(rel)
|
||||
print(f"SKIP unpublished golden binary: {rel}")
|
||||
continue
|
||||
got = digest(path)
|
||||
if got != expected:
|
||||
raise SystemExit(f"FAIL golden hash: {rel}: {got}")
|
||||
print(f"PASS golden hash: {rel}")
|
||||
|
||||
for test in TESTS:
|
||||
if test.name == "verify_stock_callsite.py" and not (
|
||||
ROOT / "diagnostics/raw/storage/gel.apk"
|
||||
).exists():
|
||||
print("SKIP stock GEL callsite test: unpublished APK is required")
|
||||
continue
|
||||
print(f"RUN {test.relative_to(ROOT)}", flush=True)
|
||||
subprocess.run([sys.executable, str(test)], cwd=test.parent, check=True)
|
||||
|
||||
lines = [f"{digest(ROOT / rel)} {rel}" for rel in ARTIFACTS]
|
||||
if "--write-hashes" in sys.argv:
|
||||
out = ROOT / "patches/artifacts/hashes.sha256"
|
||||
out.write_text("\n".join(lines) + "\n", encoding="ascii")
|
||||
print(f"WROTE {out.relative_to(ROOT)}")
|
||||
else:
|
||||
print("\n".join(lines))
|
||||
if omitted_baselines:
|
||||
print(
|
||||
"NOTE: publication omits private/redistributable baselines; "
|
||||
"their expected hashes remain documented"
|
||||
)
|
||||
print("PASS: all available Phase 6 offline gates")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
22
patches/usb-freesize/DEX-BOUNDARY.md
Normal file
22
patches/usb-freesize/DEX-BOUNDARY.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# USB size DEX boundary
|
||||
|
||||
Stock `tvfw.jar!/classes.dex`, class
|
||||
`Lorg/droidtv/tv/os/storage/UsbVolumeListerImpl;`, private
|
||||
`getVolumes(String)`, code item `code_off=0x17b5e0`, registers 15.
|
||||
|
||||
At PC `0x0133` the method reads only
|
||||
`sys.mtk.dmremoteservice.ready`. If it is not exactly `"1"`, execution jumps
|
||||
past PC `0x0157` without ever assigning `UsbVolume.mFreeDiskSize`; the
|
||||
constructor default zero is then published and logged. This is the confirmed
|
||||
zero-size mechanism.
|
||||
|
||||
The MediaTek producer itself uses `StatFs`, divides bytes by 1024 and stores
|
||||
KiB in `MountPoint.mTotalSize/mFreeSize`. `UsbVolumeListerImpl` divides those
|
||||
fields by another 1024, proving that the public `mRawDiskSize/mFreeDiskSize`
|
||||
values on this path are MiB. The fallback must therefore divide StatFs bytes by
|
||||
1,048,576, not store raw bytes.
|
||||
|
||||
Adding guarded StatFs calls and exception handling grows the code item and
|
||||
requires a rebuilt framework DEX. `tvfw.jar` is the registered `tvfw` Java
|
||||
shared library (not BOOTCLASSPATH) and is coupled to stock `tvfw.odex`/VDEX and
|
||||
client class-loader contexts; no live or standalone JAR replacement is produced.
|
||||
23
patches/usb-freesize/IMPLEMENTATION.md
Normal file
23
patches/usb-freesize/IMPLEMENTATION.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# USB free-size implementation
|
||||
|
||||
The exact stock target is `tvfw.jar!/classes.dex`,
|
||||
`UsbVolumeListerImpl.getVolumes(String)`, `code_off=0x17b5e0`. At DEX PC
|
||||
`0x0133` it reads only `sys.mtk.dmremoteservice.ready`; when the value is not
|
||||
`1`, the size assignment is skipped and constructor-default zero is published.
|
||||
|
||||
MediaTek `DMServer$4` derives `MountPoint` sizes as StatFs bytes divided by
|
||||
1,024, so those fields are KiB. `UsbVolumeListerImpl` divides by 1,024 again,
|
||||
making its public field contract MiB. The fallback must therefore use
|
||||
`StatFs.getAvailableBytes()/1,048,576`, with `long` arithmetic.
|
||||
|
||||
Accept DeviceManager values only when the service is ready, total is positive,
|
||||
and `0 <= free <= total`. Otherwise query the actual mounted `VolumeInfo` path.
|
||||
A successful query may legitimately return zero; a missing path, removal race,
|
||||
permission error, or invalid range must remain UNKNOWN/omitted—not numeric zero.
|
||||
The same decision applies to public, private/adopted and emulated volumes.
|
||||
|
||||
`PATCH.diff` is the source-level specification; `SMALI-PATCH.md` records the
|
||||
DEX integration boundary. `tools/test_usb_size_policy.py` validates both
|
||||
sources, property states, >2-TiB arithmetic, true zero and unknown. Deployment
|
||||
still requires a rebuilt `tvfw` shared-library artifact plus client/preopt and
|
||||
AVB testing.
|
||||
45
patches/usb-freesize/PATCH-PLAN.md
Normal file
45
patches/usb-freesize/PATCH-PLAN.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Patch plan – reliable USB free-space reporting
|
||||
|
||||
## Target
|
||||
|
||||
`/system/framework/tvfw.jar`, `UsbVolumeListerImpl.getVolumes(String)`, SHA-256
|
||||
`a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad`.
|
||||
|
||||
## Java pseudodiff
|
||||
|
||||
```diff
|
||||
- long freeMiB = -1;
|
||||
- if (SystemProperties.get("sys.mtk.dmremoteservice.ready").equals("1"))
|
||||
- free = deviceManager.getMountPoint(path).mFreeSize;
|
||||
+ long freeMiB = -1;
|
||||
+ boolean ready = SystemProperties.getBoolean(
|
||||
+ "vendor.mtk.dmremoteservice.ready",
|
||||
+ SystemProperties.getBoolean("sys.mtk.dmremoteservice.ready", false));
|
||||
+ if (ready) freeMiB = safeDmFreeKiB(path) / 1024L;
|
||||
+ if (freeMiB < 0 || freeMiB > totalMiB) {
|
||||
+ try { freeMiB = new StatFs(path).getAvailableBytes() / (1024L * 1024L); }
|
||||
+ catch (RuntimeException e) { freeMiB = UNKNOWN_MIB; }
|
||||
+ }
|
||||
```
|
||||
|
||||
The existing `UsbVolume` field contract is MiB on the MediaTek-ready path, so
|
||||
the fallback must retain MiB rather than mixing raw bytes. Resolve the path
|
||||
from the current `VolumeInfo`: public uses its mounted path;
|
||||
private/adopted uses `/mnt/expand/<fsUuid>`. Only return a numeric zero when a
|
||||
successful filesystem query actually reports zero; otherwise expose unknown or
|
||||
omit the volume. Use `long` end-to-end.
|
||||
|
||||
## Compatibility / smali
|
||||
|
||||
Supporting both old and vendor-prefixed properties avoids regressions across
|
||||
MediaTek branches, but the correctness guarantee comes from `StatFs`, not the
|
||||
property spelling. A smali patch adds guarded `StatFs.<init>`,
|
||||
`getAvailableBytes`, range validation and exception handling. Framework-JAR
|
||||
replacement requires shared-library/client-compatible DEX, correct oat
|
||||
handling and AVB/system-image resigning; do not hot-patch the live locked device.
|
||||
|
||||
## Tests
|
||||
|
||||
Public/adopted, ready true/false/missing, removed between enumeration/query,
|
||||
read-only volume, >2-TiB arithmetic, zero genuinely full, reinsertion and path
|
||||
change. Compare against `df` within filesystem allocation tolerance.
|
||||
38
patches/usb-freesize/PATCH.diff
Normal file
38
patches/usb-freesize/PATCH.diff
Normal file
@@ -0,0 +1,38 @@
|
||||
--- a/src/org/droidtv/tv/os/storage/UsbVolumeListerImpl.java
|
||||
+++ b/src/org/droidtv/tv/os/storage/UsbVolumeListerImpl.java
|
||||
@@
|
||||
- if ("1".equals(SystemProperties.get("sys.mtk.dmremoteservice.ready"))) {
|
||||
- MountPoint mp = DeviceManager.getInstance().getMountPoint(path);
|
||||
- if (mp != null) {
|
||||
- volume.mRawDiskSize = mp.mTotalSize / 1024;
|
||||
- volume.mFreeDiskSize = mp.mFreeSize / 1024;
|
||||
- }
|
||||
- }
|
||||
+ boolean known = false;
|
||||
+ if ("1".equals(SystemProperties.get("sys.mtk.dmremoteservice.ready"))) {
|
||||
+ MountPoint mp = DeviceManager.getInstance().getMountPoint(path);
|
||||
+ if (mp != null) {
|
||||
+ long totalMiB = mp.mTotalSize / 1024L;
|
||||
+ long freeMiB = mp.mFreeSize / 1024L;
|
||||
+ if (totalMiB > 0 && freeMiB >= 0 && freeMiB <= totalMiB) {
|
||||
+ volume.mRawDiskSize = totalMiB;
|
||||
+ volume.mFreeDiskSize = freeMiB;
|
||||
+ known = true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ if (!known) {
|
||||
+ try {
|
||||
+ StatFs fs = new StatFs(path);
|
||||
+ long totalMiB = fs.getTotalBytes() / 1_048_576L;
|
||||
+ long freeMiB = fs.getAvailableBytes() / 1_048_576L;
|
||||
+ if (totalMiB <= 0 || freeMiB < 0 || freeMiB > totalMiB)
|
||||
+ throw new IllegalStateException("invalid StatFs size");
|
||||
+ volume.mRawDiskSize = totalMiB;
|
||||
+ volume.mFreeDiskSize = freeMiB;
|
||||
+ known = true;
|
||||
+ } catch (RuntimeException e) {
|
||||
+ Log.w(TAG, "volume size unknown: " + volume.mUUID, e);
|
||||
+ }
|
||||
+ }
|
||||
+ if (!known) continue;
|
||||
39
patches/usb-freesize/REFERENCE-PATCH.diff
Normal file
39
patches/usb-freesize/REFERENCE-PATCH.diff
Normal file
@@ -0,0 +1,39 @@
|
||||
--- a/src/org/droidtv/tv/os/storage/UsbVolumeListerImpl.java
|
||||
+++ b/src/org/droidtv/tv/os/storage/UsbVolumeListerImpl.java
|
||||
@@
|
||||
- if ("1".equals(SystemProperties.get("sys.mtk.dmremoteservice.ready"))) {
|
||||
- MountPoint mp = DeviceManager.getInstance().getMountPoint(path);
|
||||
- if (mp != null) {
|
||||
- volume.mRawDiskSize = mp.mTotalSize / 1024;
|
||||
- volume.mFreeDiskSize = mp.mFreeSize / 1024;
|
||||
- }
|
||||
- }
|
||||
+ boolean sizeKnown = false;
|
||||
+ if ("1".equals(SystemProperties.get("sys.mtk.dmremoteservice.ready"))) {
|
||||
+ MountPoint mp = DeviceManager.getInstance().getMountPoint(path);
|
||||
+ if (mp != null) {
|
||||
+ long totalMiB = mp.mTotalSize / 1024; // MountPoint is KiB
|
||||
+ long freeMiB = mp.mFreeSize / 1024;
|
||||
+ if (totalMiB > 0 && freeMiB >= 0 && freeMiB <= totalMiB) {
|
||||
+ volume.mRawDiskSize = totalMiB;
|
||||
+ volume.mFreeDiskSize = freeMiB;
|
||||
+ sizeKnown = true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ if (!sizeKnown) {
|
||||
+ try {
|
||||
+ StatFs fs = new StatFs(path);
|
||||
+ long totalMiB = fs.getTotalBytes() / (1024L * 1024L);
|
||||
+ long freeMiB = fs.getAvailableBytes() / (1024L * 1024L);
|
||||
+ if (totalMiB <= 0 || freeMiB < 0 || freeMiB > totalMiB) {
|
||||
+ throw new IllegalStateException("invalid StatFs size");
|
||||
+ }
|
||||
+ volume.mRawDiskSize = totalMiB;
|
||||
+ volume.mFreeDiskSize = freeMiB;
|
||||
+ sizeKnown = true;
|
||||
+ } catch (RuntimeException e) {
|
||||
+ Log.w(TAG, "size unavailable for mounted volume " + volume.mUUID, e);
|
||||
+ }
|
||||
+ }
|
||||
+ if (!sizeKnown) continue; // never publish an unknown size as numeric zero
|
||||
18
patches/usb-freesize/SMALI-PATCH.md
Normal file
18
patches/usb-freesize/SMALI-PATCH.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# USB DEX/smali integration boundary
|
||||
|
||||
Target the golden `tvfw.jar` SHA-256
|
||||
`A0061795D87CF76C9B739E32022AB3D8C29F47ED01DDC1EEE189DD815A8D11AD`,
|
||||
`classes.dex`, method `UsbVolumeListerImpl.getVolumes(String)` at
|
||||
`code_off=0x17b5e0` (`registers=15`). The stock property branch starts at PC
|
||||
`0x0133`.
|
||||
|
||||
The rebuilt method must add a guarded `android/os/StatFs` construction,
|
||||
`getTotalBytes`, `getAvailableBytes`, 64-bit division by `0x100000`, range
|
||||
checks and a `RuntimeException` handler. Extend register count and try ranges
|
||||
through an assembler; do not hand-overwrite code bytes or offsets. Preserve
|
||||
the output-unit contract (MiB), all non-size fields, enumeration order and
|
||||
volume filtering. Re-disassemble the output and verify branch/handler targets.
|
||||
|
||||
This JAR is a registered `tvfw` Java shared library, not bootclasspath. Test
|
||||
client class-loader contexts and regenerate/omit stock ODEX/VDEX through a
|
||||
controlled image build; never pair modified DEX with the golden preopt files.
|
||||
35
patches/usb-freesize/tools/test_usb_size_policy.py
Normal file
35
patches/usb-freesize/tools/test_usb_size_policy.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Unit tests for the tvfw USB size fallback and unit contract."""
|
||||
|
||||
MIB = 1024 * 1024
|
||||
|
||||
|
||||
def sizes_mib(dm_ready, dm_total_kib, dm_free_kib, stat_total_bytes, stat_free_bytes):
|
||||
if dm_ready and dm_total_kib is not None and dm_free_kib is not None:
|
||||
total = dm_total_kib // 1024
|
||||
free = dm_free_kib // 1024
|
||||
if total > 0 and 0 <= free <= total:
|
||||
return total, free, "dm"
|
||||
if stat_total_bytes is None or stat_free_bytes is None:
|
||||
return None
|
||||
total = stat_total_bytes // MIB
|
||||
free = stat_free_bytes // MIB
|
||||
if total <= 0 or not 0 <= free <= total:
|
||||
return None
|
||||
return total, free, "statfs"
|
||||
|
||||
|
||||
def main():
|
||||
gib = 1024 * MIB
|
||||
assert sizes_mib(False, None, None, 64 * gib, 48 * gib) == (65536, 49152, "statfs")
|
||||
assert sizes_mib(True, 64 * 1024 * 1024, 48 * 1024 * 1024, None, None) == (65536, 49152, "dm")
|
||||
assert sizes_mib(True, 0, 0, 64 * gib, 48 * gib) == (65536, 49152, "statfs")
|
||||
assert sizes_mib(True, 64 * 1024 * 1024, 70 * 1024 * 1024, 64 * gib, 48 * gib) == (65536, 49152, "statfs")
|
||||
assert sizes_mib(False, None, None, 3 * 1024 * gib, 2 * 1024 * gib) == (3145728, 2097152, "statfs")
|
||||
assert sizes_mib(False, None, None, 64 * gib, 0) == (65536, 0, "statfs")
|
||||
assert sizes_mib(False, None, None, None, None) is None
|
||||
print("PASS: property states, DM validation, StatFs fallback, >2TiB, real zero, unknown")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
recovery/BASELINE-HASHES.md
Normal file
29
recovery/BASELINE-HASHES.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Golden recovery hashes
|
||||
|
||||
Captured 2026-08-26 from the matching live build. Compare SHA-256 exactly;
|
||||
filenames and X.509 subjects alone are insufficient.
|
||||
|
||||
| Component | Stock path | SHA-256 |
|
||||
|---|---|---|
|
||||
| Channels | `/system/priv-app/channels/channels.apk` | `3a44bc648624f0cc9cd9feb4cfc3fd33f41323597718e4fccfc25fe35504d34f` |
|
||||
| GEL | `/system/app/gel/gel.apk` | `29a68e6b09700ed4ccdf312c0b99eee0c3f67e329a537fe87ed4341a71d383d7` |
|
||||
| TPTvService | `/system/app/TPTvService/TPTvService.apk` | `75f492ee89906c8c267d685d2b7d0ce02bb09f3a6b4cc8456f8b096b9e441ecd` |
|
||||
| tvfw.jar | `/system/framework/tvfw.jar` | `a0061795d87cf76c9b739e32022ab3d8c29f47ed01ddc1eee189dd815a8d11ad` |
|
||||
| framework-res | `/system/framework/framework-res.apk` | `d50a2ad1caa5c6e95ee1540d798150f40246f20c90d3a0d9371c58b541a27508` |
|
||||
|
||||
Preopt:
|
||||
|
||||
| Path | SHA-256 |
|
||||
|---|---|
|
||||
| `/system/priv-app/channels/oat/arm/channels.odex` | `a440f6914a3d6010e9482cc0be962978ab2db73eb2384fb5dc2f5c1e72a25215` |
|
||||
| `/system/priv-app/channels/oat/arm/channels.vdex` | `be8923c2aa6232ed44dc81b141e24ed9dc92d84ba346cdb9625da338cd86a29b` |
|
||||
| `/system/app/gel/oat/arm/gel.odex` | `587cd8b7a1a3b6861bd0fb577a0fc0b9e101bf8899c38acae6a83c30d77895d1` |
|
||||
| `/system/app/gel/oat/arm/gel.vdex` | `f3ba405a06d1129b19e197207cde94842703acb39663d251f34156778ae867f9` |
|
||||
| `/system/app/TPTvService/oat/arm/TPTvService.odex` | `8dade47e3181a2906bc7485096d5d62b25da7ee7888bd878180bb1e0f949513d` |
|
||||
| `/system/app/TPTvService/oat/arm/TPTvService.vdex` | `7a29519398c8a136a1bf7872470e51085e9688af33500f4e707c90fdee63016e` |
|
||||
| `/system/framework/oat/arm/tvfw.odex` | `a4af432c54153c9325d92f338f93b2b0a6cfa6b618a5d2c31d587e93eceff531` |
|
||||
| `/system/framework/oat/arm/tvfw.vdex` | `abd0f4ce99404199332a779b195c53ca57d2238d0368910b0936cd55a8572509` |
|
||||
|
||||
Platform certificate SHA-256:
|
||||
`D8:FF:E0:AF:40:4E:AB:8E:45:5A:00:74:D4:17:B0:DA:12:D0:67:26:E5:05:B0:2B:7F:DB:18:31:29:C8:90:68`.
|
||||
|
||||
35
recovery/FULL-FIRMWARE-RECOVERY.md
Normal file
35
recovery/FULL-FIRMWARE-RECOVERY.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Full firmware recovery plan
|
||||
|
||||
## Acquisition
|
||||
|
||||
Request from Philips/TPVision:
|
||||
|
||||
1. full, not merely incremental, USB firmware for `43PUS7394/12` and
|
||||
`TPM191E_R`;
|
||||
2. current or explicitly upgrade/downgrade-compatible release;
|
||||
3. SHA-256 and signed publication/release note;
|
||||
4. model-specific cold-boot/emergency installation instructions;
|
||||
5. clarification whether user settings/data are preserved;
|
||||
6. boot-slot recovery and failure/power-loss behavior.
|
||||
|
||||
## Offline validation before use
|
||||
|
||||
- hash/provenance and exact filename;
|
||||
- ZIP/UPG structure, `upg.xml` product ID and source/target versions;
|
||||
- payload partition list and properties;
|
||||
- no modification, repacking or third-party wrapper;
|
||||
- sufficient, stable power and known expected duration.
|
||||
|
||||
## Execution gate
|
||||
|
||||
A full firmware action writes many boot-critical partitions and may erase data.
|
||||
It requires a new explicit authorization and a final preflight. Do not start it
|
||||
merely because Android UI is slow; first distinguish app-update rollback from a
|
||||
true boot/system-image failure.
|
||||
|
||||
## Post-recovery verification
|
||||
|
||||
Check build/fingerprint, active slot, locked/green AVB, hashes, `/data` capacity,
|
||||
TV setup, tuner/channels, Guide, favorites, recordings/reminders, network,
|
||||
HDMI/CEC, sound, standby/wake and whether the original bug is present.
|
||||
|
||||
34
recovery/README.md
Normal file
34
recovery/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Philips 43PUS7394/12 recovery packet
|
||||
|
||||
Scope: `TPM191E_R.211.000.103.000`. These runbooks are preparation only; no
|
||||
recovery, uninstall, reboot, flash or file write was performed while creating
|
||||
them.
|
||||
|
||||
## Emergency order
|
||||
|
||||
1. **Do not power-cycle repeatedly.** Record screen state, LEDs and whether ADB
|
||||
answers.
|
||||
2. If ADB works, collect read-only package/path/version/log evidence first.
|
||||
3. Identify whether the failed artifact is a `/data/app` update or a system-
|
||||
image change.
|
||||
4. For an updated-system APK, use only the package-specific guide after explicit
|
||||
authorization. PackageManager fallback is preferred over file deletion.
|
||||
5. For `tvfw.jar`, verified-system or no-ADB failures, stop. Use only an
|
||||
official Philips full firmware and a proven model-specific procedure.
|
||||
|
||||
## Files
|
||||
|
||||
- `BASELINE-HASHES.md` – known-good identities.
|
||||
- `RESTORE-CHANNELS.md`, `RESTORE-GEL.md`, `RESTORE-TPTVSERVICE.md` –
|
||||
PackageManager fallback plans.
|
||||
- `RESTORE-TVFW.md` – why no standalone restore is approved.
|
||||
- `USB-RECOVERY.md` – current evidence and prerequisites.
|
||||
- `FULL-FIRMWARE-RECOVERY.md` – OEM recovery acquisition/checklist.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
Stop and reassess if the serial/model/build do not match, ADB targets another
|
||||
device, the base APK hash differs, the official firmware provenance/hash is
|
||||
unknown, battery/power is unstable, or any instruction requires AVB bypass,
|
||||
unlock, arbitrary block writes or third-party images.
|
||||
|
||||
34
recovery/RESTORE-CHANNELS.md
Normal file
34
recovery/RESTORE-CHANNELS.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Restore Channels updated-system app
|
||||
|
||||
Use only if a newly installed `/data/app` update of `org.droidtv.channels`
|
||||
causes failures and normal ADB is available. This is a future state-changing
|
||||
rollback and requires explicit approval at execution time.
|
||||
|
||||
## Read-only triage
|
||||
|
||||
```powershell
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell pm path org.droidtv.channels
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell dumpsys package org.droidtv.channels
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell dumpsys activity processes org.droidtv.channels
|
||||
```
|
||||
|
||||
Proceed only if the active code path is under `/data/app` and the untouched base
|
||||
path/hash matches `BASELINE-HASHES.md`.
|
||||
|
||||
## Authorized rollback concept
|
||||
|
||||
PackageManager documents:
|
||||
|
||||
```text
|
||||
cmd package uninstall-system-updates org.droidtv.channels
|
||||
```
|
||||
|
||||
This removes the update and falls back to the `/system` APK. It is **not** to be
|
||||
run from this document automatically. After explicit approval, record complete
|
||||
output, then verify active path/version/signer, provider stability and EPG data.
|
||||
|
||||
## Data warning
|
||||
|
||||
The base APK reappears, but an update-created schema or preferences may be
|
||||
incompatible. Never clear app data as a reflex. Preserve the pre-update DB and
|
||||
validate channels, Guide/Now/Next, favorites, recordings, reminders and timers.
|
||||
20
recovery/RESTORE-GEL.md
Normal file
20
recovery/RESTORE-GEL.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Restore GEL updated-system app
|
||||
|
||||
Applicable only to a `/data/app` update of `org.droidtv.gel` while ADB works.
|
||||
Read-only triage:
|
||||
|
||||
```powershell
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell pm path org.droidtv.gel
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell dumpsys package org.droidtv.gel
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell dumpsys dbinfo org.droidtv.gel -v
|
||||
```
|
||||
|
||||
Future rollback after explicit approval:
|
||||
|
||||
```text
|
||||
cmd package uninstall-system-updates org.droidtv.gel
|
||||
```
|
||||
|
||||
Then verify fallback to `/system/app/gel/gel.apk`, exact stock signer/hash,
|
||||
process stability, WorkManager DB compatibility and normal device telemetry/
|
||||
consent behavior. Do not `pm clear`; it is a separate destructive action.
|
||||
23
recovery/RESTORE-TPTVSERVICE.md
Normal file
23
recovery/RESTORE-TPTVSERVICE.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Restore TPTvService updated-system app
|
||||
|
||||
`org.droidtv.tv.tptvservice` is persistent/core and UID1000. A faulty update can
|
||||
create a more serious restart loop than Channels/GEL.
|
||||
|
||||
If ADB remains stable, first collect:
|
||||
|
||||
```powershell
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell pm path org.droidtv.tv.tptvservice
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell dumpsys package org.droidtv.tv.tptvservice
|
||||
.\platform-tools\adb.exe -s <TV_IP>:5555 shell dumpsys activity processes org.droidtv.tv.tptvservice
|
||||
```
|
||||
|
||||
The future PackageManager rollback command would be:
|
||||
|
||||
```text
|
||||
cmd package uninstall-system-updates org.droidtv.tv.tptvservice
|
||||
```
|
||||
|
||||
Run only with explicit approval. Verify fallback hash, boot/service stability,
|
||||
storage monitor behavior, TV power/standby, tuner/playback and no repeated
|
||||
crashes. If ADB is unstable or absent, do not improvise file deletion; switch to
|
||||
the OEM full-firmware recovery plan.
|
||||
22
recovery/RESTORE-TVFW.md
Normal file
22
recovery/RESTORE-TVFW.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Restore tvfw.jar
|
||||
|
||||
There is no PackageManager updated-system rollback for
|
||||
`/system/framework/tvfw.jar`. It is part of the AVB-protected system image with
|
||||
matching ODEX/VDEX and many Philips client class-loader contexts.
|
||||
|
||||
## If only an experimental volatile mount was used
|
||||
|
||||
Do not assume reboot is sufficient unless the exact experiment documented its
|
||||
mount lifecycle. Collect mount/process evidence if ADB works and follow that
|
||||
experiment's explicit teardown after approval. No such deployment was made in
|
||||
Phase 7.
|
||||
|
||||
## If a system image was changed
|
||||
|
||||
Do not copy the baseline JAR over a mounted system, delete ODEX/VDEX, remount rw,
|
||||
disable verity or write blocks. Restore the entire coherent, OEM-signed slot via
|
||||
a tested A/B or official full-firmware procedure. Verify all hashes and AVB
|
||||
green state after recovery.
|
||||
|
||||
Current classification: **HIGH RISK / no standalone approved restore path**.
|
||||
|
||||
26
recovery/USB-RECOVERY.md
Normal file
26
recovery/USB-RECOVERY.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# USB recovery checklist
|
||||
|
||||
## What is proven
|
||||
|
||||
Normal Android's Settings updater scans a USB filesystem for
|
||||
`/upgrades/autorun*.upg`, validates product/version metadata and hands an A/B
|
||||
payload to UpdateEngine. This is an update path, not yet a proven dead-boot
|
||||
recovery path.
|
||||
|
||||
## What is missing
|
||||
|
||||
- official full `autorun*.upg` for 43PUS7394/12 / `TPM191E_R`;
|
||||
- Philips SHA-256/provenance and release notes;
|
||||
- exact USB filesystem/layout requirements;
|
||||
- physical button/key sequence and whether AC insertion is required;
|
||||
- proof it operates when Android/ADB cannot boot;
|
||||
- display/LED progress and safe timeout;
|
||||
- downgrade/rollback restrictions.
|
||||
|
||||
## Do not proceed until
|
||||
|
||||
Philips/TPVision documentation and firmware are obtained from an official
|
||||
source, hashes are recorded, model/platform compatibility is explicit and an
|
||||
equivalent-device rehearsal succeeds. Do not use forum mirrors or similarly
|
||||
named firmware from another TPM platform. No USB recovery was executed.
|
||||
|
||||
24
tools/epg-health-check/EXAMPLE-OUTPUT.txt
Normal file
24
tools/epg-health-check/EXAMPLE-OUTPUT.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
Example A - live read-only check
|
||||
|
||||
Philips TPM191E EPG health check (READ-ONLY)
|
||||
Source: adb-dumpsys-dbinfo
|
||||
EPG DB: 8,724,480 bytes (8.32 MiB)
|
||||
/data free: 2975.8 MiB (39% used)
|
||||
Decision: OK
|
||||
- all available metrics are below empirical thresholds
|
||||
No repair was attempted.
|
||||
|
||||
Example B - historical defective offline snapshot
|
||||
|
||||
Philips TPM191E EPG health check (READ-ONLY)
|
||||
Source: offline-sqlite-snapshot
|
||||
EPG DB: 1,742,053,376 bytes (1661.35 MiB)
|
||||
Schedule rows: 3,071,250
|
||||
Oldest schedule age: >365 days
|
||||
Rows older than 30 days: >3,000,000
|
||||
SQLite quick_check: ok
|
||||
Decision: MAINTENANCE_RECOMMENDED
|
||||
- DB is at least 256 MiB
|
||||
- schedule rows >= 250,000
|
||||
- oldest entry >= 30 days and DB >= 64 MiB
|
||||
No repair was attempted.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user