Add TestNetworkStackService to NetworkStack am: 9e6aeea309 am: 6c47d693b0
Original change: https://android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/1355068
Change-Id: I451b5459ae43e80328068ad609a0f9d9fd5bb6aa
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 879e7ed..82e7047 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -50,6 +50,17 @@
<action android:name="android.net.INetworkStackConnector"/>
</intent-filter>
</service>
+ <!-- Test instrumentation service, only usable on debuggable builds.
+ The service is protected by NETWORK_SETTINGS permissions as there is no better
+ networking-related permission that exists on Q, is sufficiently protected (signature),
+ and can be obtained via shell permissions. -->
+ <service android:name="com.android.server.TestNetworkStackService"
+ android:permission="android.permission.NETWORK_SETTINGS"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.net.INetworkStackConnector.Test"/>
+ </intent-filter>
+ </service>
<service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 0476add..a8b8628 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -87,6 +87,8 @@
"src/android/net/dhcp/IDhcpServerCallbacks.aidl",
"src/android/net/ip/IIpClient.aidl",
"src/android/net/ip/IIpClientCallbacks.aidl",
+ // New AIDL classes should go into android.net.networkstack.aidl so they can be clearly
+ // identified
],
backend: {
java: {
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
index 17a65cf..0864886 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
@@ -22,4 +22,5 @@
oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+ oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
}
diff --git a/common/networkstackclient/src/android/net/INetworkStackConnector.aidl b/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
index 3751c36..fa7abf5 100644
--- a/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
+++ b/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
@@ -17,6 +17,7 @@
import android.net.IIpMemoryStoreCallbacks;
import android.net.INetworkMonitorCallbacks;
+import android.net.INetworkStackStatusCallback;
import android.net.Network;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
@@ -29,4 +30,21 @@
void makeNetworkMonitor(in Network network, String name, in INetworkMonitorCallbacks cb);
void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
void fetchIpMemoryStore(in IIpMemoryStoreCallbacks cb);
+ /**
+ * Mark a UID as test UID, allowing it to use the TestNetworkStackService.
+ *
+ * TestNetworkStackService is a binder service identical to NetworkStackService, but only
+ * available on userdebug builds, and only usable by the test UID. It does not require the
+ * MAINLINE_NETWORK_STACK signature permission like NetworkStackService does, so it allows the
+ * test UID to use other methods in this interface even though it would otherwise not have
+ * permission to.
+ *
+ * This method must be called as root and can only be used on debuggable builds. It only affects
+ * the NetworkStack until it is restarted.
+ * Callers should pass in -1 to reset after use.
+ *
+ * @param cb Callback that will be called with a status of 0 if the call succeeds. Calls without
+ * sufficient permissions may be dropped without generating a callback.
+ */
+ oneway void allowTestUid(int uid, in INetworkStackStatusCallback cb);
}
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java
index 4c9d2bb..512f221 100644
--- a/src/com/android/server/NetworkStackService.java
+++ b/src/com/android/server/NetworkStackService.java
@@ -32,6 +32,7 @@
import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkStackConnector;
+import android.net.INetworkStackStatusCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -411,6 +412,15 @@
}
@Override
+ public void allowTestUid(int uid, @Nullable INetworkStackStatusCallback cb)
+ throws RemoteException {
+ // setTestUid does its own permission checks
+ PermissionUtil.setTestUid(mContext, uid);
+ mLog.i("Allowing test uid " + uid);
+ if (cb != null) cb.onStatusAvailable(0);
+ }
+
+ @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
checkDumpPermission();
diff --git a/src/com/android/server/TestNetworkStackService.java b/src/com/android/server/TestNetworkStackService.java
new file mode 100644
index 0000000..23981e5
--- /dev/null
+++ b/src/com/android/server/TestNetworkStackService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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 static com.android.server.util.PermissionUtil.isDebuggableBuild;
+
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A {@link NetworkStackService} that can only be bound to on debuggable builds.
+ */
+public class TestNetworkStackService extends NetworkStackService {
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (!isDebuggableBuild()) {
+ throw new SecurityException(
+ "TestNetworkStackService is only available on debuggable builds");
+ }
+ return super.onBind(intent);
+ }
+}
diff --git a/src/com/android/server/util/PermissionUtil.java b/src/com/android/server/util/PermissionUtil.java
index 3dff715..fbd13d7 100644
--- a/src/com/android/server/util/PermissionUtil.java
+++ b/src/com/android/server/util/PermissionUtil.java
@@ -16,12 +16,18 @@
package com.android.server.util;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Binder.getCallingPid;
import static android.os.Binder.getCallingUid;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserHandle;
+import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -30,6 +36,8 @@
public final class PermissionUtil {
private static final AtomicInteger sSystemPid = new AtomicInteger(-1);
+ private static volatile int sTestUid = Process.INVALID_UID;
+
/**
* Check that the caller is allowed to communicate with the network stack.
* @throws SecurityException The caller is not allowed to communicate with the network stack.
@@ -41,8 +49,9 @@
return;
}
- if (caller != Process.myUid() && // apps with NETWORK_STACK_UID
- UserHandle.getAppId(caller) != Process.BLUETOOTH_UID) {
+ if (caller != Process.myUid() // apps with NETWORK_STACK_UID
+ && UserHandle.getAppId(caller) != Process.BLUETOOTH_UID
+ && !isTestUid(caller)) {
throw new SecurityException("Invalid caller: " + caller);
}
}
@@ -67,6 +76,42 @@
}
}
+ private static boolean isTestUid(int uid) {
+ return uid == sTestUid;
+ }
+
+ /**
+ * Set a test uid that is allowed to call the NetworkStack. Pass in -1 to reset.
+ *
+ * <p>The UID must have a package with NETWORK_SETTINGS permissions when it is allowed.
+ */
+ public static void setTestUid(Context context, int uid) {
+ if (!isDebuggableBuild()) {
+ throw new SecurityException("Cannot set test UID on non-debuggable builds");
+ }
+ if (getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Only root can set the test UID");
+ }
+
+ if (uid == Process.INVALID_UID) {
+ sTestUid = uid;
+ return;
+ }
+
+ final PackageManager pm = context.getPackageManager();
+ final String[] packages = pm.getPackagesForUid(uid);
+ if (packages == null) {
+ throw new SecurityException("No package in uid " + uid);
+ }
+ final boolean hasPermission = Arrays.stream(packages).anyMatch(
+ p -> pm.checkPermission(NETWORK_SETTINGS, p) == PERMISSION_GRANTED);
+ if (!hasPermission) {
+ throw new SecurityException(
+ "The uid must have a package with NETWORK_SETTINGS permissions");
+ }
+ sTestUid = uid;
+ }
+
/**
* Check that the caller is allowed to dump the network stack, e.g. dumpsys.
* @throws SecurityException The caller is not allowed to dump the network stack.
@@ -79,6 +124,14 @@
}
}
+ /**
+ * @see android.os.Build.IS_DEBUGGABLE
+ */
+ public static boolean isDebuggableBuild() {
+ // TODO: consider adding Build.IS_DEBUGGABLE to @SystemApi
+ return SystemProperties.getInt("ro.debuggable", 0) == 1;
+ }
+
private PermissionUtil() {
throw new UnsupportedOperationException("This class is not to be instantiated");
}
diff --git a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
index c054b3a..df0787e 100644
--- a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
+++ b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
@@ -31,6 +31,7 @@
import android.net.ip.IpClient
import android.os.Build
import android.os.IBinder
+import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH
@@ -42,6 +43,8 @@
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.ExceptionUtils
+import com.android.testutils.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -202,8 +205,14 @@
connector.makeDhcpServer(TEST_IFACE, testParams, mockDhcpCb)
verify(mockDhcpCb, times(2)).onDhcpServerCreated(eq(IDhcpServer.STATUS_SUCCESS), any())
- // Verify all methods were covered by the test (4 methods + getVersion + getHash)
- assertEquals(6, INetworkStackConnector::class.declaredMemberFunctions.count {
+ // allowTestUid does not need to record the caller's version
+ assertThrows(SecurityException::class.java, ExceptionUtils.ThrowingRunnable {
+ // Should throw because the test does not run as root
+ connector.allowTestUid(Process.myUid(), null)
+ })
+
+ // Verify all methods were covered by the test (5 methods + getVersion + getHash)
+ assertEquals(7, INetworkStackConnector::class.declaredMemberFunctions.count {
it.visibility == KVisibility.PUBLIC
})
}