Unbounded TaskInfo serialization in ActivityTaskManagerService

Saturday, September 26, 2026
Unbounded TaskInfo serialization in ActivityTaskManagerService

Unbounded TaskInfo serialization in ActivityTaskManagerService leads to persistent-process (SystemUI) crash loop and device-level denial of service

Reporter: Manuel García Peña ("lostmon") — independent security researcher

Contact: lostmon.blogspot.com

Project reference: STA Research (Structured Text Amplification)

Date: 20 September 2026

Component: Android Open Source Project — frameworks/base (ActivityTaskManagerService, Task, TaskOrganizerController, TaskInfo)

Affected versions: Confirmed on Android 16 (device build below); the root cause traces to a serialization path that has been present, in incomplete form, since at least the March 2020 commit referenced in §5 — likely affects a broad range of Android versions. Requires confirmation by Google against the AOSP version matrix.

Test device: Redmi Note 14 5G (citrine_eea_global), build BP2A.250605.031.A3 / HyperOS 3.0 (OS3.0.302.0.WOQEUXM), Android 16. Reported separately to OEM (Xiaomi) for OEM-specific escalation behavior — see companion report.


1. Summary

Task.fillTaskInfo() serializes a Task's Intent data Uri and capturedLink field into TaskInfo/RootTaskInfo/RunningTaskInfo/RecentTaskInfo without any upper bound on size. This object is transferred over Binder both when the platform pushes task-organizer callbacks (ITaskOrganizer.onTaskInfoChanged, addStartingWindow) and when a client pulls task state (getFocusedRootTaskInfo, getTasks, getRecentTasks). When the underlying Intent/link data is large enough, the resulting Binder transaction exceeds the transaction buffer and fails with TransactionTooLargeException (push direction) or leaves the caller in a state where subsequent reads fail with DeadObjectException/DeadSystemException (pull direction, once system_server itself becomes unable to service the request).

SystemUI — a platform-critical, persistent process — does not degrade gracefully when these calls fail. The uncaught/unhandled exception propagates up through multiple independent SystemUI components that all query "current focused task" or "recent tasks," causing SystemUI to be killed and restarted repeatedly by ActivityManagerService ("Process com.android.systemui has crashed too many times, killing!"), which in turn triggers the platform's RescueParty self-healing subsystem through an escalating series of mitigations. Depending on how many times the trigger repeats, the outcome ranges from a forced device reboot to an OEM-level "safe mode" recovery requiring manual user intervention.

This is reachable with a single user tap on a link that opens in the default browser — no special permissions, no user interaction beyond a single tap, fully reproducible.


2. Affected code paths (AOSP frameworks/base)

PathDirectionAPI
services/core/java/com/android/server/wm/TaskOrganizerController.javaPushITaskOrganizer.onTaskInfoChanged, addStartingWindow
services/core/java/com/android/server/wm/ActivityTaskManagerService.javaPullgetFocusedRootTaskInfo
core/java/android/app/ActivityTaskManager.javaPullgetTasks, getRecentTasks
services/core/java/com/android/server/wm/Task.javaShared serialization pointfillTaskInfo(), trimIneffectiveInfo()
core/java/android/app/TaskInfo.javaShared serialization pointwriteTaskToParcel(), field capturedLink

All five paths converge on Task.fillTaskInfo(), which is the single point where the defect should be fixed.


3. Root cause

3.1 Task.fillTaskInfo() does not bound the Intent data Uri or capturedLink

void fillTaskInfo(TaskInfo info, boolean stripExtras, @Nullable TaskDisplayArea tda) {
    ...
    final Intent baseIntent = getBaseIntent();
    final int baseIntentFlags = baseIntent == null ? 0 : baseIntent.getFlags();
    info.baseIntent = baseIntent == null
            ? new Intent()
            : stripExtras ? baseIntent.cloneFilter() : new Intent(baseIntent);
    info.baseIntent.setFlags(baseIntentFlags);
    ...
    // capturedLink is not cleared or bounded here; it is only nulled in
    // trimIneffectiveInfo() for non-privileged callers.
}

