Add API for apps to check if they are the network owner

This CL extends NetworkCapabilities#mEstablishingVpnAppUid
to the network owner app UID and introduces a new public API
to get this owner app's UID.

Bug: 142072839
Test: atest FrameworksNetTests
Change-Id: Id83cdea62b89b586aff74e51e3fee60e53d37d4c
This commit is contained in:
Qingxi Li
2020-01-17 17:54:27 -08:00
parent 452ccae317
commit bb8da980ae
4 changed files with 62 additions and 36 deletions

View File

@@ -26,6 +26,7 @@ import android.net.ConnectivityManager.NetworkCallback;
import android.os.Build; import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.Process;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream;
@@ -58,7 +59,6 @@ import java.util.StringJoiner;
*/ */
public final class NetworkCapabilities implements Parcelable { public final class NetworkCapabilities implements Parcelable {
private static final String TAG = "NetworkCapabilities"; private static final String TAG = "NetworkCapabilities";
private static final int INVALID_UID = -1;
// Set to true when private DNS is broken. // Set to true when private DNS is broken.
private boolean mPrivateDnsBroken; private boolean mPrivateDnsBroken;
@@ -85,8 +85,8 @@ public final class NetworkCapabilities implements Parcelable {
mTransportInfo = null; mTransportInfo = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
mUids = null; mUids = null;
mEstablishingVpnAppUid = INVALID_UID;
mAdministratorUids.clear(); mAdministratorUids.clear();
mOwnerUid = Process.INVALID_UID;
mSSID = null; mSSID = null;
mPrivateDnsBroken = false; mPrivateDnsBroken = false;
} }
@@ -104,8 +104,8 @@ public final class NetworkCapabilities implements Parcelable {
mTransportInfo = nc.mTransportInfo; mTransportInfo = nc.mTransportInfo;
mSignalStrength = nc.mSignalStrength; mSignalStrength = nc.mSignalStrength;
setUids(nc.mUids); // Will make the defensive copy setUids(nc.mUids); // Will make the defensive copy
mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
setAdministratorUids(nc.mAdministratorUids); setAdministratorUids(nc.mAdministratorUids);
mOwnerUid = nc.mOwnerUid;
mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
mSSID = nc.mSSID; mSSID = nc.mSSID;
mPrivateDnsBroken = nc.mPrivateDnsBroken; mPrivateDnsBroken = nc.mPrivateDnsBroken;
@@ -810,31 +810,26 @@ public final class NetworkCapabilities implements Parcelable {
} }
/** /**
* UID of the app that manages this network, or INVALID_UID if none/unknown. * UID of the app that owns this network, or INVALID_UID if none/unknown.
* *
* This field keeps track of the UID of the app that created this network and is in charge * <p>This field keeps track of the UID of the app that created this network and is in charge of
* of managing it. In the practice, it is used to store the UID of VPN apps so it is named * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running
* accordingly, but it may be renamed if other mechanisms are offered for third party apps * VPN, or Carrier Service app managing a cellular data connection.
* to create networks.
*
* Because this field is only used in the services side (and to avoid apps being able to
* set this to whatever they want), this field is not parcelled and will not be conserved
* across the IPC boundary.
* @hide
*/ */
private int mEstablishingVpnAppUid = INVALID_UID; private int mOwnerUid = Process.INVALID_UID;
/** /**
* Set the UID of the managing app. * Set the UID of the owner app.
* @hide
*/ */
public void setEstablishingVpnAppUid(final int uid) { public void setOwnerUid(final int uid) {
mEstablishingVpnAppUid = uid; mOwnerUid = uid;
} }
/** @hide */ /**
public int getEstablishingVpnAppUid() { * Retrieves the UID of the owner app.
return mEstablishingVpnAppUid; */
public int getOwnerUid() {
return mOwnerUid;
} }
/** /**
@@ -1157,7 +1152,7 @@ public final class NetworkCapabilities implements Parcelable {
* member is null, then the network is not restricted by app UID. If it's an empty list, then * member is null, then the network is not restricted by app UID. If it's an empty list, then
* it means nobody can use it. * it means nobody can use it.
* As a special exception, the app managing this network (as identified by its UID stored in * As a special exception, the app managing this network (as identified by its UID stored in
* mEstablishingVpnAppUid) can always see this network. This is embodied by a special check in * mOwnerUid) can always see this network. This is embodied by a special check in
* satisfiedByUids. That still does not mean the network necessarily <strong>applies</strong> * satisfiedByUids. That still does not mean the network necessarily <strong>applies</strong>
* to the app that manages it as determined by #appliesToUid. * to the app that manages it as determined by #appliesToUid.
* <p> * <p>
@@ -1264,7 +1259,7 @@ public final class NetworkCapabilities implements Parcelable {
* in the passed nc (representing the UIDs that this network is available to). * in the passed nc (representing the UIDs that this network is available to).
* <p> * <p>
* As a special exception, the UID that created the passed network (as represented by its * As a special exception, the UID that created the passed network (as represented by its
* mEstablishingVpnAppUid field) always satisfies a NetworkRequest requiring it (of LISTEN * mOwnerUid field) always satisfies a NetworkRequest requiring it (of LISTEN
* or REQUEST types alike), even if the network does not apply to it. That is so a VPN app * or REQUEST types alike), even if the network does not apply to it. That is so a VPN app
* can see its own network when it listens for it. * can see its own network when it listens for it.
* <p> * <p>
@@ -1275,7 +1270,7 @@ public final class NetworkCapabilities implements Parcelable {
public boolean satisfiedByUids(@NonNull NetworkCapabilities nc) { public boolean satisfiedByUids(@NonNull NetworkCapabilities nc) {
if (null == nc.mUids || null == mUids) return true; // The network satisfies everything. if (null == nc.mUids || null == mUids) return true; // The network satisfies everything.
for (UidRange requiredRange : mUids) { for (UidRange requiredRange : mUids) {
if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true; if (requiredRange.contains(nc.mOwnerUid)) return true;
if (!nc.appliesToUidRange(requiredRange)) { if (!nc.appliesToUidRange(requiredRange)) {
return false; return false;
} }
@@ -1541,6 +1536,7 @@ public final class NetworkCapabilities implements Parcelable {
dest.writeString(mSSID); dest.writeString(mSSID);
dest.writeBoolean(mPrivateDnsBroken); dest.writeBoolean(mPrivateDnsBroken);
dest.writeList(mAdministratorUids); dest.writeList(mAdministratorUids);
dest.writeInt(mOwnerUid);
} }
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR = public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -1562,6 +1558,7 @@ public final class NetworkCapabilities implements Parcelable {
netCap.mSSID = in.readString(); netCap.mSSID = in.readString();
netCap.mPrivateDnsBroken = in.readBoolean(); netCap.mPrivateDnsBroken = in.readBoolean();
netCap.setAdministratorUids(in.readArrayList(null)); netCap.setAdministratorUids(in.readArrayList(null));
netCap.mOwnerUid = in.readInt();
return netCap; return netCap;
} }
@Override @Override
@@ -1611,8 +1608,8 @@ public final class NetworkCapabilities implements Parcelable {
sb.append(" Uids: <").append(mUids).append(">"); sb.append(" Uids: <").append(mUids).append(">");
} }
} }
if (mEstablishingVpnAppUid != INVALID_UID) { if (mOwnerUid != Process.INVALID_UID) {
sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid); sb.append(" OwnerUid: ").append(mOwnerUid);
} }
if (!mAdministratorUids.isEmpty()) { if (!mAdministratorUids.isEmpty()) {

View File

@@ -1625,7 +1625,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network)); return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
} }
private NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( @VisibleForTesting
NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
NetworkCapabilities nc, int callerPid, int callerUid) { NetworkCapabilities nc, int callerPid, int callerUid) {
final NetworkCapabilities newNc = new NetworkCapabilities(nc); final NetworkCapabilities newNc = new NetworkCapabilities(nc);
if (!checkSettingsPermission(callerPid, callerUid)) { if (!checkSettingsPermission(callerPid, callerUid)) {
@@ -1636,9 +1637,23 @@ public class ConnectivityService extends IConnectivityManager.Stub
newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact()); newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
} }
newNc.setAdministratorUids(Collections.EMPTY_LIST); newNc.setAdministratorUids(Collections.EMPTY_LIST);
maybeSanitizeLocationInfoForCaller(newNc, callerUid);
return newNc; return newNc;
} }
private void maybeSanitizeLocationInfoForCaller(
NetworkCapabilities nc, int callerUid) {
// TODO(b/142072839): Conditionally reset the owner UID if the following
// conditions are not met:
// 1. The destination app is the network owner
// 2. The destination app has the ACCESS_COARSE_LOCATION permission granted
// if target SDK<29 or otherwise has the ACCESS_FINE_LOCATION permission granted
// 3. The user's location toggle is on
nc.setOwnerUid(INVALID_UID);
}
private LinkProperties linkPropertiesRestrictedForCallerPermissions( private LinkProperties linkPropertiesRestrictedForCallerPermissions(
LinkProperties lp, int callerPid, int callerUid) { LinkProperties lp, int callerPid, int callerUid) {
if (lp == null) return new LinkProperties(); if (lp == null) return new LinkProperties();
@@ -1667,6 +1682,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
nc.setSingleUid(Binder.getCallingUid()); nc.setSingleUid(Binder.getCallingUid());
} }
nc.setAdministratorUids(Collections.EMPTY_LIST); nc.setAdministratorUids(Collections.EMPTY_LIST);
// Clear owner UID; this can never come from an app.
nc.setOwnerUid(INVALID_UID);
} }
private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) { private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) {
@@ -5794,7 +5812,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
final Set<UidRange> ranges = nai.networkCapabilities.getUids(); final Set<UidRange> ranges = nai.networkCapabilities.getUids();
final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid(); final int vpnAppUid = nai.networkCapabilities.getOwnerUid();
// TODO: this create a window of opportunity for apps to receive traffic between the time // TODO: this create a window of opportunity for apps to receive traffic between the time
// when the old rules are removed and the time when new rules are added. To fix this, // when the old rules are removed and the time when new rules are added. To fix this,
// make eBPF support two whitelisted interfaces so here new rules can be added before the // make eBPF support two whitelisted interfaces so here new rules can be added before the
@@ -5993,7 +6011,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (nc == null || lp == null) return false; if (nc == null || lp == null) return false;
return nai.isVPN() return nai.isVPN()
&& !nai.networkAgentConfig.allowBypass && !nai.networkAgentConfig.allowBypass
&& nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID && nc.getOwnerUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null && lp.getInterfaceName() != null
&& (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
} }
@@ -6041,12 +6059,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
// TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range // TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
// to be removed will never overlap with the new range to be added. // to be removed will never overlap with the new range to be added.
if (wasFiltering && !prevRanges.isEmpty()) { if (wasFiltering && !prevRanges.isEmpty()) {
mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getOwnerUid());
prevNc.getEstablishingVpnAppUid());
} }
if (shouldFilter && !newRanges.isEmpty()) { if (shouldFilter && !newRanges.isEmpty()) {
mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getOwnerUid());
newNc.getEstablishingVpnAppUid());
} }
} catch (Exception e) { } catch (Exception e) {
// Never crash! // Never crash!

View File

@@ -269,6 +269,7 @@ public class NetworkCapabilitiesTest {
.setUids(uids) .setUids(uids)
.addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_EIMS)
.addCapability(NET_CAPABILITY_NOT_METERED); .addCapability(NET_CAPABILITY_NOT_METERED);
netCap.setOwnerUid(123);
assertParcelingIsLossless(netCap); assertParcelingIsLossless(netCap);
netCap.setSSID(TEST_SSID); netCap.setSSID(TEST_SSID);
assertParcelSane(netCap, 13); assertParcelSane(netCap, 13);

View File

@@ -6313,12 +6313,24 @@ public class ConnectivityServiceTest {
assertEquals(wifiLp, mService.getActiveLinkProperties()); assertEquals(wifiLp, mService.getActiveLinkProperties());
} }
@Test
public void testNetworkCapabilitiesRestrictedForCallerPermissions() {
int callerUid = Process.myUid();
final NetworkCapabilities originalNc = new NetworkCapabilities();
originalNc.setOwnerUid(callerUid);
private TestNetworkAgentWrapper establishVpn(LinkProperties lp, int establishingUid, final NetworkCapabilities newNc =
Set<UidRange> vpnRange) throws Exception { mService.networkCapabilitiesRestrictedForCallerPermissions(
originalNc, Process.myPid(), callerUid);
assertEquals(Process.INVALID_UID, newNc.getOwnerUid());
}
private TestNetworkAgentWrapper establishVpn(
LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception {
final TestNetworkAgentWrapper final TestNetworkAgentWrapper
vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp); vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp);
vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid); vpnNetworkAgent.getNetworkCapabilities().setOwnerUid(ownerUid);
mMockVpn.setNetworkAgent(vpnNetworkAgent); mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.connect(); mMockVpn.connect();
mMockVpn.setUids(vpnRange); mMockVpn.setUids(vpnRange);