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
         })
     }