#include "DumpWriter.h"
#include "NetdConstants.h"
#include "NetdNativeService.h"
+#include "RouteController.h"
+#include "UidRanges.h"
using android::base::StringPrintf;
return binder::Status::ok();
}
+binder::Status NetdNativeService::networkRejectNonSecureVpn(bool add,
+ const std::vector<UidRange>& uidRangeArray) {
+ // TODO: elsewhere RouteController is only used from the tethering and network controllers, so
+ // it should be possible to use the same lock as NetworkController. However, every call through
+ // the CommandListener "network" command will need to hold this lock too, not just the ones that
+ // read/modify network internal state (that is sufficient for ::dump() because it doesn't
+ // look at routes, but it's not enough here).
+ NETD_BIG_LOCK_RPC(CONNECTIVITY_INTERNAL);
+
+ UidRanges uidRanges;
+ uidRanges.createFrom(uidRangeArray);
+
+ int err;
+ if (add) {
+ err = RouteController::addUsersToRejectNonSecureNetworkRule(uidRanges);
+ } else {
+ err = RouteController::removeUsersFromRejectNonSecureNetworkRule(uidRanges);
+ }
+
+ if (err != 0) {
+ return binder::Status::fromServiceSpecificError(-err,
+ String8::format("RouteController error: %s", strerror(-err)));
+ }
+ return binder::Status::ok();
+}
+
} // namespace net
} // namespace android
const String16& chainName, bool isWhitelist,
const std::vector<int32_t>& uids, bool *ret) override;
binder::Status bandwidthEnableDataSaver(bool enable, bool *ret) override;
-
+ binder::Status networkRejectNonSecureVpn(bool enable, const std::vector<UidRange>& uids)
+ override;
};
} // namespace net
const uint32_t RULE_PRIORITY_VPN_OVERRIDE_SYSTEM = 10000;
const uint32_t RULE_PRIORITY_VPN_OVERRIDE_OIF = 10500;
const uint32_t RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000;
+const uint32_t RULE_PRIORITY_PROHIBIT_NON_VPN = 11500;
const uint32_t RULE_PRIORITY_SECURE_VPN = 12000;
const uint32_t RULE_PRIORITY_EXPLICIT_NETWORK = 13000;
const uint32_t RULE_PRIORITY_OUTPUT_INTERFACE = 14000;
return modifyImplicitNetworkRule(netId, table, permission, add);
}
+WARN_UNUSED_RESULT int modifyRejectNonSecureNetworkRule(const UidRanges& uidRanges, bool add) {
+ Fwmark fwmark;
+ Fwmark mask;
+ fwmark.protectedFromVpn = false;
+ mask.protectedFromVpn = true;
+
+ for (const UidRanges::Range& range : uidRanges.getRanges()) {
+ if (int ret = modifyIpRule(add ? RTM_NEWRULE : RTM_DELRULE,
+ RULE_PRIORITY_PROHIBIT_NON_VPN, FR_ACT_PROHIBIT, RT_TABLE_UNSPEC,
+ fwmark.intValue, mask.intValue, IIF_LOOPBACK, OIF_NONE,
+ range.first, range.second)) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
WARN_UNUSED_RESULT int modifyVirtualNetwork(unsigned netId, const char* interface,
const UidRanges& uidRanges, bool secure, bool add,
bool modifyNonUidBasedRules) {
return modifyPhysicalNetwork(netId, interface, oldPermission, ACTION_DEL);
}
+int RouteController::addUsersToRejectNonSecureNetworkRule(const UidRanges& uidRanges) {
+ return modifyRejectNonSecureNetworkRule(uidRanges, true);
+}
+
+int RouteController::removeUsersFromRejectNonSecureNetworkRule(const UidRanges& uidRanges) {
+ return modifyRejectNonSecureNetworkRule(uidRanges, false);
+}
+
int RouteController::addUsersToVirtualNetwork(unsigned netId, const char* interface, bool secure,
const UidRanges& uidRanges) {
return modifyVirtualNetwork(netId, interface, uidRanges, secure, ACTION_ADD,
static int removeUsersFromVirtualNetwork(unsigned netId, const char* interface, bool secure,
const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+ static int addUsersToRejectNonSecureNetworkRule(const UidRanges& uidRanges)
+ WARN_UNUSED_RESULT;
+ static int removeUsersFromRejectNonSecureNetworkRule(const UidRanges& uidRanges)
+ WARN_UNUSED_RESULT;
+
static int addInterfaceToDefaultNetwork(const char* interface,
Permission permission) WARN_UNUSED_RESULT;
static int removeInterfaceFromDefaultNetwork(const char* interface,
return true;
}
+void UidRanges::createFrom(const std::vector<android::net::UidRange>& ranges) {
+ mRanges.resize(ranges.size());
+ std::transform(ranges.begin(), ranges.end(), mRanges.begin(),
+ [](const android::net::UidRange& range) {
+ return Range(range.getStart(), range.getStop());
+ });
+ std::sort(mRanges.begin(), mRanges.end());
+}
+
void UidRanges::add(const UidRanges& other) {
auto middle = mRanges.insert(mRanges.end(), other.mRanges.begin(), other.mRanges.end());
std::inplace_merge(mRanges.begin(), middle, mRanges.end());
class UidRanges {
public:
+ // TODO: replace with AIDL type: android::net::UidRange
+ // int32_t may not be a safe replacement for uid_t. If not, UidRange will need to change to use
+ // a larger type first.
typedef std::pair<uid_t, uid_t> Range;
bool hasUid(uid_t uid) const;
const std::vector<Range>& getRanges() const;
bool parseFrom(int argc, char* argv[]);
+ void createFrom(const std::vector<android::net::UidRange>& ranges);
std::string toString() const;
void add(const UidRanges& other);
package android.net;
+import android.net.UidRange;
+
/** {@hide} */
interface INetd {
/**
* @return true if the if the operation was successful, false otherwise.
*/
boolean bandwidthEnableDataSaver(boolean enable);
+
+ /**
+ * Adds or removes one rule for each supplied UID range to prohibit all network activity outside
+ * of secure VPN.
+ *
+ * When a UID is covered by one of these rules, traffic sent through any socket that is not
+ * protected or explicitly overriden by the system will be rejected. The kernel will respond
+ * with an ICMP prohibit message.
+ *
+ * Initially, there are no such rules. Any rules that are added will only last until the next
+ * restart of netd or the device.
+ *
+ * @param add {@code true} if the specified UID ranges should be denied access to any network
+ * which is not secure VPN by adding rules, {@code false} to remove existing rules.
+ * @param uidRanges a set of non-overlapping, contiguous ranges of UIDs to which to apply or
+ * remove this restriction.
+ * <p> Added rules should not overlap with existing rules. Likewise, removed rules should
+ * each correspond to an existing rule.
+ *
+ * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+ * unix errno.
+ */
+ void networkRejectNonSecureVpn(boolean add, in UidRange[] uidRanges);
}
* binder_test.cpp - unit tests for netd binder RPCs.
*/
+#include <cerrno>
+#include <cinttypes>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <cutils/multiuser.h>
#include <gtest/gtest.h>
#include <logwrap/logwrap.h>
#include "NetdConstants.h"
#include "android/net/INetd.h"
+#include "android/net/UidRange.h"
#include "binder/IServiceManager.h"
using namespace android;
using namespace android::base;
using namespace android::binder;
using android::net::INetd;
+using android::net::UidRange;
+
+static const char* IP_RULE_V4 = "-4";
+static const char* IP_RULE_V6 = "-6";
class BinderTest : public ::testing::Test {
return 100000 * arc4random_uniform(7) + 10000 + arc4random_uniform(5000);
}
-static std::vector<std::string> listIptablesRule(const char *binary, const char *chainName) {
+static std::vector<std::string> runCommand(const std::string& command) {
std::vector<std::string> lines;
FILE *f;
- std::string command = StringPrintf("%s -n -L %s", binary, chainName);
if ((f = popen(command.c_str(), "r")) == nullptr) {
perror("popen");
return lines;
}
char *line = nullptr;
- size_t linelen = 0;
- while (getline(&line, &linelen, f) >= 0) {
+ size_t bufsize = 0;
+ ssize_t linelen = 0;
+ while ((linelen = getline(&line, &bufsize, f)) >= 0) {
lines.push_back(std::string(line, linelen));
free(line);
line = nullptr;
return lines;
}
+static std::vector<std::string> listIpRules(const char *ipVersion) {
+ std::string command = StringPrintf("%s %s rule list", IP_PATH, ipVersion);
+ return runCommand(command);
+}
+
+static std::vector<std::string> listIptablesRule(const char *binary, const char *chainName) {
+ std::string command = StringPrintf("%s -n -L %s", binary, chainName);
+ return runCommand(command);
+}
+
static int iptablesRuleLineLength(const char *binary, const char *chainName) {
return listIptablesRule(binary, chainName).size();
}
-
TEST_F(BinderTest, TestFirewallReplaceUidChain) {
std::string chainName = StringPrintf("netd_binder_test_%u", arc4random_uniform(10000));
const int kNumUids = 500;
EXPECT_EQ(0, getDataSaverState());
}
}
+
+static bool ipRuleExistsForRange(const uint32_t priority, const UidRange& range,
+ const std::string& action, const char* ipVersion) {
+ // Output looks like this:
+ // "11500:\tfrom all fwmark 0x0/0x20000 iif lo uidrange 1000-2000 prohibit"
+ std::vector<std::string> rules = listIpRules(ipVersion);
+
+ std::string prefix = StringPrintf("%" PRIu32 ":", priority);
+ std::string suffix = StringPrintf(" iif lo uidrange %d-%d %s\n",
+ range.getStart(), range.getStop(), action.c_str());
+ for (std::string line : rules) {
+ if (android::base::StartsWith(line, prefix.c_str())
+ && android::base::EndsWith(line, suffix.c_str())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool ipRuleExistsForRange(const uint32_t priority, const UidRange& range,
+ const std::string& action) {
+ bool existsIp4 = ipRuleExistsForRange(priority, range, action, IP_RULE_V4);
+ bool existsIp6 = ipRuleExistsForRange(priority, range, action, IP_RULE_V6);
+ EXPECT_EQ(existsIp4, existsIp6);
+ return existsIp4;
+}
+
+TEST_F(BinderTest, TestNetworkRejectNonSecureVpn) {
+ constexpr uint32_t RULE_PRIORITY = 11500;
+
+ constexpr int baseUid = MULTIUSER_APP_PER_USER_RANGE * 5;
+ std::vector<UidRange> uidRanges = {
+ {baseUid + 150, baseUid + 224},
+ {baseUid + 226, baseUid + 300}
+ };
+
+ const std::vector<std::string> initialRulesV4 = listIpRules(IP_RULE_V4);
+ const std::vector<std::string> initialRulesV6 = listIpRules(IP_RULE_V6);
+
+ // Create two valid rules.
+ ASSERT_TRUE(mNetd->networkRejectNonSecureVpn(true, uidRanges).isOk());
+ EXPECT_EQ(initialRulesV4.size() + 2, listIpRules(IP_RULE_V4).size());
+ EXPECT_EQ(initialRulesV6.size() + 2, listIpRules(IP_RULE_V6).size());
+ for (auto const& range : uidRanges) {
+ EXPECT_TRUE(ipRuleExistsForRange(RULE_PRIORITY, range, "prohibit"));
+ }
+
+ // Remove the rules.
+ ASSERT_TRUE(mNetd->networkRejectNonSecureVpn(false, uidRanges).isOk());
+ EXPECT_EQ(initialRulesV4.size(), listIpRules(IP_RULE_V4).size());
+ EXPECT_EQ(initialRulesV6.size(), listIpRules(IP_RULE_V6).size());
+ for (auto const& range : uidRanges) {
+ EXPECT_FALSE(ipRuleExistsForRange(RULE_PRIORITY, range, "prohibit"));
+ }
+
+ // Fail to remove the rules a second time after they are already deleted.
+ binder::Status status = mNetd->networkRejectNonSecureVpn(false, uidRanges);
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode());
+ EXPECT_EQ(ENOENT, status.serviceSpecificErrorCode());
+
+ // All rules should be the same as before.
+ EXPECT_EQ(initialRulesV4, listIpRules(IP_RULE_V4));
+ EXPECT_EQ(initialRulesV6, listIpRules(IP_RULE_V6));
+}