CVE-2025-48588 AI-generated analysis
platform/frameworks/base, system_server connectivity (services/core/java/com/android/server/connectivity/Vpn.java, always-on VPN)
Vpn.startAlwaysOnVpn() no longer resets the always-on VPN configuration (setAlwaysOnPackage(null, false, null)) when the always-on package is deemed unsupported while the device is in safe mode; per the code comment, the always-on VPN can then start after rebooting to normal mode.
Root cause
In Vpn.startAlwaysOnVpn(), when isAlwaysOnPackageSupported(alwaysOnPackage) returned false the code unconditionally called setAlwaysOnPackage(null, false, null), removing the always-on VPN package (with lockdown false). The commit message states that in safe mode third-party VPN apps have limited functionality and can be deemed unsupported for always-on use, so booting into safe mode caused the always-on VPN configuration to be removed rather than merely not started for that boot. The Android 15 test (testStartAlwaysOnVpnOnSafeMode) models this by making the VPN profile store and queryIntentServicesAsUser return null while isSafeMode() returns true, and asserts the always-on package survives.
How the fix works
- services/core/java/com/android/server/connectivity/Vpn.java:startAlwaysOnVpn(): inside the existing `if (!isAlwaysOnPackageSupported(alwaysOnPackage))` branch, the call setAlwaysOnPackage(null, false, null) is now wrapped in `if (!mContext.getPackageManager().isSafeMode())`. In safe mode the setting is left intact; the method still returns false so no VPN is started. Outside safe mode behavior is unchanged.
- Android 15 commit fccd4c6bc347 additionally adds services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java:testStartAlwaysOnVpnOnSafeMode, which sets an always-on package, simulates safe mode (isSafeMode() true, profile store and queryIntentServicesAsUser returning null), calls startAlwaysOnVpn() and asserts it returns false while getAlwaysOnPackage() still equals TEST_VPN_PKG. The Android 14 commit da768ae60459 contains only the Vpn.java change (commit message says Test: manual, VpnTest).
Completeness
- Only the startAlwaysOnVpn() path is guarded. The fix does not change isAlwaysOnPackageSupported() itself; in safe mode the package is still reported unsupported and the always-on VPN is not started for that boot.
- Any other code path that resets the always-on VPN configuration is not touched by this diff; the material shows no such path, so whether others exist is unknown.
- The guard relies on PackageManager.isSafeMode(); no other restricted-boot condition is considered.
Behavior changes
- In safe mode, an always-on VPN package that fails isAlwaysOnPackageSupported() no longer causes setAlwaysOnPackage(null, false, null) to be called; startAlwaysOnVpn() still returns false (VPN not started).
- getAlwaysOnPackage() still returns the configured package afterwards (as asserted by the A15 test), so per the code comment the always-on VPN can start after rebooting to normal mode.
- No change to behavior outside safe mode.
Per major
- Android 14: same fix: commit da768ae60459 in android-security-14.0.0_r24 (Vpn.java only, no test file); at android-security-14.0.0_r29 the patched region is fully present (4/4 lines).
- Android 15: same fix plus test: commit fccd4c6bc347 in android-security-15.0.0_r12 changes Vpn.java identically (hunk at ~1109) and adds VpnTest.testStartAlwaysOnVpnOnSafeMode; at android-security-15.0.0_r17 7/7 lines present.
- Android 16: not affected per the bulletin (not listed); at android-security-16.0.0_r8 the patched region is present (4/4 lines).
- Android 17: not affected per the bulletin (not listed); at android-security-17.0.0_r1 the patched region is present (4/4 lines).
Unknowns
- Who calls startAlwaysOnVpn() and how the device ends up in safe mode in the EoP scenario; the material does not show the trigger.
- How setAlwaysOnPackage(null, false, null) persists the reset (e.g. which Settings keys are written); not shown.
- Whether other paths exist that reset the always-on VPN setting under restricted conditions.
- Why the bulletin lists 16-qpr2-next as affected while the per-major facts treat Android 16 as not listed; the code at android-security-16.0.0_r8 already contains the fix.