1 /*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <set>
18
19 #include <errno.h>
20 #include <limits.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <cstdint>
25
26 #define LOG_TAG "FirewallController"
27 #define LOG_NDEBUG 0
28
29 #include <android-base/file.h>
30 #include <android-base/stringprintf.h>
31 #include <android-base/strings.h>
32 #include <log/log.h>
33
34 #include "Controllers.h"
35 #include "FirewallController.h"
36 #include "NetdConstants.h"
37 #include "bpf/BpfUtils.h"
38
39 using android::base::Join;
40 using android::base::ReadFileToString;
41 using android::base::Split;
42 using android::base::StringAppendF;
43 using android::base::StringPrintf;
44 using android::net::gCtls;
45
46 namespace {
47
48 // Default maximum valid uid in a normal root user namespace. The maximum valid uid is used in
49 // rules that exclude all possible UIDs in the namespace in order to match packets that have
50 // no socket associated with them.
51 constexpr const uid_t kDefaultMaximumUid = UID_MAX - 1; // UID_MAX defined as UINT_MAX
52
53 // Proc file containing the uid mapping for the user namespace of the current process.
54 const char kUidMapProcFile[] = "/proc/self/uid_map";
55
56 } // namespace
57
58 namespace android {
59 namespace net {
60
61 auto FirewallController::execIptablesRestore = ::execIptablesRestore;
62
63 const char* FirewallController::TABLE = "filter";
64
65 const char* FirewallController::LOCAL_INPUT = "fw_INPUT";
66 const char* FirewallController::LOCAL_OUTPUT = "fw_OUTPUT";
67 const char* FirewallController::LOCAL_FORWARD = "fw_FORWARD";
68
69 const char* FirewallController::LOCAL_DOZABLE = "fw_dozable";
70 const char* FirewallController::LOCAL_STANDBY = "fw_standby";
71 const char* FirewallController::LOCAL_POWERSAVE = "fw_powersave";
72 const char* FirewallController::LOCAL_RESTRICTED = "fw_restricted";
73
74 // ICMPv6 types that are required for any form of IPv6 connectivity to work. Note that because the
75 // fw_dozable chain is called from both INPUT and OUTPUT, this includes both packets that we need
76 // to be able to send (e.g., RS, NS), and packets that we need to receive (e.g., RA, NA).
77 const char* FirewallController::ICMPV6_TYPES[] = {
78 "packet-too-big",
79 "router-solicitation",
80 "router-advertisement",
81 "neighbour-solicitation",
82 "neighbour-advertisement",
83 "redirect",
84 };
85
FirewallController(void)86 FirewallController::FirewallController(void) : mMaxUid(discoverMaximumValidUid(kUidMapProcFile)) {
87 // If no rules are set, it's in DENYLIST mode
88 mFirewallType = DENYLIST;
89 mIfaceRules = {};
90 }
91
setupIptablesHooks(void)92 int FirewallController::setupIptablesHooks(void) {
93 int res = flushRules();
94
95 // mUseBpfOwnerMatch should be removed, but it is still depended upon by test code.
96 mUseBpfOwnerMatch = true;
97 if (mUseBpfOwnerMatch) {
98 return res;
99 }
100 res |= createChain(LOCAL_DOZABLE, getFirewallType(DOZABLE));
101 res |= createChain(LOCAL_STANDBY, getFirewallType(STANDBY));
102 res |= createChain(LOCAL_POWERSAVE, getFirewallType(POWERSAVE));
103 res |= createChain(LOCAL_RESTRICTED, getFirewallType(RESTRICTED));
104 return res;
105 }
106
setFirewallType(FirewallType ftype)107 int FirewallController::setFirewallType(FirewallType ftype) {
108 int res = 0;
109 if (mFirewallType != ftype) {
110 // flush any existing rules
111 resetFirewall();
112
113 if (ftype == ALLOWLIST) {
114 // create default rule to drop all traffic
115 std::string command =
116 "*filter\n"
117 "-A fw_INPUT -j DROP\n"
118 "-A fw_OUTPUT -j REJECT\n"
119 "-A fw_FORWARD -j REJECT\n"
120 "COMMIT\n";
121 res = execIptablesRestore(V4V6, command.c_str());
122 }
123
124 // Set this after calling disableFirewall(), since it defaults to ALLOWLIST there
125 mFirewallType = ftype;
126 }
127 return res ? -EREMOTEIO : 0;
128 }
129
flushRules()130 int FirewallController::flushRules() {
131 std::string command =
132 "*filter\n"
133 ":fw_INPUT -\n"
134 ":fw_OUTPUT -\n"
135 ":fw_FORWARD -\n"
136 "-6 -A fw_OUTPUT ! -o lo -s ::1 -j DROP\n"
137 "COMMIT\n";
138
139 return (execIptablesRestore(V4V6, command.c_str()) == 0) ? 0 : -EREMOTEIO;
140 }
141
resetFirewall(void)142 int FirewallController::resetFirewall(void) {
143 mFirewallType = ALLOWLIST;
144 mIfaceRules.clear();
145 return flushRules();
146 }
147
enableChildChains(ChildChain chain,bool enable)148 int FirewallController::enableChildChains(ChildChain chain, bool enable) {
149 int res = 0;
150 const char* name;
151 switch(chain) {
152 case DOZABLE:
153 name = LOCAL_DOZABLE;
154 break;
155 case STANDBY:
156 name = LOCAL_STANDBY;
157 break;
158 case POWERSAVE:
159 name = LOCAL_POWERSAVE;
160 break;
161 case RESTRICTED:
162 name = LOCAL_RESTRICTED;
163 break;
164 default:
165 return res;
166 }
167
168 if (mUseBpfOwnerMatch) {
169 return gCtls->trafficCtrl.toggleUidOwnerMap(chain, enable);
170 }
171
172 std::string command = "*filter\n";
173 for (const char *parent : { LOCAL_INPUT, LOCAL_OUTPUT }) {
174 StringAppendF(&command, "%s %s -j %s\n", (enable ? "-A" : "-D"), parent, name);
175 }
176 StringAppendF(&command, "COMMIT\n");
177
178 return execIptablesRestore(V4V6, command);
179 }
180
isFirewallEnabled(void)181 int FirewallController::isFirewallEnabled(void) {
182 // TODO: verify that rules are still in place near top
183 return -1;
184 }
185
setInterfaceRule(const char * iface,FirewallRule rule)186 int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
187 if (mFirewallType == DENYLIST) {
188 // Unsupported in DENYLIST mode
189 return -EINVAL;
190 }
191
192 if (!isIfaceName(iface)) {
193 errno = ENOENT;
194 return -ENOENT;
195 }
196
197 // Only delete rules if we actually added them, because otherwise our iptables-restore
198 // processes will terminate with "no such rule" errors and cause latency penalties while we
199 // spin up new ones.
200 const char* op;
201 if (rule == ALLOW && mIfaceRules.find(iface) == mIfaceRules.end()) {
202 op = "-I";
203 mIfaceRules.insert(iface);
204 } else if (rule == DENY && mIfaceRules.find(iface) != mIfaceRules.end()) {
205 op = "-D";
206 mIfaceRules.erase(iface);
207 } else {
208 return 0;
209 }
210
211 std::string command = Join(std::vector<std::string> {
212 "*filter",
213 StringPrintf("%s fw_INPUT -i %s -j RETURN", op, iface),
214 StringPrintf("%s fw_OUTPUT -o %s -j RETURN", op, iface),
215 "COMMIT\n"
216 }, "\n");
217 return (execIptablesRestore(V4V6, command) == 0) ? 0 : -EREMOTEIO;
218 }
219
getFirewallType(ChildChain chain)220 FirewallType FirewallController::getFirewallType(ChildChain chain) {
221 switch(chain) {
222 case DOZABLE:
223 return ALLOWLIST;
224 case STANDBY:
225 return DENYLIST;
226 case POWERSAVE:
227 return ALLOWLIST;
228 case RESTRICTED:
229 return ALLOWLIST;
230 case NONE:
231 return mFirewallType;
232 default:
233 return DENYLIST;
234 }
235 }
236
setUidRule(ChildChain chain,int uid,FirewallRule rule)237 int FirewallController::setUidRule(ChildChain chain, int uid, FirewallRule rule) {
238 const char* op;
239 const char* target;
240 FirewallType firewallType = getFirewallType(chain);
241 if (firewallType == ALLOWLIST) {
242 target = "RETURN";
243 // When adding, insert RETURN rules at the front, before the catch-all DROP at the end.
244 op = (rule == ALLOW)? "-I" : "-D";
245 } else { // DENYLIST mode
246 target = "DROP";
247 // When adding, append DROP rules at the end, after the RETURN rule that matches TCP RSTs.
248 op = (rule == DENY)? "-A" : "-D";
249 }
250
251 std::vector<std::string> chainNames;
252 switch(chain) {
253 case DOZABLE:
254 chainNames = {LOCAL_DOZABLE};
255 break;
256 case STANDBY:
257 chainNames = {LOCAL_STANDBY};
258 break;
259 case POWERSAVE:
260 chainNames = {LOCAL_POWERSAVE};
261 break;
262 case RESTRICTED:
263 chainNames = {LOCAL_RESTRICTED};
264 break;
265 case NONE:
266 chainNames = {LOCAL_INPUT, LOCAL_OUTPUT};
267 break;
268 default:
269 ALOGW("Unknown child chain: %d", chain);
270 return -EINVAL;
271 }
272 if (mUseBpfOwnerMatch) {
273 return gCtls->trafficCtrl.changeUidOwnerRule(chain, uid, rule, firewallType);
274 }
275
276 std::string command = "*filter\n";
277 for (const std::string& chainName : chainNames) {
278 StringAppendF(&command, "%s %s -m owner --uid-owner %d -j %s\n",
279 op, chainName.c_str(), uid, target);
280 }
281 StringAppendF(&command, "COMMIT\n");
282
283 return (execIptablesRestore(V4V6, command) == 0) ? 0 : -EREMOTEIO;
284 }
285
createChain(const char * chain,FirewallType type)286 int FirewallController::createChain(const char* chain, FirewallType type) {
287 static const std::vector<int32_t> NO_UIDS;
288 return replaceUidChain(chain, type == ALLOWLIST, NO_UIDS);
289 }
290
291 /* static */
makeCriticalCommands(IptablesTarget target,const char * chainName)292 std::string FirewallController::makeCriticalCommands(IptablesTarget target, const char* chainName) {
293 // Allow ICMPv6 packets necessary to make IPv6 connectivity work. http://b/23158230 .
294 std::string commands;
295 if (target == V6) {
296 for (size_t i = 0; i < ARRAY_SIZE(ICMPV6_TYPES); i++) {
297 StringAppendF(&commands, "-A %s -p icmpv6 --icmpv6-type %s -j RETURN\n",
298 chainName, ICMPV6_TYPES[i]);
299 }
300 }
301 return commands;
302 }
303
makeUidRules(IptablesTarget target,const char * name,bool isAllowlist,const std::vector<int32_t> & uids)304 std::string FirewallController::makeUidRules(IptablesTarget target, const char* name,
305 bool isAllowlist, const std::vector<int32_t>& uids) {
306 std::string commands;
307 StringAppendF(&commands, "*filter\n:%s -\n", name);
308
309 // Allowlist chains have UIDs at the beginning, and new UIDs are added with '-I'.
310 if (isAllowlist) {
311 for (auto uid : uids) {
312 StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j RETURN\n", name, uid);
313 }
314
315 // Always allowlist system UIDs.
316 StringAppendF(&commands,
317 "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
318
319 // This rule inverts the match for all UIDs; ie, if there is no UID match here,
320 // there is no socket to be found
321 StringAppendF(&commands,
322 "-A %s -m owner ! --uid-owner %d-%u -j RETURN\n", name, 0, mMaxUid);
323
324 // Always allowlist traffic with protocol ESP, or no known socket - required for IPSec
325 StringAppendF(&commands, "-A %s -p esp -j RETURN\n", name);
326 }
327
328 // Always allow networking on loopback.
329 StringAppendF(&commands, "-A %s -i lo -j RETURN\n", name);
330 StringAppendF(&commands, "-A %s -o lo -j RETURN\n", name);
331
332 // Allow TCP RSTs so we can cleanly close TCP connections of apps that no longer have network
333 // access. Both incoming and outgoing RSTs are allowed.
334 StringAppendF(&commands, "-A %s -p tcp --tcp-flags RST RST -j RETURN\n", name);
335
336 if (isAllowlist) {
337 commands.append(makeCriticalCommands(target, name));
338 }
339
340 // Denylist chains have UIDs at the end, and new UIDs are added with '-A'.
341 if (!isAllowlist) {
342 for (auto uid : uids) {
343 StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j DROP\n", name, uid);
344 }
345 }
346
347 // If it's an allowlist chain, add a default DROP at the end. This is not necessary for a
348 // denylist chain, because all user-defined chains implicitly RETURN at the end.
349 if (isAllowlist) {
350 StringAppendF(&commands, "-A %s -j DROP\n", name);
351 }
352
353 StringAppendF(&commands, "COMMIT\n");
354
355 return commands;
356 }
357
replaceUidChain(const std::string & name,bool isAllowlist,const std::vector<int32_t> & uids)358 int FirewallController::replaceUidChain(const std::string& name, bool isAllowlist,
359 const std::vector<int32_t>& uids) {
360 if (mUseBpfOwnerMatch) {
361 return gCtls->trafficCtrl.replaceUidOwnerMap(name, isAllowlist, uids);
362 }
363 std::string commands4 = makeUidRules(V4, name.c_str(), isAllowlist, uids);
364 std::string commands6 = makeUidRules(V6, name.c_str(), isAllowlist, uids);
365 return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
366 }
367
368 /* static */
discoverMaximumValidUid(const std::string & fileName)369 uid_t FirewallController::discoverMaximumValidUid(const std::string& fileName) {
370 std::string content;
371 if (!ReadFileToString(fileName, &content, false)) {
372 // /proc/self/uid_map only exists if a uid mapping has been set.
373 ALOGD("Could not read %s, max uid defaulting to %u", fileName.c_str(), kDefaultMaximumUid);
374 return kDefaultMaximumUid;
375 }
376
377 std::vector<std::string> lines = Split(content, "\n");
378 if (lines.empty()) {
379 ALOGD("%s was empty, max uid defaulting to %u", fileName.c_str(), kDefaultMaximumUid);
380 return kDefaultMaximumUid;
381 }
382
383 uint32_t maxUid = 0;
384 for (const auto& line : lines) {
385 if (line.empty()) {
386 continue;
387 }
388
389 // Choose the end of the largest range found in the file.
390 uint32_t start;
391 uint32_t ignored;
392 uint32_t rangeLength;
393 int items = sscanf(line.c_str(), "%u %u %u", &start, &ignored, &rangeLength);
394 if (items != 3) {
395 // uid_map lines must have 3 items, see the man page of 'user_namespaces' for details.
396 ALOGD("Format of %s unrecognized, max uid defaulting to %u", fileName.c_str(),
397 kDefaultMaximumUid);
398 return kDefaultMaximumUid;
399 }
400 maxUid = std::max(maxUid, start + rangeLength - 1);
401 }
402
403 if (maxUid == 0) {
404 ALOGD("No max uid found, max uid defaulting to %u", kDefaultMaximumUid);
405 return kDefaultMaximumUid;
406 }
407
408 return maxUid;
409 }
410
411 } // namespace net
412 } // namespace android
413