3.2 The 2020 mitigation is incomplete — verified against source

Commit 66b08f0201877e6013058fda2f0263016338c770 ("Reduce parceled data size between system & sysui", Winson Chung, 3 March 2020, Bug: 150242007) changed:

- info.baseIntent = baseIntent == null ? new Intent() : new Intent(baseIntent);
+ info.baseIntent = baseIntent == null ? new Intent() : baseIntent.cloneFilter();

This removes Bundle extras and ClipData from the serialized baseIntent. It does not bound:

  • the Intent's data Uri (cloneFilter() explicitly preserves action, data, type, categories, flags, package, component);
  • the capturedLink field, which is a separate TaskInfo field entirely untouched by this commit and only cleared in trimIneffectiveInfo() for non-privileged callers — SystemUI and TaskOrganizer receive it unfiltered.

The test added in the same commit, testTaskInfo_expectNoExtras, only asserts that baseIntent.getExtras() is null/empty — it does not assert any bound on data-field length.

3.3 TaskInfo.writeTaskToParcel() has no length limit on variable-size fields

dest.writeTypedObject(baseIntent, 0);       // data Uri, post-cloneFilter, unbounded
dest.writeTypedObject(capturedLink, flags); // full Uri when not trimmed, unbounded

3.3 trimIneffectiveInfo() clears capturedLink only for non-privileged callers

info.capturedLink = null;
info.capturedLinkTimestamp = 0;

This clearing is applied only when the caller is not privileged. SystemUI and TaskOrganizer — precisely the components affected by this defect — are privileged callers and receive capturedLink unfiltered.

3.4 TaskInfo.writeTaskToParcel() has no length limit on variable-size fields

dest.writeTypedObject(baseIntent, 0);       // data Uri, post-cloneFilter, unbounded
dest.writeTypedObject(capturedLink, flags); // full Uri when not trimmed, unbounded

RootTaskInfo adds fixed-size bounds/children fields and then delegates the rest to the same writeTaskToParcel().

3.5 Confluence of push and pull paths

                    ├─ onTaskInfoChanged / addStartingWindow   (push, §5.1)
Task.fillTaskInfo ──┼─ getFocusedRootTaskInfo                  (pull, §5.2, variants A/B)
                    ├─ getTasks                                (pull, §5.2, variant C)
                    └─ getRecentTasks                          (pull, §5.2, variant E)

This explains why five structurally distinct SystemUI call sites (different classes, different threads, different public APIs) all fail with the same root cause: every one of them ultimately calls Task.fillTaskInfo()/writeTaskToParcel() on the same oversized Task object.

3.6 SystemUI does not degrade gracefully on ATMS binder failure

Independently of the above, the Binder client side of this contract — SystemUI's various components that call getFocusedRootTaskInfo/getTasks/getRecentTasks — does not catch DeadObjectException/DeadSystemException and fall back to a last-known-good state or a safe default. The exception propagates and crashes the calling component, and because these components (Dynamic Island top-activity tracking, notification shade token interactor, recents controller) are on SystemUI's main/critical threads, repeated failures cause ActivityManagerService to conclude the persistent process is unrecoverable and kill it outright.


4. Reproduction

  1. Construct an Intent/link whose data Uri (and/or any field that populates TaskInfo.capturedLink) is large — on the order of hundreds of kilobytes. In our reproduction this was delivered as a link inside an HTML document opened via a cloud-storage viewer, which the OS resolved to the default browser (Firefox, in our test — confirmed not to be the origin of the fault; see §7).
  2. The browser is launched as a new Task; system_server/ATMS retains this oversized data as part of the Task's baseIntent/captured-link state.
  3. As TaskOrganizerController dispatches pending task-info-changed / starting-window events for this task, and/or as SystemUI's focus/recents observers poll getFocusedRootTaskInfo/getTasks/getRecentTasks, the oversized TaskInfo is serialized to Binder.
  4. Observe TransactionTooLargeException (push) and/or DeadObjectException/DeadSystemException (pull) in dumpsys dropbox -p system_app_crash and/or logcat.
  5. Repeat exposure (re-opening the link while the browser remains foregrounded) escalates the failure into a sustained SystemUI crash loop.

