Allow app-specific network creation with a new permission
If an app registers a network agent and doesn't have NETWORK_FACTORY or PERMISSION_MAINLINE_NETWORK_STACK, but does have CREATE_APP_SPECIFIC_NETWORK, ConnectivityService will: - Allow it to create a network - Force the capabilities to contain only the app's UID - Force the network to be restricted - Ignore attempts to change the UID or make the network non-restricted Test: TH Flag: android.net.platform.flags.create_app_specific_network_permission Bug: 435313135 Change-Id: I5a9517515e64f799c88aca4a51636b1a08e15817
This commit is contained in:
parent
4c516ffd67
commit
72d4b516c9
8 changed files with 158 additions and 17 deletions
|
|
@ -3974,7 +3974,8 @@ public class ConnectivityManager {
|
|||
*/
|
||||
@RequiresPermission(anyOf = {
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_FACTORY})
|
||||
android.Manifest.permission.NETWORK_FACTORY,
|
||||
android.Manifest.permission.CREATE_APP_SPECIFIC_NETWORK})
|
||||
public NetworkAndAgentRegistryParcelable registerNetworkAgent(
|
||||
@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
|
||||
@NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
|
||||
|
|
@ -3990,7 +3991,8 @@ public class ConnectivityManager {
|
|||
*/
|
||||
@RequiresPermission(anyOf = {
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_FACTORY})
|
||||
android.Manifest.permission.NETWORK_FACTORY,
|
||||
android.Manifest.permission.CREATE_APP_SPECIFIC_NETWORK})
|
||||
public NetworkAndAgentRegistryParcelable registerNetworkAgent(
|
||||
@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
|
||||
@NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
|
||||
|
|
|
|||
|
|
@ -1565,6 +1565,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
return SdkLevel.isAtLeastB();
|
||||
}
|
||||
|
||||
public boolean isAtLeast25Q4() {
|
||||
return Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1;
|
||||
}
|
||||
|
||||
/** Get SystemClock.elapsedRealtime() */
|
||||
public long getElapsedRealtime() {
|
||||
return SystemClock.elapsedRealtime();
|
||||
|
|
@ -2303,7 +2307,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
new LinkProperties(), new NetworkCapabilities(), null /* localNetworkConfig */,
|
||||
new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
|
||||
new NetworkAgentConfig(), this, null, null, 0, INVALID_UID,
|
||||
mLingerDelayMs, mQosCallbackTracker, mDeps);
|
||||
false /* isAppSpecificNetwork */, mLingerDelayMs, mQosCallbackTracker, mDeps);
|
||||
|
||||
try {
|
||||
// DscpPolicyTracker cannot run on S because on S the tethering module can only load
|
||||
|
|
@ -4090,14 +4094,28 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
|
||||
}
|
||||
|
||||
private void enforceNetworkFactoryPermission() {
|
||||
private boolean hasCreateAppSpecificNetworkPermission() {
|
||||
// The CREATE_APP_SPECIFIC_NETWORK permission was introduced in 25Q4.
|
||||
// It must not be granted on earlier platform versions, even if an app declares it.
|
||||
return mDeps.isAtLeast25Q4() && hasAnyPermissionOf(mContext,
|
||||
android.Manifest.permission.CREATE_APP_SPECIFIC_NETWORK);
|
||||
}
|
||||
|
||||
private boolean hasNetworkFactoryPermission() {
|
||||
// TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
|
||||
if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
|
||||
enforceAnyPermissionOf(mContext,
|
||||
if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return true;
|
||||
return hasAnyPermissionOf(mContext,
|
||||
android.Manifest.permission.NETWORK_FACTORY,
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
|
||||
}
|
||||
|
||||
private void enforceNetworkFactoryPermission() {
|
||||
if (!hasNetworkFactoryPermission()) {
|
||||
throw new SecurityException("Requires one of the following permissions: "
|
||||
+ "NETWORK_FACTORY, MAINLINE_NETWORK_STACK");
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceNetworkFactoryOrSettingsPermission() {
|
||||
// TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
|
||||
if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
|
||||
|
|
@ -9864,10 +9882,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
Objects.requireNonNull(networkCapabilities, "networkCapabilities must not be null");
|
||||
Objects.requireNonNull(initialScore, "initialScore must not be null");
|
||||
Objects.requireNonNull(networkAgentConfig, "networkAgentConfig must not be null");
|
||||
boolean isAppSpecificNetwork = false;
|
||||
if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
|
||||
enforceAnyPermissionOf(mContext, Manifest.permission.MANAGE_TEST_NETWORKS);
|
||||
} else if (hasNetworkFactoryPermission()) {
|
||||
// Factory permission is present. Do nothing and proceed.
|
||||
} else if (hasCreateAppSpecificNetworkPermission()) {
|
||||
// The app lacks the network factory permission but has
|
||||
// CREATE_APP_SPECIFIC_NETWORK permission.
|
||||
isAppSpecificNetwork = true;
|
||||
} else {
|
||||
enforceNetworkFactoryPermission();
|
||||
throw new SecurityException("Requires one of the following permissions: "
|
||||
+ "NETWORK_FACTORY, MAINLINE_NETWORK_STACK"
|
||||
+ (mDeps.isAtLeast25Q4() ? ", CREATE_APP_SPECIFIC_NETWORK" : ""));
|
||||
}
|
||||
final boolean hasLocalCap =
|
||||
networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
|
||||
|
|
@ -9888,7 +9915,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
try {
|
||||
return registerNetworkAgentInternal(na, networkInfo, linkProperties,
|
||||
networkCapabilities, initialScore, networkAgentConfig, localNetworkConfig,
|
||||
providerId, uid);
|
||||
providerId, uid, isAppSpecificNetwork);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
|
|
@ -9899,7 +9926,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
|
||||
NetworkScore currentScore, NetworkAgentConfig networkAgentConfig,
|
||||
@Nullable LocalNetworkConfig localNetworkConfig, int providerId,
|
||||
int uid) {
|
||||
int uid, boolean isAppSpecificNetwork) {
|
||||
|
||||
// Make a copy of the passed NI, LP, NC as the caller may hold a reference to them
|
||||
// and mutate them at any time.
|
||||
|
|
@ -9908,6 +9935,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
final LinkProperties lpCopy = new LinkProperties(linkProperties);
|
||||
// No need to copy |localNetworkConfiguration| as it is immutable.
|
||||
|
||||
if (isAppSpecificNetwork) {
|
||||
// For app specific network, set the app's UID and make network restricted.
|
||||
ncCopy.setSingleUid(uid);
|
||||
ncCopy.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
// At this point the capabilities/properties are untrusted and unverified, e.g. checks that
|
||||
// the capabilities' access UIDs comply with security limitations. They will be sanitized
|
||||
// as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
|
||||
|
|
@ -9916,7 +9949,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
new Network(mNetIdManager.reserveNetId()), niCopy, lpCopy, ncCopy,
|
||||
localNetworkConfig, currentScore, mContext, mTrackerHandler,
|
||||
new NetworkAgentConfig(networkAgentConfig), this, mNetd, mDnsResolver, providerId,
|
||||
uid, mLingerDelayMs, mQosCallbackTracker, mDeps);
|
||||
uid, isAppSpecificNetwork, mLingerDelayMs, mQosCallbackTracker, mDeps);
|
||||
|
||||
final String extraInfo = niCopy.getExtraInfo();
|
||||
final String name = TextUtils.isEmpty(extraInfo)
|
||||
|
|
|
|||
|
|
@ -437,6 +437,9 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
// The UID of the remote entity that created this Network.
|
||||
public final int creatorUid;
|
||||
|
||||
// Indicates whether this network is specific to the app that registered the networkAgent.
|
||||
private final boolean mIsAppSpecificNetwork;
|
||||
|
||||
// Network agent portal info of the network, if any. This information is provided from
|
||||
// non-RFC8908 sources, such as Wi-Fi Passpoint, which can provide information such as Venue
|
||||
// URL, Terms & Conditions URL, and network friendly name.
|
||||
|
|
@ -505,6 +508,18 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
+ networkCapabilities.getOwnerUid() + " to " + nc.getOwnerUid());
|
||||
nc.setOwnerUid(networkCapabilities.getOwnerUid());
|
||||
}
|
||||
if (mIsAppSpecificNetwork) {
|
||||
if (!networkCapabilities.getUids().equals(nc.getUids())) {
|
||||
Log.e(TAG, toShortString() + ": ignoring attempt to change uid from "
|
||||
+ networkCapabilities.getUids() + " to " + nc.getUids());
|
||||
nc.setUids(networkCapabilities.getUids());
|
||||
}
|
||||
if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
|
||||
Log.e(TAG, toShortString() + ": ignoring attempt to add "
|
||||
+ NetworkCapabilities.capabilityNameOf(NET_CAPABILITY_NOT_RESTRICTED));
|
||||
nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
}
|
||||
restrictCapabilitiesFromNetworkAgent(nc, creatorUid, mHasAutomotiveFeature,
|
||||
mConnServiceDeps, carrierPrivilegeAuthenticator);
|
||||
return nc;
|
||||
|
|
@ -657,7 +672,8 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
@NonNull NetworkScore score, Context context,
|
||||
Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
|
||||
IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid,
|
||||
int lingerDurationMs, QosCallbackTracker qosCallbackTracker,
|
||||
boolean isAppSpecificNetwork, int lingerDurationMs,
|
||||
QosCallbackTracker qosCallbackTracker,
|
||||
ConnectivityService.Dependencies deps) {
|
||||
Objects.requireNonNull(net);
|
||||
Objects.requireNonNull(info);
|
||||
|
|
@ -684,6 +700,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
mRegistry = new NetworkAgentMessageHandler(mHandler);
|
||||
this.factorySerialNumber = factorySerialNumber;
|
||||
this.creatorUid = creatorUid;
|
||||
mIsAppSpecificNetwork = isAppSpecificNetwork;
|
||||
mLingerDurationMs = lingerDurationMs;
|
||||
mQosCallbackTracker = qosCallbackTracker;
|
||||
declaredUnderlyingNetworks = (nc.getUnderlyingNetworks() != null)
|
||||
|
|
@ -1781,6 +1798,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
+ " lp{" + linkProperties + "}"
|
||||
+ " nc{" + networkCapabilities + "}"
|
||||
+ " factorySerialNumber=" + factorySerialNumber
|
||||
+ (mIsAppSpecificNetwork ? " appSpecificNetwork" : "")
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13089,8 +13089,8 @@ public class ConnectivityServiceTest {
|
|||
nc, null /* localNetworkConfig */,
|
||||
new NetworkScore.Builder().setLegacyInt(0).build(),
|
||||
mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0,
|
||||
INVALID_UID, TEST_LINGER_DELAY_MS, mQosCallbackTracker,
|
||||
new ConnectivityService.Dependencies());
|
||||
INVALID_UID, false /* isAppSpecificNetwork */, TEST_LINGER_DELAY_MS,
|
||||
mQosCallbackTracker, new ConnectivityService.Dependencies());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -375,8 +375,8 @@ public class LingerMonitorTest {
|
|||
new LinkProperties(), caps, null /* localNetworkConfiguration */,
|
||||
new NetworkScore.Builder().setLegacyInt(50).build(), mCtx, null,
|
||||
new NetworkAgentConfig.Builder().build(), mConnService, mNetd, mDnsResolver,
|
||||
NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS,
|
||||
mQosCallbackTracker, new ConnectivityService.Dependencies());
|
||||
NetworkProvider.ID_NONE, Binder.getCallingUid(), false /* isAppSpecificNetwork */,
|
||||
TEST_LINGER_DELAY_MS, mQosCallbackTracker, new ConnectivityService.Dependencies());
|
||||
if (setEverValidated) {
|
||||
// As tests in this class deal with testing lingering, most tests are interested
|
||||
// in networks that can be lingered, and therefore must have validated in the past.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server
|
||||
|
||||
import android.content.pm.PackageManager.PERMISSION_DENIED
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
|
||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE
|
||||
import android.net.NetworkRequest
|
||||
import android.net.NetworkStack
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import android.util.Range
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.TestableNetworkCallback.Event.CapabilitiesChanged
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
class CSCreateAppSpecificNetworkPermissionTest : CSTest() {
|
||||
@Test
|
||||
fun testRegisterNetworkAgent() {
|
||||
deps.setBuildSdk(VERSION_25Q4)
|
||||
context.setPermission(
|
||||
android.Manifest.permission.NETWORK_FACTORY,
|
||||
PERMISSION_DENIED
|
||||
)
|
||||
context.setPermission(
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
PERMISSION_DENIED
|
||||
)
|
||||
context.setPermission(
|
||||
android.Manifest.permission.CREATE_APP_SPECIFIC_NETWORK,
|
||||
PERMISSION_GRANTED
|
||||
)
|
||||
|
||||
val nc = nc(TRANSPORT_WIFI_AWARE)
|
||||
nc.uids = null
|
||||
nc.addCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||
val agent = Agent(nc = nc, score = keepConnectedScore())
|
||||
agent.connect()
|
||||
|
||||
// Uid is set and NOT_RESTRICTED is removed
|
||||
val myUid = Process.myUid()
|
||||
val expectedUids = setOf(Range(myUid, myUid))
|
||||
val cb = TestableNetworkCallback()
|
||||
cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
|
||||
cb.eventuallyExpect<CapabilitiesChanged> {
|
||||
it.network == agent.network &&
|
||||
it.caps.uids!!.equals(expectedUids) &&
|
||||
!it.caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||
}
|
||||
|
||||
// Attempt to change uid and make network non-restricted is ignored
|
||||
nc.setSingleUid(myUid + 1)
|
||||
nc.addCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||
agent.sendNetworkCapabilities(nc)
|
||||
cb.assertNoCallback() {
|
||||
it is CapabilitiesChanged && it.network == agent.network && (
|
||||
!it.caps.uids!!.equals(expectedUids) ||
|
||||
it.caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,7 +76,8 @@ class CSNetdBypassRulesTests : CSTest() {
|
|||
private fun createNetworkAgentInfo(network: Network): NetworkAgentInfo {
|
||||
return NetworkAgentInfo(na, network, info, lp, nc, localNetworkConfig?.value, score,
|
||||
context, csHandler, config, service, netd, dnsResolver, NetworkProvider.ID_NONE,
|
||||
Binder.getCallingUid(), TEST_LINGER_DELAY_MS, qosCallbackTracker, CSDeps())
|
||||
Binder.getCallingUid(), false /* isAppSpecificNetwork */, TEST_LINGER_DELAY_MS,
|
||||
qosCallbackTracker, CSDeps())
|
||||
}
|
||||
|
||||
private fun verifySetAllowBypassVpnOnNetwork(
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ internal const val VERSION_T = 3
|
|||
internal const val VERSION_U = 4
|
||||
internal const val VERSION_V = 5
|
||||
internal const val VERSION_B = 6
|
||||
internal const val VERSION_MAX = VERSION_B
|
||||
internal const val VERSION_25Q4 = 7
|
||||
internal const val VERSION_MAX = VERSION_25Q4
|
||||
|
||||
internal const val CALLING_UID_UNMOCKED = Process.INVALID_UID
|
||||
|
||||
|
|
@ -478,6 +479,11 @@ open class CSTest {
|
|||
override fun isAtLeastU() = if (isSdkUnmocked) super.isAtLeastU() else sdkLevel >= VERSION_U
|
||||
override fun isAtLeastV() = if (isSdkUnmocked) super.isAtLeastV() else sdkLevel >= VERSION_V
|
||||
override fun isAtLeastB() = if (isSdkUnmocked) super.isAtLeastB() else sdkLevel >= VERSION_B
|
||||
override fun isAtLeast25Q4() = if (isSdkUnmocked) {
|
||||
super.isAtLeast25Q4()
|
||||
} else {
|
||||
sdkLevel >= VERSION_25Q4
|
||||
}
|
||||
|
||||
private var callingUid = CALLING_UID_UNMOCKED
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue