FirewallController: discover max uid in the current user namespace
This patch gives the capability to FirewallController to discover the
maximum valid uid in the user namespace in which netd is currently
running, and uses that value in the whitelist uid rules.
This is done by parsing the content of /proc/self/uid_map as explained
in the man page of 'user_namespaces'.
On the default root namespace the maximum uid is expected to be
UINT32_MAX - 1, but this assumption is incorrect in other user
namespaces created for instance for container environments.
The uid mapping is de facto constant from within the user namespace and
cannot be modified from inside (more precisely uid_map and gid_map proc
files can only be written once each for a new user namespacE).
netd makes the assumption that the uid mapping stays constant, meaning
it is a bug if the host namespace tries to remap uids after netd starts.
Bug: 110459356
Test: - built,
- flashed and booted a marlin, 'fw_powersave' rule is as expected
- flashed and booted ARC++ container, 'fw_powersave' rule is as
expected
- new unit tests pass
Change-Id: I44a885c34e174b0067848b860be8d7b8f3e83296
diff --git a/server/FirewallController.cpp b/server/FirewallController.cpp
index 6e572af..ff1f19d 100644
--- a/server/FirewallController.cpp
+++ b/server/FirewallController.cpp
@@ -16,17 +16,19 @@
#include <set>
-#include <cstdint>
#include <errno.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <cstdint>
#define LOG_TAG "FirewallController"
#define LOG_NDEBUG 0
-#include <android-base/strings.h>
+#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <log/log.h>
#include "Controllers.h"
@@ -35,6 +37,8 @@
#include "bpf/BpfUtils.h"
using android::base::Join;
+using android::base::ReadFileToString;
+using android::base::Split;
using android::base::StringAppendF;
using android::base::StringPrintf;
using android::bpf::DOZABLE_UID_MAP_PATH;
@@ -42,6 +46,22 @@
using android::bpf::STANDBY_UID_MAP_PATH;
using android::net::gCtls;
+namespace {
+
+// Default maximum valid uid in a normal root user namespace. The maximum valid uid is used in
+// rules that exclude all possible UIDs in the namespace in order to match packets that have
+// no socket associated with them.
+constexpr const uid_t kDefaultMaximumUid = UID_MAX - 1; // UID_MAX defined as UINT_MAX
+
+// Proc file containing the uid mapping for the user namespace of the current process.
+const char kUidMapProcFile[] = "/proc/self/uid_map";
+
+bool getBpfOwnerStatus() {
+ return gCtls->trafficCtrl.checkBpfStatsEnable();
+}
+
+} // namespace
+
auto FirewallController::execIptablesRestore = ::execIptablesRestore;
const char* FirewallController::TABLE = "filter";
@@ -66,11 +86,7 @@
"redirect",
};
-bool getBpfOwnerStatus() {
- return gCtls->trafficCtrl.checkBpfStatsEnable();
-}
-
-FirewallController::FirewallController(void) {
+FirewallController::FirewallController(void) : mMaxUid(discoverMaximumValidUid(kUidMapProcFile)) {
// If no rules are set, it's in BLACKLIST mode
mFirewallType = BLACKLIST;
mIfaceRules = {};
@@ -292,7 +308,7 @@
// This rule inverts the match for all UIDs; ie, if there is no UID match here,
// there is no socket to be found
StringAppendF(&commands,
- "-A %s -m owner ! --uid-owner %d-%u -j RETURN\n", name, 0, UINT32_MAX-1);
+ "-A %s -m owner ! --uid-owner %d-%u -j RETURN\n", name, 0, mMaxUid);
// Always whitelist traffic with protocol ESP, or no known socket - required for IPSec
StringAppendF(&commands, "-A %s -p esp -j RETURN\n", name);
@@ -337,3 +353,46 @@
std::string commands6 = makeUidRules(V6, name.c_str(), isWhitelist, uids);
return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
}
+
+/* static */
+uid_t FirewallController::discoverMaximumValidUid(const std::string& fileName) {
+ std::string content;
+ if (!ReadFileToString(fileName, &content, false)) {
+ // /proc/self/uid_map only exists if a uid mapping has been set.
+ ALOGD("Could not read %s, max uid defaulting to %u", fileName.c_str(), kDefaultMaximumUid);
+ return kDefaultMaximumUid;
+ }
+
+ std::vector<std::string> lines = Split(content, "\n");
+ if (lines.empty()) {
+ ALOGD("%s was empty, max uid defaulting to %u", fileName.c_str(), kDefaultMaximumUid);
+ return kDefaultMaximumUid;
+ }
+
+ uint32_t maxUid = 0;
+ for (const auto& line : lines) {
+ if (line.empty()) {
+ continue;
+ }
+
+ // Choose the end of the largest range found in the file.
+ uint32_t start;
+ uint32_t ignored;
+ uint32_t rangeLength;
+ int items = sscanf(line.c_str(), "%u %u %u", &start, &ignored, &rangeLength);
+ if (items != 3) {
+ // uid_map lines must have 3 items, see the man page of 'user_namespaces' for details.
+ ALOGD("Format of %s unrecognized, max uid defaulting to %u", fileName.c_str(),
+ kDefaultMaximumUid);
+ return kDefaultMaximumUid;
+ }
+ maxUid = std::max(maxUid, start + rangeLength - 1);
+ }
+
+ if (maxUid == 0) {
+ ALOGD("No max uid found, max uid defaulting to %u", kDefaultMaximumUid);
+ return kDefaultMaximumUid;
+ }
+
+ return maxUid;
+}