Skip to content
HN On Hacker News ↗

Android NAT-T Keepalive Offload Bypasses VPN Lockdown: Device-Class Exposure Across Most Android 12+ Devices | Armin Šupuk

▲ 213 points • 60 comments • by mhitza • 4w ago • HN discussion ↗

Pangram verdict · v3.3

We believe this text is mainly AI, with some human-written content.

99 %

AI likelihood · overall

AI
1% human-written 99% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,337
PEAK AI % 99% · §1
Analyzed
Sep 12
backend: pangram/v3.3
Segments scanned
1 windows
avg 1337 words each
Distribution
1 / 99%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,337 words · 1 segments analyzed

Human AI-generated
§1 AI · 99%

1. AbstractAndroid’s Always-on VPN and “Block connections without VPN” settings create a user-visible expectation that traffic attributable to covered applications will not leave through a non-VPN path. A normal application can violate that boundary through Android’s public NAT-T socket-keepalive API, causing clear, fixed-format UDP/4500 packets to reach the physical router outside the VPN path. The runtime evidence has three levels. A controlled access-point capture on a Pixel 8 Pro running Android 16 build CP1A.260505.005 recorded the packets at the public minimum 10-second interval while Always-on VPN and lockdown were enabled. A Samsung SM-F966B running Android 16 exposed one active Wi-Fi slot through the same public path; VPN Leak Guard selected the physical IPv4 default gateway, observed the active callback, and recorded a continuous router-directed active-slot lease for 24 h 32 min. On a Nothing A059 (Asteroids) running Android 16, the same implementation selected the physical gateway and recorded one active Wi-Fi slot. The Nothing result confirms public-path admission and the active callback on a third OEM. No independent packet capture or duration measurement was collected for that device. The Pixel lifecycle matrix covered backgrounding, lock, Doze, battery saver, restricted standby bucket, Binder freezer, and the observed force-stop, uninstall, network-loss, and reboot boundaries.Source history traces the failure to a collapsed trust model in startNattKeepaliveWithFd(...): a privileged raw-fd API evolved into a public UdpEncapsulationSocket path, resource validation was added and reverted, and admission no longer authenticates the fd/resource pair or enforces the original caller UID’s current VPN policy before offload. In an F-Droid/IzzyOnDroid study of 4,679 distinct stored Git origins, the scanner detected no Android framework IPsec, IKE, or NAT-T API use; manual audit found 73 Android VpnService apps. Runtime confirmation across three OEMs and two confirmed WLAN families, the shared Android 12+ framework path, and firmware coverage across seven WLAN families representing 91.24% of estimated Android-derived shipments establish device-class exposure affecting most Android 12+ devices. The remaining 8.76% is unresolved.2. IntroductionVPN lockdown governs routing and confinement in addition to encryption. Users and administrators expect covered applications to fail closed when the VPN is unavailable and to withhold their real network identity from destinations outside the tunnel. Prior VPN-leak research has studied routing exceptions, IPv6 and DNS leaks, WebRTC address exposure, VPN client ecosystems, and shared VPN infrastructure failures [1; 2; 3; 4; 5; 6; 7]. Android also delegates some application-triggered packet emission to system_server, a NetworkAgent, a hardware abstraction layer, or firmware, beyond the application’s ordinary socket send path.A normal application can cross that boundary through the public Android-managed IpSecManager.UdpEncapsulationSocket and ask ConnectivityManager.createSocketKeepalive(...) to maintain a NAT-T mapping. The framework routes the request through startNattKeepaliveWithFd(...), accepts the duplicated fd and resource ID without proving current caller-owned IpSec resource identity, and hands a completed NAT-T keepalive packet to the Wi-Fi keepalive offload path without first enforcing the caller UID’s effective VPN-lockdown policy. A controlled Pixel 8 Pro capture recorded the resulting UDP/4500 packet on the physical access-point interface. Active-slot observations on two additional OEMs exercised the same public physical-gateway path on Qualcomm hardware [8; 9; 10; 11; 12; 13; 14; 15; 16; 17].Recent Android Automotive access-control work identified ConnectivityService.startNattKeepaliveWithFd in a broad sweep of framework permission anomalies because a related keepalive API enforced PACKET_KEEPALIVE_OFFLOAD and the fd-based path did not [18; 19; 20]. That work reported the permission inconsistency. It did not trace the public UdpEncapsulationSocket trust split, the reverted IpSec resource validation, or physical Wi-Fi emission under VPN lockdown.The platform fixes the packet shape, but the caller chooses the destination within the API and routing constraints. Repeated packets disclose the physical network’s source address and timing to that destination after the user has enabled blocking without the VPN. This violates lockdown’s identity-confinement property without requiring arbitrary payload control.3. Background: Android VPN Lockdown and NAT-T Keepalive Offload3.1 Android VPN LockdownAndroid’s VPN model can route covered application traffic into a VPN app’s TUN interface. Always-on VPN keeps the selected VPN active, and the user-facing “Block connections without VPN” setting is intended to prevent covered traffic from using non-secure networks outside that VPN path [21; 22]. In the normal case, an application write traverses the socket layer, per-UID network policy, fwmark and netd routing state, VPN UID-range routing, firewall/prohibit rules, and eventually the VPN TUN interface when the UID is covered by the VPN.The normal VPN-protected path is:covered app UID -> socket connect/write -> fwmark/netd policy and VPN UID range checks -> lockdown prohibit/fail-closed decision when needed -> VPN app TUN interface -> encrypted VPN tunnel over an allowed underlay The relevant security property covers traffic and delegated packet emission attributable to a covered non-owner application. Such emission must stay off non-VPN interfaces unless the platform defines an explicit exemption. VPN-owner underlay traffic, configured split tunnels, documented platform probes, and privileged system functions may follow separate policy.Android separately records which package is prepared to act as the VPN for each user. VpnService.prepare() may require user consent; the service itself must be declared with BIND_VPN_SERVICE. In Vpn, the prepared package is paired with its installed owner UID so uninstall/reinstall and package-only comparisons do not preserve authority. An APK that merely declares a VPN service is not the prepared VPN, and prior consent that has been revoked is not current approval [23; 24]. The installed owner UID and current prepared package provide the authority needed for keepalive admission.3.2 NAT-T Socket Keepalive OffloadNAT traversal for IPsec commonly uses UDP port 4500. Android exposes a public API path in which an application creates an IpSecManager.UdpEncapsulationSocket and asks ConnectivityManager.createSocketKeepalive(...) to maintain the NAT mapping [8; 9]. Internally, this path passes a duplicated file descriptor and an IpSec resource ID to IConnectivityManager.startNattKeepaliveWithFd(...), which then reaches ConnectivityService, KeepaliveTracker, a NetworkAgent, and transport-specific Wi-Fi or cellular keepalive machinery [10; 11; 12].The NAT-T keepalive offload path is:app UID -> IpSecManager.openUdpEncapsulationSocket() -> ConnectivityManager.createSocketKeepalive(...) -> NattSocketKeepalive.startImpl() -> IConnectivityManager.startNattKeepaliveWithFd(...) -> ConnectivityService / KeepaliveTracker -> NetworkAgent -> Wi-Fi HAL / chipset firmware -> UDP/4500 keepalive on physical Wi-Fi The Wi-Fi offload path can emit keepalive frames without waking the application or performing a new socket write for each packet. After framework admission, the final emitter sits below the ordinary app socket path that VPN lockdown normally controls.The public keepalive path, Wi-Fi HAL offload methods, and compatibility slot requirements are shared platform interfaces below the ordinary app socket path [25; 8; 26].4. Threat Model and Expected Lockdown Behavior4.1 Expected Lockdown BehaviorUnder Always-on VPN and “Block connections without VPN,” a covered normal application must not cause Android-managed NAT-T keepalive packets to leave over the physical network outside the VPN tunnel. If the caller’s full Android UID is covered by a non-bypassable or lockdown VPN, the public UdpEncapsulationSocket keepalive request should fail, remain unsupported, or be stopped before Wi-Fi or cellular offload emits UDP/4500 traffic on the physical underlay.The security goal applies to NAT-T emission attributable to a covered normal app. Android’s documented policies for VPN-owner underlay traffic, configured split tunnels, and privileged platform functions remain separate. Acceptance of an unauthenticated public fd/resource pair cannot confer physical-underlay offload authority.4.2 AttackerThe attacker controls a normal Android application installed on the victim device and controls or observes a UDP/4500 endpoint on the Internet. The validated public-API path does not require root, ADB, hidden API access, JNI, raw Binder construction, a dangerous runtime permission prompt, or the privileged PACKET_KEEPALIVE_OFFLOAD permission. The local PoC variants declared ordinary networking capabilities such as INTERNET and ACCESS_NETWORK_STATE.4.3 Victim ConfigurationThe victim device has Always-on VPN enabled and “Block connections without VPN” enabled for the attacker’s UID. Runtime confirmation used Android 16 configurations across multiple OEMs. The Pixel 8 Pro run used a researcher-controlled Wi-Fi network and an external physical-interface capture; the Qualcomm-based devices supplied active physical-gateway slot observations [21; 22; 27; 15; 16; 17].ADB and root were used for research instrumentation and packet collection. Neither is an exploitation precondition for the public API path.DimensionPublic claimApp privilegeNormal third-party app.Runtime dangerous permissionNone required for the core public API behavior.Common declared permissionsACCESS_NETWORK_STATE and INTERNET.Privileged keepalive permissionNo PACKET_KEEPALIVE_OFFLOAD for the public API path.Root / ADBNot required for exploitation; used only for lab instrumentation.User interactionInitial app launch is enough for