跪拜 Guibai
← All articles
Android · Reverse Engineering

A Samsung OneUI Bug Leaked `top` Processes for Years, Crashing Banking and Government Apps Across China

By k3x1n ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The bug had been silently crashing Chinese banking, tax, railway, and carrier apps on Samsung devices for years without generating crash reports, because the kill came from AMS rather than an app-level exception. It demonstrates how a single unclosed subprocess in a vendor-customized APEX module can degrade an entire device, and why shelling out to command-line tools from a system service is a pattern worth auditing in any Android skin.

Summary

Samsung's Chinese OneUI builds contain a CPU-monitoring routine in `service-samsung-shell.jar` that calls `top -b -n 1` via `Runtime.exec`. The reader loop exits after 8 lines without ever calling `destroy()` or `close()`, leaving the `top` process orphaned as a phantom process owned by `system_server`. Each cold start or app install triggers the call when free storage drops below 3 GB, with a minimum 5-minute cooldown between invocations.

Once 32 phantom processes accumulate, Android's `trimPhantomProcessesIfNecessary` begins killing them in order of parent oom_adj. Because `system_server` runs at oom_adj -900, the cleanup logic preferentially terminates phantom processes belonging to ordinary apps. Apps protected by the enterprise version of the Bangcle packer fork a detection child process; when that child is killed, the parent app exits immediately, producing a crash that APM platforms never see because it is not a native crash.

The bug only affects mainland China firmware, where a region-gated `mIpmAntiAgingController` enables the code path. OneUI 8.5 replaces the `top` call with a proc-filesystem read, closing the leak. Users who enabled "Disable child process restrictions" merely masked the symptom while `system_server` accumulated hundreds of leaked processes and file descriptors, sometimes causing spontaneous reboots.

Takeaways
`Runtime.getRuntime().exec("top -b -n 1")` in `service-samsung-shell.jar` creates a `top` process that is never destroyed or closed after the reader loop exits early at 8 lines.
Each leaked `top` process is recorded as a phantom process owned by `system_server` (oom_adj -900), so Android's phantom-process trimmer kills app-owned phantom processes first.
Apps using the enterprise version of the Bangcle packer fork a child process for environment detection; when that child is killed, the parent app exits immediately, mimicking a crash.
The trigger is gated on available storage falling below 3 GB and only activates on mainland China firmware builds, where `mIpmAntiAgingController` is assigned.
Cold-starting an app or installing a new app invokes the buggy code path, but a cooldown of at least 5 minutes (30 seconds per GB of free space, minimum 300 seconds) spaces out the leaks.
OneUI 8.5 replaces the `top` call with a direct read from `/proc`, eliminating the process leak.
Enabling "Disable child process restrictions" in Developer Options stops the crashes but allows `system_server` to accumulate hundreds of leaked processes and file descriptors, eventually causing spontaneous reboots.
Conclusions

A single unclosed subprocess in a vendor APEX module evaded detection for years because the resulting app exits were not crashes — AMS killed the process, so APM tools never collected a stack trace.

Parsing `top` output by hardcoded column index is fragile across Android versions; the code also reads column 10 (MEM) instead of column 9 (CPU), making the CPU measurement useless even when it runs.

The phantom-process trimmer sorts by parent oom_adj, which means a leak in a high-priority system process punishes ordinary apps rather than the leaker — a design that turns a resource leak into a user-visible denial of service.

Region-gating the bug behind a China-only code path, combined with Samsung's low Chinese market share, meant the issue received almost no engineering attention despite affecting essential government and financial apps.

Concepts & terms
Phantom process
A child process forked by an Android app (not by Zygote) that the framework cannot directly manage. Android 12+ limits phantom processes to 32 by default and trims the excess based on the parent process's oom_adj.
APEX module
Introduced in Android 10, APEX (Android Pony EXpress) packages system components into separately updatable modules stored under /apex, allowing OEMs and Google to update system code without a full OTA.
oom_adj
A per-process priority score used by Android's low-memory killer. Lower values mean higher priority; system_server runs at -900, foreground apps at 0, and background apps at higher positive values.
Bangcle enterprise packer
A Chinese app-hardening solution that encrypts DEX and native code. Its enterprise version forks a child process for environment integrity checks; if that child is killed, the parent app exits.
From the discussion

The discussion is thin — one joke and one note of personal vindication. No substantive debate or competing technical views surface.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