We can provide the full sequence of bugreports (adb bugreport, 4 captures across 2 dates on the same device/build) documenting both directions with complete stack traces on request through the appropriate secure channel. We are withholding the exact payload construction from this initial report per responsible-disclosure practice; happy to share it privately with the assigned engineer.


5. Evidence (redacted stack excerpts)

5.1 Push — TransactionTooLargeException, kernel-confirmed (16-09-2026, 11:26–11:28, same device)

TaskOrganizerController: android.os.TransactionTooLargeException: data parcel size 365036 bytes
    at android.window.ITaskOrganizer$Stub$Proxy.onTaskInfoChanged(ITaskOrganizer.java:513)
    at com.android.server.wm.TaskOrganizerController$TaskOrganizerCallbacks.onTaskInfoChanged(TaskOrganizerController.java:191)
    ...
    at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:923)
    at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:173)

Kernel binder driver, independently confirming the same failure:

binder: 2124:3180 transaction async to 20444:0 failed 7086360/29201/-28, code 1 size 365788-48 line 3548
binder: 2124:2173 transaction async to 20444:0 failed 7086449/29201/-28, code 7 size 365036-40 line 3548

ActivityManager log line for the same event: pid 2124 system sent binder code 7 with flags 1 to frozen apps and got error -2147483646 — the target process (SystemUI) was in the platform's cached/frozen state at time of delivery, which appears to aggravate the failure.

5.2 Pull — complete evidence set: 25 recorded instances across 3 sessions, 5 distinct call sites

All 25 instances were extracted from dumpsys dropbox -p system_app_crash across three independent bugreports on the same device/build (19–20 September 2026). Every instance shares the same two root exceptions (DeadObjectException or DeadSystemException); they differ only in which SystemUI component happened to be polling task state at the moment system_server/ATMS became unable to service the request.

Frequency by call site:

VariantComponentAPIInstances
Ccom.android.systemui.shared.system.ActivityManagerWrapper.getRunningTask (via com.miui.systemui.functions.MiuiTopActivityObserver)getTasks12
Acom.android.systemui.statusbar.notification.policy.DynamicIslandTopActivityControllergetFocusedRootTaskInfo5
Bcom.miui.systemui.statusbar.shade.domain.interactor.ShadeStatusBarTokenInteractorgetFocusedRootTaskInfo5
Dcom.android.systemui.statusbar.notification.InstantAppNotifier(stack truncated by system, root cause DeadSystemException)2
Ecom.android.wm.shell.recents.RecentTasksController.getRecentTasksgetRecentTasks1

Complete timestamped log (25/25 instances, PID and exception type):

2026-09-19 23:32:04  PID 2858   DeadObjectException      Variant A
2026-09-19 23:39:14  PID 21592  DeadSystemException       Variant D
2026-09-19 23:39:28  PID 29332  DeadSystemException       Variant C
2026-09-19 23:39:40  PID 29982  DeadObjectException       Variant B
2026-09-19 23:39:49  PID 30266  DeadSystemException       Variant D
2026-09-19 23:39:56  PID 30579  DeadSystemException       Variant C
2026-09-19 23:42:14  PID 5351   DeadSystemException       Variant C
2026-09-20 00:25:16  PID 2847   DeadSystemException       Variant D
2026-09-20 00:25:24  PID 29311  DeadSystemException       Variant C
2026-09-20 00:25:30  PID 29616  DeadSystemException       Variant C
2026-09-20 00:25:33  PID 29806  DeadSystemException       Variant C
2026-09-20 00:25:39  PID 29952  DeadSystemException       Variant C
2026-09-20 00:25:46  PID 30137  DeadSystemException       Variant C
2026-09-20 14:10:27  PID 2558   DeadObjectException       Variant B
2026-09-20 14:10:44  PID 22995  DeadObjectException       Variant A
2026-09-20 14:10:47  PID 23667  DeadSystemException       Variant C
2026-09-20 14:10:55  PID 24079  DeadSystemException       Variant C
2026-09-20 14:11:09  PID 24700  DeadObjectException       Variant A
2026-09-20 14:11:20  PID 25138  DeadObjectException       Variant B
2026-09-20 14:32:12  PID 26950  DeadObjectException       Variant B
2026-09-20 14:32:28  PID 6263   DeadObjectException       Variant B
2026-09-20 14:32:31  PID 6809   DeadSystemException       Variant C
2026-09-20 14:32:46  PID 6983   DeadObjectException       Variant A
2026-09-20 14:32:52  PID 7317   DeadSystemException       Variant C
2026-09-20 14:33:01  PID 7535   DeadSystemException       Variant E

