CVE-2026-49887 AI-generated analysis
platform/frameworks/base, packages/PackageInstaller (v2 install flow, InstallRepository)
Fixes an inverted boolean in PackageInstaller v2's InstallRepository.maybeRemoveInvalidInstallerPackageName so that a mismatching EXTRA_INSTALLER_PACKAGE_NAME is stripped when the caller lacks INSTALL_PACKAGES, rather than when it holds it.
Root cause
In packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt, maybeRemoveInvalidInstallerPackageName() is meant to drop a caller-supplied Intent.EXTRA_INSTALLER_PACKAGE_NAME that differs from the calling package unless the caller holds android.permission.INSTALL_PACKAGES. The condition was written as `&& isPermissionGranted(packageManager, INSTALL_PACKAGES, callerInfo.packageName)`, the opposite of the intended `!isPermissionGranted(...)`. As a result, a caller without INSTALL_PACKAGES kept its arbitrary installer package name in the intent (the extra was never removed), while callers that did hold the permission had their installer name removed. The commit message states the inversion was introduced while porting the V1 PackageInstaller logic to V2.
How the fix works
- InstallRepository.kt:maybeRemoveInvalidInstallerPackageName: the third conjunct of the if-condition is changed from `isPermissionGranted(packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName)` to `!isPermissionGranted(...)`. With the fix, the extra is handled only when three things hold: the intent's EXTRA_INSTALLER_PACKAGE_NAME differs from callerInfo.packageName, callerInfo.packageName is non-null, and the caller does NOT hold INSTALL_PACKAGES. In that case the code logs an error, writes EventLog tag 0x534e4554 with id 236687884 and the caller uid, and calls intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME).
- No other logic is changed. This is a one-line, one-character fix.
Completeness
- The fix restores the intended check at the single site in InstallRepository.maybeRemoveInvalidInstallerPackageName. The rest of the condition (name mismatch and non-null caller package) is unchanged.
- The check only runs when callerInfo.packageName is non-null. If the caller package cannot be determined, the extra is left in place by both the old and the new code, so this fix does not change that case.
- The material does not show the V1 PackageInstaller counterpart of this check, so it cannot confirm whether V1 is correct. The commit message implies V1 had the correct form.
Behavior changes
- Callers without INSTALL_PACKAGES that supply an EXTRA_INSTALLER_PACKAGE_NAME different from their own package now have that extra removed, with a Log.e and an EventLog 0x534e4554/236687884 entry.
- Callers that hold INSTALL_PACKAGES and supply a different installer package name now keep that extra. Before the fix it was stripped.
Per major
- Android 14: not affected: not listed by the bulletin; packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt is absent at android-security-14.0.0_r29.
- Android 15: not affected per the bulletin; at android-security-15.0.0_r17 the resolver reports 4/4 lines of the patched region present. The material does not show whether the condition there is negated.
- Android 16: not affected per the bulletin for base 16 (only 16-qpr2 is listed); at android-security-16.0.0_r8 the resolver reports 4/4 lines of the patched region present. The material does not show whether the condition there is negated.
- Android 17: same fix: commit 110da4cfe2a8 (cherry-pick of 9dbc12bb1ebb, Change-Id I6c336109c5422170d993adba901d5a025217baa1) in android-security-17.0.0_r1, resolved by bug_id. The resolver reports 3/4 lines of the patched region present at that tag, and the code context shows `!isPermissionGranted` at line 357.
Unknowns
- The material does not explain why the bulletin lists 16-qpr2 as affected while the 15 and 16 security tags contain the patched region. It does not include the 16-qpr2 state of the file or the commit that introduced the inversion.
- Whether the condition at 15.0.0_r17 and 16.0.0_r8 carries the negation is not shown; the line-presence metric does not distinguish the two forms.
- The material does not explain why the Android 17 resolver reports only 3/4 lines of the patched region present when the code context contains the fixed condition.
- The material does not show the downstream consequence of a retained spoofed EXTRA_INSTALLER_PACKAGE_NAME, that is, how later code consumes the extra.