Log bpf rc file if it differs from the template files.
Check if the bpf loader rc file is differs from the template files when
ConnectivityService starts. Use Log.wtf() to log the file if there is a
diff.
Bug: 312638861
Test: TH
Change-Id: I0b9ed9962ad0954a051a17b01357d04a0e655ff4
diff --git a/service/src/com/android/server/BpfLoaderRcUtils.java b/service/src/com/android/server/BpfLoaderRcUtils.java
new file mode 100644
index 0000000..1b6ee55
--- /dev/null
+++ b/service/src/com/android/server/BpfLoaderRcUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 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.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * BpfRcUtils is responsible for comparing the bpf loader rc file.
+ *
+ * {@hide}
+ */
+public class BpfLoaderRcUtils {
+ public static final String TAG = BpfLoaderRcUtils.class.getSimpleName();
+
+ private static final List<String> BPF_LOADER_RC_S_T = List.of(
+ "service bpfloader /system/bin/bpfloader",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ );
+
+ private static final List<String> BPF_LOADER_RC_U = List.of(
+ "service bpfloader /system/bin/bpfloader",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+ "user root",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ );
+
+ private static final List<String> BPF_LOADER_RC_UQPR2 = List.of(
+ "service bpfloader /system/bin/netbpfload",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+ "user root",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ );
+
+
+ private static final String BPF_LOADER_RC_FILE_PATH = "/etc/init/bpfloader.rc";
+ private static final String NET_BPF_LOAD_RC_FILE_PATH = "/etc/init/netbpfload.rc";
+
+ private BpfLoaderRcUtils() {
+ }
+
+ /**
+ * Load the bpf rc file content from the input stream.
+ */
+ @VisibleForTesting
+ public static List<String> loadExistingBpfRcFile(@NonNull InputStream inputStream) {
+ List<String> contents = new ArrayList<>();
+ boolean bpfSectionFound = false;
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ line = line.trim();
+ if (line.isEmpty()) {
+ continue;
+ }
+ if (line.startsWith("#")) {
+ continue;
+ }
+ // If bpf service section was found and new service or action section start. The
+ // read should stop.
+ if (bpfSectionFound && (line.startsWith("service ") || (line.startsWith("on ")))) {
+ break;
+ }
+ if (line.startsWith("service bpfloader ")) {
+ bpfSectionFound = true;
+ }
+ if (bpfSectionFound) {
+ contents.add(line);
+ }
+ }
+ } catch (IOException e) {
+ Log.wtf("read input stream failed.", e);
+ contents.clear();
+ return contents;
+ }
+ return contents;
+ }
+
+ /**
+ * Check the bpfLoader rc file on the system image matches any of the template files.
+ */
+ public static boolean checkBpfLoaderRc() {
+ File bpfRcFile = new File(BPF_LOADER_RC_FILE_PATH);
+ if (!bpfRcFile.exists()) {
+ if (SdkLevel.isAtLeastU()) {
+ bpfRcFile = new File(NET_BPF_LOAD_RC_FILE_PATH);
+ }
+ if (!bpfRcFile.exists()) {
+ Log.wtf(TAG,
+ "neither " + BPF_LOADER_RC_FILE_PATH + " nor " + NET_BPF_LOAD_RC_FILE_PATH
+ + " exist.");
+ return false;
+ }
+ // Check bpf rc file in U QPR2
+ return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_UQPR2);
+ }
+
+ if (SdkLevel.isAtLeastU()) {
+ // Check bpf rc file in U
+ return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_U);
+ }
+ // Check bpf rc file in S/T
+ return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_S_T);
+ }
+
+ private static boolean compareBpfLoaderRc(@NonNull File bpfRcFile,
+ @NonNull List<String> template) {
+ try {
+ List<String> actualContent = loadExistingBpfRcFile(new FileInputStream(bpfRcFile));
+ if (!actualContent.equals(template)) {
+ Log.wtf(TAG, "BPF rc file is not same as the template files " + actualContent);
+ return false;
+ }
+ } catch (FileNotFoundException e) {
+ Log.wtf(bpfRcFile.getPath() + " doesn't exist.", e);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8f29078..f2279f8 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1876,6 +1876,10 @@
activityManager.registerUidFrozenStateChangedCallback(
(Runnable r) -> r.run(), frozenStateChangedCallback);
}
+
+ if (mDeps.isFeatureNotChickenedOut(mContext, LOG_BPF_RC)) {
+ mHandler.post(BpfLoaderRcUtils::checkBpfLoaderRc);
+ }
}
/**
@@ -3335,6 +3339,8 @@
public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
"allow_sysui_connectivity_reports";
+ public static final String LOG_BPF_RC = "log_bpf_rc_force_disable";
+
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
diff --git a/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
new file mode 100644
index 0000000..2cf6b17
--- /dev/null
+++ b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.S)
+class BpfLoaderRcUtilsTest {
+ @Test
+ fun testLoadExistingBpfRcFile() {
+
+ val inputString = """
+ service a
+ # test comment
+ service bpfloader /system/bin/bpfloader
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ # comment 漢字
+ reboot_on_failure reboot,bpfloader-failed
+ updatable
+
+ #test comment
+ on b
+ oneshot
+ # test comment
+ """.trimIndent()
+ val expectedResult = listOf(
+ "service bpfloader /system/bin/bpfloader",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+ "user root",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ )
+
+ assertEquals(expectedResult,
+ BpfLoaderRcUtils.loadExistingBpfRcFile(inputString.byteInputStream()))
+ }
+
+ @Test
+ fun testCheckBpfRcFile() {
+ assertTrue(BpfLoaderRcUtils.checkBpfLoaderRc())
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 9fad766..8f5fd7c 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -161,6 +161,7 @@
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
+import static com.android.server.ConnectivityService.LOG_BPF_RC;
import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
@@ -2157,6 +2158,8 @@
switch (name) {
case ALLOW_SYSUI_CONNECTIVITY_REPORTS:
return true;
+ case LOG_BPF_RC:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index f4c62c5..958c4f2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -132,6 +132,7 @@
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
+ it[ConnectivityService.LOG_BPF_RC] = true
}
fun enableFeature(f: String) = enabledFeatures.set(f, true)
fun disableFeature(f: String) = enabledFeatures.set(f, false)