Entry #25 (14:33:01, RecentTasksController.getRecentTasks) is the last crash of the entire sequence, immediately before the device stabilized — the fault is not limited to "resolve current focus," it extends to enumerating the recent-tasks list in general, which we consider additional evidence that the defect is in the shared Task/TaskInfo state rather than in any single SystemUI component's handling of it.

Full stack — Variant A (DynamicIslandTopActivityController):

android.os.DeadObjectException: Transaction failed on small parcel; remote process
probably died, but this could also be caused by running out of binder buffer space
    at android.os.BinderProxy.transactNative(Native Method)
    at android.os.BinderProxy.transact(BinderProxy.java:736)
    at android.app.IActivityTaskManager$Stub$Proxy.getFocusedRootTaskInfo(IActivityTaskManager.java:3682)
    at com.android.systemui.statusbar.notification.policy.DynamicIslandTopActivityController
        $special$$inlined$mapNotNull$1$2.emit(...)
    at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(...)

Full stack — Variant B (ShadeStatusBarTokenInteractor):

android.os.DeadObjectException: Transaction failed on small parcel; remote process
probably died, but this could also be caused by running out of binder buffer space
    at android.os.BinderProxy.transactNative(Native Method)
    at android.os.BinderProxy.transact(BinderProxy.java:736)
    at android.app.IActivityTaskManager$Stub$Proxy.getFocusedRootTaskInfo(IActivityTaskManager.java:3682)
    at com.miui.systemui.statusbar.shade.domain.interactor.ShadeStatusBarTokenInteractor
        $special$$inlined$mapNotNull$1$2.emit(go/retraceme...:62)
    at com.miui.systemui.statusbar.shade.domain.interactor.ShadeStatusBarTokenInteractor
        $special$$inlined$filter$1$2.emit(go/retraceme...:64)
    at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(go/retraceme...:194)

Full stack — Variant C (ActivityManagerWrapper.getRunningTask, the most frequent — 12/25):

android.os.DeadSystemRuntimeException: android.os.DeadSystemException
    at android.app.ActivityTaskManager.getTasks(ActivityTaskManager.java:480)
    at android.app.ActivityTaskManager.getTasks(ActivityTaskManager.java:454)
    at com.android.systemui.shared.system.ActivityManagerWrapper.getRunningTask(...:5)
    at com.miui.systemui.functions.MiuiTopActivityObserver$$ExternalSyntheticLambda0.run(...:71)
    at android.os.Handler.handleCallback(Handler.java:1029)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loopOnce(Looper.java:274)
    at android.os.Looper.loop(Looper.java:369)
    at android.os.HandlerThread.run(HandlerThread.java:85)
Caused by: android.os.DeadSystemException
    ... 9 more

Full stack — Variant D (InstantAppNotifier):

android.os.DeadSystemRuntimeException: android.os.DeadSystemException
    at com.android.systemui.statusbar.notification.InstantAppNotifier$$ExternalSyntheticLambda0.run(...:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1100)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:1572)
Caused by: android.os.DeadSystemException
    ... 4 more

Full stack — Variant E (RecentTasksController.getRecentTasks, the final crash of the sequence):

