CVE-2024-31314 AI-generated analysis
platform/frameworks/base, system_server ShortcutService (com.android.server.pm.ShortcutService / ShortcutPackage)
Adds a per-package, time-windowed throttle in ShortcutPackage so that ShortcutService forwards at most REPORT_USAGE_BUFFER_SIZE (3) shortcut-usage reports per mSaveDelayMillis window to UsageStatsManagerInternal.reportShortcutUsage, instead of forwarding every reportShortcutUsed/pushDynamicShortcut call unconditionally.
Root cause
ShortcutService.reportShortcutUsed (app-facing via ShortcutManager.reportShortcutUsed) and the pushDynamicShortcut path both called the private helper reportShortcutUsedInternal. That helper cleared the calling identity and invoked mUsageStatsManagerInternal.reportShortcutUsage on every call. On the reportShortcutUsed path, the only gate was that the shortcut id must exist in the caller's package. Nothing limited how often a package could make system_server do this usage-stats work.
How the fix works
- services/core/java/com/android/server/pm/ShortcutPackage.java: new method reportShortcutUsed(UsageStatsManagerInternal, String shortcutId). Inside synchronized(mLock) it keeps a List<Long> mLastReportedTime of SystemClock.elapsedRealtime() timestamps (new field, @GuardedBy mLock). If the list has fewer than REPORT_USAGE_BUFFER_SIZE (3) entries, the method appends the current timestamp and the report goes ahead. If the list is full and the oldest entry is older than mShortcutUser.mService.mSaveDelayMillis, the method evicts the oldest entry, appends the current timestamp, and the report goes ahead. Otherwise it returns without reporting. The call to usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId, getUser().getUserId()) is still wrapped in s.injectClearCallingIdentity()/injectRestoreCallingIdentity(token).
- services/core/java/com/android/server/pm/ShortcutService.java: mSaveDelayMillis changed from private to package-private so ShortcutPackage can read it. The private helper reportShortcutUsedInternal is deleted.
- services/core/java/com/android/server/pm/ShortcutService.java (pushDynamicShortcut path, around line 2285): the call reportShortcutUsedInternal(packageName, shortcut.getId(), userId) is replaced by ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()) on the ShortcutPackage ps.
- services/core/java/com/android/server/pm/ShortcutService.java reportShortcutUsed(): the ps variable is now declared outside the synchronized(mLock) block. The findShortcutById(shortcutId)==null check still logs a warning and returns early. After the block, the method calls ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId) instead of reportShortcutUsedInternal.
- Tests: ShortcutManagerTest1.testPushDynamicShortcut and ShortcutManagerTest2.testReportShortcutUsed now set KEY_SAVE_DELAY_MILLIS=1 so their existing expectations are not throttled. The new ShortcutManagerTest1.testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled sets KEY_SAVE_DELAY_MILLIS=500 and checks four things: after the first REPORT_USAGE_BUFFER_SIZE pushes, s1 was reported once; the pushes that follow, up to s10, produce no reports; a second package is throttled independently; and one more report per package goes through after a 505 ms sleep.
Completeness
- Both callers of the removed helper are covered: the pushDynamicShortcut path and the public reportShortcutUsed binder entry point now go through ShortcutPackage.reportShortcutUsed.
- Throttle state is kept per ShortcutPackage and only in memory. It is not shared across packages, so N packages can each send 3 reports per window. The window length is the existing, configurable mSaveDelayMillis (ConfigConstants.KEY_SAVE_DELAY_MILLIS), not a dedicated constant. Its default is not shown in the material.
- Only the forward to UsageStatsManagerInternal.reportShortcutUsage is throttled. The rest of the pushDynamicShortcut work (packageShortcutsChanged, verifyStates) and the findShortcutById lookup in reportShortcutUsed still run on every call. The diff does not change them.
- Dropped reports are silent: no log, no return value, no exception.
Behavior changes
- Per ShortcutPackage, at most 3 shortcut-usage reports reach UsageStatsManagerInternal within any mSaveDelayMillis window. Further reportShortcutUsed/pushDynamicShortcut calls in that window are silently dropped and do not update usage stats.
- The UsageStatsManagerInternal.reportShortcutUsage call now runs inside the ShortcutPackage synchronized(mLock) block. On the reportShortcutUsed path it used to run after ShortcutService's synchronized(mLock) block.
- ShortcutService.mSaveDelayMillis is now package-private instead of private, and reportShortcutUsedInternal is removed.
- Tests that expect every push to be reported must set KEY_SAVE_DELAY_MILLIS to a small value. The fix does this in two existing tests.
Per major
- Android 12/12L/13: listed as affected by the bulletin, but the material has no per-major facts or commits for these branches; status unknown.
- Android 14: same fix: commit 6dc0f712dbe7 in android-security-14.0.0_r9 (cherry-pick of bd88f35c6797, Change-Id I96370cbd4f6a55f894c1a93307e5f82dfd394652). At android-security-14.0.0_r29, all 8/8 lines of the patched region are present.
- Android 15: not affected per the bulletin; the patched region (8/8 lines) is present at android-security-15.0.0_r17.
- Android 16: not affected per the bulletin; the patched region (8/8 lines) is present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin; the patched region (8/8 lines) is present at android-security-17.0.0_r1.
Unknowns
- The default value of mSaveDelayMillis, and so the effective throttle window in production, is not shown.
- The material does not describe the concrete DoS effect of unthrottled reportShortcutUsage calls in UsageStatsService.
- Whether and how the fix was applied to Android 12, 12L and 13, which the bulletin lists as affected.
- Whether older branches have the same ShortcutPackage locking structure and mSaveDelayMillis field.
- Which lock, if any, was held around the usage report on the pushDynamicShortcut path before the fix.