mullvad / mullvadvpn-app

The Mullvad VPN client app for desktop and mobile
https://mullvad.net/
GNU General Public License v3.0
4.99k stars 337 forks source link

On Android, inbound LAN traffic is possible even when Local Network Sharing is off #4643

Open t-m-w opened 1 year ago

t-m-w commented 1 year ago

Issue report

Operating system: Android 13

App version: 2022.3

Issue description

When Mullvad is connected, inbound traffic is still possible from Wi-Fi or any other interface, regardless of the "Local Network Sharing" setting, because when sharing is off, Mullvad uses two separate routes instead of a single default route. This leads the Android system to allow traffic from any interface, because the system is confused into assuming that Mullvad does not intend to handle all traffic.

For example, when a user connects to Mullvad and sets it as their always-on VPN with the "Block connections without VPN" feature, they might expect that traffic from the LAN or elsewhere is not permitted; however, because Mullvad is not fully-routed, such traffic is always allowed inbound. This can be tested intentionally by installing the primitive ftpd app to start an FTP server and trying to access it over the local network while Mullvad is connected. It will succeed.

Background

Android 13 uses the presence of default routes as a hint that a VPN is "isolated". This determination causes the VPN's tunnel interface to be established as the only valid inbound interface for apps covered by the VPN, meaning that remote-initiated connections from Wi-Fi, etc. will not be able to reach apps. If a VPN doesn't have default routes, it doesn't matter how the user configures it; the "Block connections without VPN" option only affects locally-initiated outbound traffic, not remote-initiated inbound traffic -- an odd quirk of Android.

When Local Network Sharing is turned on, this behavior is unavoidable and it makes some sense. When it's off, however, Mullvad is still not fully-routed from Android's perspective because it uses two separate routes for each of IPv4 and IPv6: 0.0.0.0/1 and 128.0.0.0/1, and ::/1 and 8000::/1, respectively. By doing this, Android does not recognize Mullvad as an "isolated" VPN, regardless of the "Block connections without VPN" setting, which is a setting that only affects locally-initiated traffic.

Resolution

Unless there is a good reason for splitting the routes like this, the problem should be easily solvable by using a single default route per IPv4/IPv6 instead, which is something that other VPN apps tend to do. I didn't immediately notice any complications from a quick test of this and will post a pull request shortly.

faern commented 1 year ago

Thank you for this detailed investigation and report. We will look into it, but a PR is also very welcome!

t-m-w commented 1 year ago

With adb, to easily tell if Android 13 is limiting inbound traffic to the VPN interface without needing to test it with it with a server app: adb shell dumpsys connectivity trafficcontroller | sed -n '/mUidOwnerMap:/,/^$/p'

This command should show many lines ending with e.g. tun0 or tun1, which represents the allowed ingress interface. (You could add | grep tun and simply see if there's any output or not.)

If it shows many lines ending with simply 0 instead, that represents the "wildcard" interface, and the VPN is not considered isolated; traffic will be allowed on any interface.

As mentioned earlier, you can also do a real-world test with something like primitive ftpd.

albin-mullvad commented 1 year ago

Thanks for reporting this issue and sharing the PR to fix it. Do you know whether this is documented outside of the AOSP source code?

t-m-w commented 1 year ago

Not to my knowledge. If anything, it contradicts the documentation.

t-m-w commented 1 year ago

Well... It is documented here! https://issuetracker.google.com/issues/249990229#comment4 ;-)

t-m-w commented 1 year ago

I could not find an existing Android issue specifically about this, so I reported it here: https://issuetracker.google.com/issues/280462382