Merge "Add deny firewall chain for OEM" am: 181f7c85fc

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2114533

Change-Id: I42c0033bae3a71bb4893eedf59b36ccf5fe0553d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Motomu Utsumi
2022-06-03 11:30:33 +00:00
committed by Automerger Merge Worker
10 changed files with 276 additions and 2 deletions

View File

@@ -133,6 +133,8 @@ enum UidOwnerMatchType {
LOW_POWER_STANDBY_MATCH = (1 << 6),
IIF_MATCH = (1 << 7),
LOCKDOWN_VPN_MATCH = (1 << 8),
OEM_DENY_1_MATCH = (1 << 9),
OEM_DENY_2_MATCH = (1 << 10),
};
enum BpfPermissionMatch {

View File

@@ -216,6 +216,12 @@ static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direc
if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
return BPF_DROP;
}
if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
return BPF_DROP;
}
if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
return BPF_DROP;
}
}
if (direction == BPF_INGRESS && skb->ifindex != 1) {
if (uidRules & IIF_MATCH) {

View File

@@ -992,6 +992,20 @@ public class ConnectivityManager {
*/
public static final int FIREWALL_CHAIN_LOCKDOWN_VPN = 6;
/**
* Firewall chain used for OEM-specific application restrictions.
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7;
/**
* Firewall chain used for OEM-specific application restrictions.
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
@@ -1000,7 +1014,9 @@ public class ConnectivityManager {
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
FIREWALL_CHAIN_LOCKDOWN_VPN
FIREWALL_CHAIN_LOCKDOWN_VPN,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2
})
public @interface FirewallChain {}
// LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)

View File

@@ -74,6 +74,8 @@ const char* TrafficController::LOCAL_STANDBY = "fw_standby";
const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby";
const char* TrafficController::LOCAL_OEM_DENY_1 = "fw_oem_deny_1";
const char* TrafficController::LOCAL_OEM_DENY_2 = "fw_oem_deny_2";
static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
"Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
@@ -99,6 +101,8 @@ const std::string uidMatchTypeToString(uint32_t match) {
FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
FLAG_MSG_TRANS(matchType, LOCKDOWN_VPN_MATCH, match);
FLAG_MSG_TRANS(matchType, OEM_DENY_1_MATCH, match);
FLAG_MSG_TRANS(matchType, OEM_DENY_2_MATCH, match);
if (match) {
return StringPrintf("Unknown match: %u", match);
}
@@ -336,6 +340,10 @@ FirewallType TrafficController::getFirewallType(ChildChain chain) {
return ALLOWLIST;
case LOCKDOWN:
return DENYLIST;
case OEM_DENY_1:
return DENYLIST;
case OEM_DENY_2:
return DENYLIST;
case NONE:
default:
return DENYLIST;
@@ -364,6 +372,12 @@ int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallR
case LOCKDOWN:
res = updateOwnerMapEntry(LOCKDOWN_VPN_MATCH, uid, rule, type);
break;
case OEM_DENY_1:
res = updateOwnerMapEntry(OEM_DENY_1_MATCH, uid, rule, type);
break;
case OEM_DENY_2:
res = updateOwnerMapEntry(OEM_DENY_2_MATCH, uid, rule, type);
break;
case NONE:
default:
ALOGW("Unknown child chain: %d", chain);
@@ -441,6 +455,10 @@ int TrafficController::replaceUidOwnerMap(const std::string& name, bool isAllowl
res = replaceRulesInMap(RESTRICTED_MATCH, uids);
} else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) {
res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids);
} else if (!name.compare(LOCAL_OEM_DENY_1)) {
res = replaceRulesInMap(OEM_DENY_1_MATCH, uids);
} else if (!name.compare(LOCAL_OEM_DENY_2)) {
res = replaceRulesInMap(OEM_DENY_2_MATCH, uids);
} else {
ALOGE("unknown chain name: %s", name.c_str());
return -EINVAL;
@@ -480,6 +498,12 @@ int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
case LOW_POWER_STANDBY:
match = LOW_POWER_STANDBY_MATCH;
break;
case OEM_DENY_1:
match = OEM_DENY_1_MATCH;
break;
case OEM_DENY_2:
match = OEM_DENY_2_MATCH;
break;
default:
return -EINVAL;
}

View File

@@ -299,6 +299,8 @@ TEST_F(TrafficControllerTest, TestChangeUidOwnerRule) {
checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
checkUidOwnerRuleForChain(LOCKDOWN, LOCKDOWN_VPN_MATCH);
checkUidOwnerRuleForChain(OEM_DENY_1, OEM_DENY_1_MATCH);
checkUidOwnerRuleForChain(OEM_DENY_2, OEM_DENY_2_MATCH);
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
}
@@ -310,6 +312,8 @@ TEST_F(TrafficControllerTest, TestReplaceUidOwnerMap) {
checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH);
checkUidMapReplace("fw_oem_deny_1", uids, OEM_DENY_1_MATCH);
checkUidMapReplace("fw_oem_deny_2", uids, OEM_DENY_2_MATCH);
ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
}

View File

@@ -36,6 +36,8 @@ enum ChildChain {
RESTRICTED = 4,
LOW_POWER_STANDBY = 5,
LOCKDOWN = 6,
OEM_DENY_1 = 7,
OEM_DENY_2 = 8,
INVALID_CHAIN
};
// LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)

View File

@@ -88,6 +88,8 @@ class TrafficController {
static const char* LOCAL_POWERSAVE;
static const char* LOCAL_RESTRICTED;
static const char* LOCAL_LOW_POWER_STANDBY;
static const char* LOCAL_OEM_DENY_1;
static const char* LOCAL_OEM_DENY_2;
private:
/*
@@ -149,7 +151,7 @@ class TrafficController {
* the map right now:
* - Entry with UID_RULES_CONFIGURATION_KEY:
* Store the configuration for the current uid rules. It indicates the device
* is in doze/powersave/standby/restricted/low power standby mode.
* is in doze/powersave/standby/restricted/low power standby/oem deny mode.
* - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
* Stores the current live stats map that kernel program is writing to.
* Userspace can do scraping and cleaning job on the other one depending on the

View File

@@ -11359,6 +11359,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
final int defaultRule;
switch (chain) {
case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
defaultRule = FIREWALL_RULE_ALLOW;
break;
case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
@@ -11408,6 +11410,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
mBpfNetMaps.replaceUidChain("fw_oem_deny_1", false /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
mBpfNetMaps.replaceUidChain("fw_oem_deny_2", false /* isAllowList */, uids);
break;
default:
throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
+ chain);

View File

@@ -37,6 +37,10 @@ import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.EXTRA_NETWORK;
import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -204,6 +208,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -218,6 +224,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -255,6 +262,7 @@ public class ConnectivityManagerTest {
private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 5_000;
private static final int NO_CALLBACK_TIMEOUT_MS = 100;
private static final int SOCKET_TIMEOUT_MS = 100;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
// device could have only one interface: data, wifi.
@@ -3274,6 +3282,111 @@ public class ConnectivityManagerTest {
assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
}
private void checkFirewallBlocking(final DatagramSocket srcSock, final DatagramSocket dstSock,
final boolean expectBlock) throws Exception {
final Random random = new Random();
final byte[] sendData = new byte[100];
random.nextBytes(sendData);
final DatagramPacket pkt = new DatagramPacket(sendData, sendData.length,
InetAddresses.parseNumericAddress("::1"), dstSock.getLocalPort());
try {
srcSock.send(pkt);
} catch (IOException e) {
if (expectBlock) {
return;
}
fail("Expect not to be blocked by firewall but sending packet was blocked");
}
if (expectBlock) {
fail("Expect to be blocked by firewall but sending packet was not blocked");
}
dstSock.receive(pkt);
assertArrayEquals(sendData, pkt.getData());
}
private static final boolean EXPECT_PASS = false;
private static final boolean EXPECT_BLOCK = true;
private void doTestFirewallBlockingDenyRule(final int chain) {
runWithShellPermissionIdentity(() -> {
try (DatagramSocket srcSock = new DatagramSocket();
DatagramSocket dstSock = new DatagramSocket()) {
dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
// No global config, No uid config
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
// Has global config, No uid config
mCm.setFirewallChainEnabled(chain, true /* enable */);
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
// Has global config, Has uid config
mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
// No global config, Has uid config
mCm.setFirewallChainEnabled(chain, false /* enable */);
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
// No global config, No uid config
mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
} finally {
mCm.setFirewallChainEnabled(chain, false /* enable */);
mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
}
}, NETWORK_SETTINGS);
}
private void doTestFirewallBlockingAllowRule(final int chain) {
runWithShellPermissionIdentity(() -> {
try (DatagramSocket srcSock = new DatagramSocket();
DatagramSocket dstSock = new DatagramSocket()) {
dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
// No global config, No uid config
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
// Has global config, No uid config
mCm.setFirewallChainEnabled(chain, true /* enable */);
checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
// Has global config, Has uid config
mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
// No global config, Has uid config
mCm.setFirewallChainEnabled(chain, false /* enable */);
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
// No global config, No uid config
mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
} finally {
mCm.setFirewallChainEnabled(chain, false /* enable */);
mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
}
}, NETWORK_SETTINGS);
}
@Test @IgnoreUpTo(SC_V2)
public void testFirewallBlocking() {
// Following tests affect the actual state of networking on the device after the test.
// This might cause unexpected behaviour of the device. So, we skip them for now.
// We will enable following tests after adding the logic of firewall state restoring.
// doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_DOZABLE);
// doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_POWERSAVE);
// doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_RESTRICTED);
// doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
// doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_STANDBY);
doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_1);
doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_2);
}
private void assumeTestSApis() {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.

View File

@@ -51,8 +51,16 @@ import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
@@ -9545,6 +9553,95 @@ public class ConnectivityServiceTest {
verify(mBpfNetMaps, never()).removeUidInterfaceRules(any());
}
private void doTestSetUidFirewallRule(final int chain, final int defaultRule) {
final int uid = 1001;
mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_ALLOW);
reset(mBpfNetMaps);
mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DENY);
verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_DENY);
reset(mBpfNetMaps);
mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
verify(mBpfNetMaps).setUidRule(chain, uid, defaultRule);
reset(mBpfNetMaps);
}
@Test @IgnoreUpTo(SC_V2)
public void testSetUidFirewallRule() throws Exception {
doTestSetUidFirewallRule(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW);
doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
}
@Test @IgnoreUpTo(SC_V2)
public void testSetFirewallChainEnabled() throws Exception {
final List<Integer> firewallChains = Arrays.asList(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2);
for (final int chain: firewallChains) {
mCm.setFirewallChainEnabled(chain, true /* enabled */);
verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
reset(mBpfNetMaps);
mCm.setFirewallChainEnabled(chain, false /* enabled */);
verify(mBpfNetMaps).setChildChain(chain, false /* enable */);
reset(mBpfNetMaps);
}
}
private void doTestReplaceFirewallChain(final int chain, final String chainName,
final boolean allowList) {
final int[] uids = new int[] {1001, 1002};
mCm.replaceFirewallChain(chain, uids);
verify(mBpfNetMaps).replaceUidChain(chainName, allowList, uids);
reset(mBpfNetMaps);
}
@Test @IgnoreUpTo(SC_V2)
public void testReplaceFirewallChain() {
doTestReplaceFirewallChain(FIREWALL_CHAIN_DOZABLE, "fw_dozable", true);
doTestReplaceFirewallChain(FIREWALL_CHAIN_STANDBY, "fw_standby", false);
doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE, "fw_powersave", true);
doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED, "fw_restricted", true);
doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY, "fw_low_power_standby", true);
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1, "fw_oem_deny_1", false);
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2, "fw_oem_deny_2", false);
}
@Test @IgnoreUpTo(SC_V2)
public void testInvalidFirewallChain() throws Exception {
final int uid = 1001;
final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
assertThrows(expected,
() -> mCm.setUidFirewallRule(-1 /* chain */, uid, FIREWALL_RULE_ALLOW));
assertThrows(expected,
() -> mCm.setUidFirewallRule(100 /* chain */, uid, FIREWALL_RULE_ALLOW));
assertThrows(expected, () -> mCm.replaceFirewallChain(-1 /* chain */, new int[]{uid}));
assertThrows(expected, () -> mCm.replaceFirewallChain(100 /* chain */, new int[]{uid}));
}
@Test @IgnoreUpTo(SC_V2)
public void testInvalidFirewallRule() throws Exception {
final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
assertThrows(expected,
() -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
1001 /* uid */, -1 /* rule */));
assertThrows(expected,
() -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
1001 /* uid */, 100 /* rule */));
}
/**
* Test mutable and requestable network capabilities such as
* {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and