android.os.DeadSystemRuntimeException: android.os.DeadSystemException
    at android.app.ActivityTaskManager.getRecentTasks(ActivityTaskManager.java:539)
    at com.android.wm.shell.recents.RecentTasksController.getRecentTasks(...:10)
    at com.android.wm.shell.recents.RecentTasksController$IRecentTasksImpl$$ExternalSyntheticLambda1.accept(...:11)
    at com.android.wm.shell.common.ExternalInterfaceBinder$$ExternalSyntheticLambda0.run(...:18)
    at com.android.wm.shell.common.ShellExecutor$$ExternalSyntheticLambda0.run(...:5)
    at android.os.Handler.handleCallback(Handler.java:1029)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loopOnce(Looper.java:274)
    at android.os.Looper.loop(Looper.java:369)
    at android.os.HandlerThread.run(HandlerThread.java:85)
Caused by: android.os.DeadSystemException
    ... 10 more

5.3 Terminating action (ActivityManagerService)

Captured live during the 20-09-2026 14:32 reproduction:

14:32:52.874  ActivityManager: Process com.android.systemui has crashed too many times, killing! Reason: crashed quickly
14:32:52.874  RescueParty: Checking available remediations for health check failure. failedPackage: com.android.systemui failureReason: 3 available impact: 50
14:32:52.884  RescueParty: Executing remediation. failedPackage: com.android.systemui failureReason: 3 mitigationCount: 3

RollbackPackageHealthObserver reports available impact: 0 for the same event — no Mainline/APEX rollback candidate exists for com.android.systemui on this build, so the platform's self-healing falls through entirely to the OEM's own escalation logic (documented in the companion Xiaomi report).


6. Impact

  • Class: Denial of service, platform-level.
  • User interaction: One tap on an attacker-controlled or attacker-influenced link.
  • Privileges required: None.
  • Effect: Forced lockscreen, sustained SystemUI crash loop, and — depending on repeat exposure — full device reboot or entry into an OEM-defined degraded/safe-mode recovery state requiring the user to manually restart the device. No code execution is claimed.
  • Reliability: Deterministic across 25/25 observed crash instances in our testing; consistent across two dates four days apart on the same build.

7. Note on browser involvement

We initially suspected the browser itself (Chrome) was implicated, based on a temporally-adjacent ANR in our first capture. On closer inspection of the activity-resume records across all sessions, the browser that actually receives the triggering intent in every case is Firefox (org.mozilla.fenix.IntentReceiverActivity). Firefox shows no crash, ANR, or tombstone of its own in any of the four bugreports we collected — it is the intent receiver, not a party to the defect. We flag this so the assigned engineer does not spend time chasing a red herring; the defect is entirely within the ATMS/SystemUI Binder contract described above.


8. Suggested remediation

  1. Introduce a length bound on the Intent data Uri and capturedLink in Task.fillTaskInfo() / TaskInfo.writeTaskToParcel() — truncate, hash, or replace with a placeholder above a reasonable threshold before serialization, extending the intent of commit 66b08f0 to cover these two fields.
  2. Have TaskOrganizerController measure/cap the serialized size of a TaskInfo before dispatching push callbacks, rather than relying on the caller to catch TransactionTooLargeException after the fact.
  3. Harden SystemUI's focus/recents observers (and any similar persistent-process client of these APIs) to catch DeadObjectException/DeadSystemException and fall back to last-known-good state instead of propagating the exception to a crash.

9. Attachments available on request

  • 4 full adb bugreport captures (device: Redmi Note 14 5G, build BP2A.250605.031.A3), dated 16-09-2026 and 20-09-2026 (×3).
  • Consolidated internal forensic report cross-referencing all four captures against AOSP source.

10. Researcher background

Independent security researcher (handle "lostmon"), prior public vulnerability credits from Microsoft, Google, and Mozilla. President of the BojosXtu association (civic/educational digital-rights work). Blog: lostmon.blogspot.com.

 

Browse

About:Me

My blog:http://lostmon.blogspot.com
Mail:Lostmon@gmail.com
Lostmon Google group
Lostmon@googlegroups.com

La curiosidad es lo que hace
mover la mente...

Friends