Block incoming non-VPN packets to apps under fully-routed VPN
When a fully-routed VPN is running, we want to prevent normal apps
under the VPN from receiving packets originating from any local non-VPN
interfaces. This is achieved by using eBPF to create a per-UID input
interface whitelist and populate the whitelist such that all
non-bypassable apps under a VPN can only receive packets from the VPN's
TUN interface (and loopback implicitly)
This is the Netd part of the change that auguments the existing UidOwner map
to include a new boolean to enable ingress interface filtering as well as
a new field per UID for the whitelisted interface index. The eBPF program
is updated to drop packets according to the ingress interface whitelist map
when present and enabled. This change also exposes two new netd Binder
interfaces to allow ConnectivityService to update the whitelist.
Test: system/netd/tests/runtests.sh
Bug: 114231106
Change-Id: I033c068a350af82023c2bf909e3b3e65d9952b66
diff --git a/server/TrafficControllerTest.cpp b/server/TrafficControllerTest.cpp
index b4acd66..07e3c3e 100644
--- a/server/TrafficControllerTest.cpp
+++ b/server/TrafficControllerTest.cpp
@@ -65,7 +65,7 @@
BpfMap<StatsKey, StatsValue> mFakeUidStatsMap;
BpfMap<StatsKey, StatsValue> mFakeTagStatsMap;
BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
- BpfMap<uint32_t, uint8_t> mFakeUidOwnerMap;
+ BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
void SetUp() {
@@ -97,8 +97,8 @@
createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
ASSERT_TRUE(mFakeConfigurationMap.isValid());
- mFakeUidOwnerMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ mFakeUidOwnerMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(UidOwnerValue),
+ TEST_MAP_SIZE, 0));
ASSERT_TRUE(mFakeUidOwnerMap.isValid());
mFakeUidPermissionMap.reset(
createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
@@ -152,15 +152,15 @@
void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
uint32_t uid = TEST_UID;
EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, BLACKLIST));
- StatusOr<uint8_t> value = mFakeUidOwnerMap.readValue(uid);
+ StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
EXPECT_TRUE(isOk(value));
- EXPECT_TRUE(value.value() & match);
+ EXPECT_TRUE(value.value().rule & match);
uid = TEST_UID2;
EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, ALLOW, WHITELIST));
value = mFakeUidOwnerMap.readValue(uid);
EXPECT_TRUE(isOk(value));
- EXPECT_TRUE(value.value() & match);
+ EXPECT_TRUE(value.value().rule & match);
EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, WHITELIST));
value = mFakeUidOwnerMap.readValue(uid);
@@ -182,13 +182,13 @@
void checkEachUidValue(const std::vector<int32_t>& uids, UidOwnerMatchType match) {
for (uint32_t uid : uids) {
- StatusOr<uint8_t> value = mFakeUidOwnerMap.readValue(uid);
+ StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
EXPECT_TRUE(isOk(value));
- EXPECT_TRUE(value.value() & match);
+ EXPECT_TRUE(value.value().rule & match);
}
std::set<uint32_t> uidSet(uids.begin(), uids.end());
const auto checkNoOtherUid = [&uidSet](const int32_t& key,
- const BpfMap<uint32_t, uint8_t>&) {
+ const BpfMap<uint32_t, UidOwnerValue>&) {
EXPECT_NE(uidSet.end(), uidSet.find(key));
return netdutils::status::ok;
};
@@ -205,30 +205,28 @@
EXPECT_EQ(0, mTc.replaceUidOwnerMap(name, isWhitelist, uids));
checkEachUidValue(uids, match);
}
- void expectUidOwnerMapValues(const std::vector<std::string>& appStrUids,
- uint8_t expectedValue) {
+ void expectUidOwnerMapValues(const std::vector<std::string>& appStrUids, uint8_t expectedRule,
+ uint32_t expectedIif) {
for (const std::string& strUid : appStrUids) {
uint32_t uid = stoi(strUid);
- StatusOr<uint8_t> value = mFakeUidOwnerMap.readValue(uid);
+ StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
EXPECT_TRUE(isOk(value));
- EXPECT_EQ(expectedValue, value.value()) <<
- "Expected value for UID " << uid << " to be " << expectedValue <<
- ", but was " << value.value();
+ EXPECT_EQ(expectedRule, value.value().rule)
+ << "Expected rule for UID " << uid << " to be " << expectedRule << ", but was "
+ << value.value().rule;
+ EXPECT_EQ(expectedIif, value.value().iif)
+ << "Expected iif for UID " << uid << " to be " << expectedIif << ", but was "
+ << value.value().iif;
}
}
- void expectMapEmpty(BpfMap<uint64_t, UidTag>& map) {
+ template <class Key, class Value>
+ void expectMapEmpty(BpfMap<Key, Value>& map) {
auto isEmpty = map.isEmpty();
EXPECT_TRUE(isOk(isEmpty));
EXPECT_TRUE(isEmpty.value());
}
- void expectMapEmpty(BpfMap<uint32_t, uint8_t>& map) {
- auto isEmpty = map.isEmpty();
- ASSERT_TRUE(isOk(isEmpty));
- ASSERT_TRUE(isEmpty.value());
- }
-
void expectUidPermissionMapValues(const std::vector<uid_t>& appUids, uint8_t expectedValue) {
for (uid_t uid : appUids) {
StatusOr<uint8_t> value = mFakeUidPermissionMap.readValue(uid);
@@ -549,19 +547,19 @@
uint32_t uid = TEST_UID;
ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, BLACKLIST)));
- StatusOr<uint8_t> value = mFakeUidOwnerMap.readValue(uid);
+ StatusOr<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
ASSERT_TRUE(isOk(value));
- ASSERT_TRUE(value.value() & STANDBY_MATCH);
+ ASSERT_TRUE(value.value().rule & STANDBY_MATCH);
ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, ALLOW, WHITELIST)));
value = mFakeUidOwnerMap.readValue(uid);
ASSERT_TRUE(isOk(value));
- ASSERT_TRUE(value.value() & DOZABLE_MATCH);
+ ASSERT_TRUE(value.value().rule & DOZABLE_MATCH);
ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(DOZABLE_MATCH, uid, DENY, WHITELIST)));
value = mFakeUidOwnerMap.readValue(uid);
ASSERT_TRUE(isOk(value));
- ASSERT_FALSE(value.value() & DOZABLE_MATCH);
+ ASSERT_FALSE(value.value().rule & DOZABLE_MATCH);
ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, ALLOW, BLACKLIST)));
ASSERT_FALSE(isOk(mFakeUidOwnerMap.readValue(uid)));
@@ -606,7 +604,7 @@
std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
BandwidthController::IptOpInsert)));
- expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
BandwidthController::IptOpDelete)));
expectMapEmpty(mFakeUidOwnerMap);
@@ -618,7 +616,7 @@
std::vector<std::string> appStrUids = {"1000", "1001", "10012"};
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
BandwidthController::IptOpInsert)));
- expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
BandwidthController::IptOpDelete)));
expectMapEmpty(mFakeUidOwnerMap);
@@ -631,18 +629,18 @@
// Add appStrUids to the blacklist and expect that their values are all PENALTY_BOX_MATCH.
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
BandwidthController::IptOpInsert)));
- expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
// Add the same UIDs to the whitelist and expect that we get PENALTY_BOX_MATCH |
// HAPPY_BOX_MATCH.
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
BandwidthController::IptOpInsert)));
- expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0);
// Remove the same UIDs from the whitelist and check the PENALTY_BOX_MATCH is still there.
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
BandwidthController::IptOpDelete)));
- expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, PENALTY_BOX_MATCH, 0);
// Remove the same UIDs from the blacklist and check the map is empty.
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
@@ -662,7 +660,7 @@
// Add blacklist rules for appStrUids.
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReturn,
BandwidthController::IptOpInsert)));
- expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
// Delete (non-existent) blacklist rules for appStrUids, and check that this silently does
// nothing if the uid is in the map but does not have blacklist match. This is required because
@@ -670,7 +668,118 @@
// whitelist and if the remove fails it will not update the uid status.
ASSERT_TRUE(isOk(mTc.updateUidOwnerMap(appStrUids, BandwidthController::IptJumpReject,
BandwidthController::IptOpDelete)));
- expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH);
+ expectUidOwnerMapValues(appStrUids, HAPPY_BOX_MATCH, 0);
+}
+
+TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRules) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+
+ int iif0 = 15;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
+ expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+
+ // Add some non-overlapping new uids. They should coexist with existing rules
+ int iif1 = 16;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
+ expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({"2000", "2001"}, IIF_MATCH, iif1);
+
+ // Overwrite some existing uids
+ int iif2 = 17;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif2, {1000, 2000})));
+ expectUidOwnerMapValues({"1001"}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({"2001"}, IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"1000", "2000"}, IIF_MATCH, iif2);
+}
+
+TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRules) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+
+ int iif0 = 15;
+ int iif1 = 16;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif0, {1000, 1001})));
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {2000, 2001})));
+ expectUidOwnerMapValues({"1000", "1001"}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({"2000", "2001"}, IIF_MATCH, iif1);
+
+ // Rmove some uids
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001, 2001})));
+ expectUidOwnerMapValues({"1000"}, IIF_MATCH, iif0);
+ expectUidOwnerMapValues({"2000"}, IIF_MATCH, iif1);
+ checkEachUidValue({1000, 2000}, IIF_MATCH); // Make sure there are only two uids remaining
+
+ // Remove non-existent uids shouldn't fail
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({2000, 3000})));
+ expectUidOwnerMapValues({"1000"}, IIF_MATCH, iif0);
+ checkEachUidValue({1000}, IIF_MATCH); // Make sure there are only one uid remaining
+
+ // Remove everything
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+
+ // Set up existing PENALTY_BOX_MATCH rules
+ ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({"1000", "1001", "10012"},
+ BandwidthController::IptJumpReject,
+ BandwidthController::IptOpInsert)));
+ expectUidOwnerMapValues({"1000", "1001", "10012"}, PENALTY_BOX_MATCH, 0);
+
+ // Add some partially-overlapping uid owner rules and check result
+ int iif1 = 32;
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10012, 10013, 10014})));
+ expectUidOwnerMapValues({"1000", "1001"}, PENALTY_BOX_MATCH, 0);
+ expectUidOwnerMapValues({"10012"}, PENALTY_BOX_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10013", "10014"}, IIF_MATCH, iif1);
+
+ // Removing some PENALTY_BOX_MATCH rules should not change uid interface rule
+ ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({"1001", "10012"}, BandwidthController::IptJumpReject,
+ BandwidthController::IptOpDelete)));
+ expectUidOwnerMapValues({"1000"}, PENALTY_BOX_MATCH, 0);
+ expectUidOwnerMapValues({"10012", "10013", "10014"}, IIF_MATCH, iif1);
+
+ // Remove all uid interface rules
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({10012, 10013, 10014})));
+ expectUidOwnerMapValues({"1000"}, PENALTY_BOX_MATCH, 0);
+ // Make sure these are the only uids left
+ checkEachUidValue({1000}, PENALTY_BOX_MATCH);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithNewMatches) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+
+ int iif1 = 56;
+ // Set up existing uid interface rules
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif1, {10001, 10002})));
+ expectUidOwnerMapValues({"10001", "10002"}, IIF_MATCH, iif1);
+
+ // Add some partially-overlapping doze rules
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {10002, 10003}));
+ expectUidOwnerMapValues({"10001"}, IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10002"}, DOZABLE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10003"}, DOZABLE_MATCH, 0);
+
+ // Introduce a third rule type (powersave) on various existing UIDs
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {10000, 10001, 10002, 10003}));
+ expectUidOwnerMapValues({"10000"}, POWERSAVE_MATCH, 0);
+ expectUidOwnerMapValues({"10001"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10002"}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10003"}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
+
+ // Remove all doze rules
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_dozable", true, {}));
+ expectUidOwnerMapValues({"10000"}, POWERSAVE_MATCH, 0);
+ expectUidOwnerMapValues({"10001"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10002"}, POWERSAVE_MATCH | IIF_MATCH, iif1);
+ expectUidOwnerMapValues({"10003"}, POWERSAVE_MATCH, 0);
+
+ // Remove all powersave rules, expect ownerMap to only have uid interface rules left
+ EXPECT_EQ(0, mTc.replaceUidOwnerMap("fw_powersave", true, {}));
+ expectUidOwnerMapValues({"10001", "10002"}, IIF_MATCH, iif1);
+ // Make sure these are the only uids left
+ checkEachUidValue({10001, 10002}, IIF_MATCH);
}
TEST_F(TrafficControllerTest, TestGrantInternetPermission) {