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 <errno.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #define LOG_TAG "FirewallController"
23 #define LOG_NDEBUG 0
24 
25 #include <android-base/stringprintf.h>
26 #include <cutils/log.h>
27 
28 #include "NetdConstants.h"
29 #include "FirewallController.h"
30 
31 using android::base::StringAppendF;
32 
33 auto FirewallController::execIptables = ::execIptables;
34 auto FirewallController::execIptablesSilently = ::execIptables;
35 auto FirewallController::execIptablesRestore = ::execIptablesRestore;
36 
37 const char* FirewallController::TABLE = "filter";
38 
39 const char* FirewallController::LOCAL_INPUT = "fw_INPUT";
40 const char* FirewallController::LOCAL_OUTPUT = "fw_OUTPUT";
41 const char* FirewallController::LOCAL_FORWARD = "fw_FORWARD";
42 
43 const char* FirewallController::LOCAL_DOZABLE = "fw_dozable";
44 const char* FirewallController::LOCAL_STANDBY = "fw_standby";
45 const char* FirewallController::LOCAL_POWERSAVE = "fw_powersave";
46 
47 // ICMPv6 types that are required for any form of IPv6 connectivity to work. Note that because the
48 // fw_dozable chain is called from both INPUT and OUTPUT, this includes both packets that we need
49 // to be able to send (e.g., RS, NS), and packets that we need to receive (e.g., RA, NA).
50 const char* FirewallController::ICMPV6_TYPES[] = {
51     "packet-too-big",
52     "router-solicitation",
53     "router-advertisement",
54     "neighbour-solicitation",
55     "neighbour-advertisement",
56     "redirect",
57 };
58 
FirewallController(void)59 FirewallController::FirewallController(void) {
60     // If no rules are set, it's in BLACKLIST mode
61     mFirewallType = BLACKLIST;
62 }
63 
setupIptablesHooks(void)64 int FirewallController::setupIptablesHooks(void) {
65     int res = 0;
66     // child chains are created but not attached, they will be attached explicitly.
67     FirewallType firewallType = getFirewallType(DOZABLE);
68     res |= createChain(LOCAL_DOZABLE, LOCAL_INPUT, firewallType);
69 
70     firewallType = getFirewallType(STANDBY);
71     res |= createChain(LOCAL_STANDBY, LOCAL_INPUT, firewallType);
72 
73     firewallType = getFirewallType(POWERSAVE);
74     res |= createChain(LOCAL_POWERSAVE, LOCAL_INPUT, firewallType);
75 
76     return res;
77 }
78 
enableFirewall(FirewallType ftype)79 int FirewallController::enableFirewall(FirewallType ftype) {
80     int res = 0;
81     if (mFirewallType != ftype) {
82         // flush any existing rules
83         disableFirewall();
84 
85         if (ftype == WHITELIST) {
86             // create default rule to drop all traffic
87             res |= execIptables(V4V6, "-A", LOCAL_INPUT, "-j", "DROP", NULL);
88             res |= execIptables(V4V6, "-A", LOCAL_OUTPUT, "-j", "REJECT", NULL);
89             res |= execIptables(V4V6, "-A", LOCAL_FORWARD, "-j", "REJECT", NULL);
90         }
91 
92         // Set this after calling disableFirewall(), since it defaults to WHITELIST there
93         mFirewallType = ftype;
94     }
95     return res;
96 }
97 
disableFirewall(void)98 int FirewallController::disableFirewall(void) {
99     int res = 0;
100 
101     mFirewallType = WHITELIST;
102 
103     // flush any existing rules
104     res |= execIptables(V4V6, "-F", LOCAL_INPUT, NULL);
105     res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
106     res |= execIptables(V4V6, "-F", LOCAL_FORWARD, NULL);
107 
108     return res;
109 }
110 
enableChildChains(ChildChain chain,bool enable)111 int FirewallController::enableChildChains(ChildChain chain, bool enable) {
112     int res = 0;
113     const char* name;
114     switch(chain) {
115         case DOZABLE:
116             name = LOCAL_DOZABLE;
117             break;
118         case STANDBY:
119             name = LOCAL_STANDBY;
120             break;
121         case POWERSAVE:
122             name = LOCAL_POWERSAVE;
123             break;
124         default:
125             return res;
126     }
127 
128     if (enable) {
129         res |= attachChain(name, LOCAL_INPUT);
130         res |= attachChain(name, LOCAL_OUTPUT);
131     } else {
132         res |= detachChain(name, LOCAL_INPUT);
133         res |= detachChain(name, LOCAL_OUTPUT);
134     }
135     return res;
136 }
137 
isFirewallEnabled(void)138 int FirewallController::isFirewallEnabled(void) {
139     // TODO: verify that rules are still in place near top
140     return -1;
141 }
142 
setInterfaceRule(const char * iface,FirewallRule rule)143 int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
144     if (mFirewallType == BLACKLIST) {
145         // Unsupported in BLACKLIST mode
146         return -1;
147     }
148 
149     if (!isIfaceName(iface)) {
150         errno = ENOENT;
151         return -1;
152     }
153 
154     const char* op;
155     if (rule == ALLOW) {
156         op = "-I";
157     } else {
158         op = "-D";
159     }
160 
161     int res = 0;
162     res |= execIptables(V4V6, op, LOCAL_INPUT, "-i", iface, "-j", "RETURN", NULL);
163     res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-o", iface, "-j", "RETURN", NULL);
164     return res;
165 }
166 
setEgressSourceRule(const char * addr,FirewallRule rule)167 int FirewallController::setEgressSourceRule(const char* addr, FirewallRule rule) {
168     if (mFirewallType == BLACKLIST) {
169         // Unsupported in BLACKLIST mode
170         return -1;
171     }
172 
173     IptablesTarget target = V4;
174     if (strchr(addr, ':')) {
175         target = V6;
176     }
177 
178     const char* op;
179     if (rule == ALLOW) {
180         op = "-I";
181     } else {
182         op = "-D";
183     }
184 
185     int res = 0;
186     res |= execIptables(target, op, LOCAL_INPUT, "-d", addr, "-j", "RETURN", NULL);
187     res |= execIptables(target, op, LOCAL_OUTPUT, "-s", addr, "-j", "RETURN", NULL);
188     return res;
189 }
190 
setEgressDestRule(const char * addr,int protocol,int port,FirewallRule rule)191 int FirewallController::setEgressDestRule(const char* addr, int protocol, int port,
192         FirewallRule rule) {
193     if (mFirewallType == BLACKLIST) {
194         // Unsupported in BLACKLIST mode
195         return -1;
196     }
197 
198     IptablesTarget target = V4;
199     if (strchr(addr, ':')) {
200         target = V6;
201     }
202 
203     char protocolStr[16];
204     sprintf(protocolStr, "%d", protocol);
205 
206     char portStr[16];
207     sprintf(portStr, "%d", port);
208 
209     const char* op;
210     if (rule == ALLOW) {
211         op = "-I";
212     } else {
213         op = "-D";
214     }
215 
216     int res = 0;
217     res |= execIptables(target, op, LOCAL_INPUT, "-s", addr, "-p", protocolStr,
218             "--sport", portStr, "-j", "RETURN", NULL);
219     res |= execIptables(target, op, LOCAL_OUTPUT, "-d", addr, "-p", protocolStr,
220             "--dport", portStr, "-j", "RETURN", NULL);
221     return res;
222 }
223 
getFirewallType(ChildChain chain)224 FirewallType FirewallController::getFirewallType(ChildChain chain) {
225     switch(chain) {
226         case DOZABLE:
227             return WHITELIST;
228         case STANDBY:
229             return BLACKLIST;
230         case POWERSAVE:
231             return WHITELIST;
232         case NONE:
233             return mFirewallType;
234         default:
235             return BLACKLIST;
236     }
237 }
238 
setUidRule(ChildChain chain,int uid,FirewallRule rule)239 int FirewallController::setUidRule(ChildChain chain, int uid, FirewallRule rule) {
240     char uidStr[16];
241     sprintf(uidStr, "%d", uid);
242 
243     const char* op;
244     const char* target;
245     FirewallType firewallType = getFirewallType(chain);
246     if (firewallType == WHITELIST) {
247         target = "RETURN";
248         // When adding, insert RETURN rules at the front, before the catch-all DROP at the end.
249         op = (rule == ALLOW)? "-I" : "-D";
250     } else { // BLACKLIST mode
251         target = "DROP";
252         // When adding, append DROP rules at the end, after the RETURN rule that matches TCP RSTs.
253         op = (rule == DENY)? "-A" : "-D";
254     }
255 
256     int res = 0;
257     switch(chain) {
258         case DOZABLE:
259             res |= execIptables(V4V6, op, LOCAL_DOZABLE, "-m", "owner", "--uid-owner",
260                     uidStr, "-j", target, NULL);
261             break;
262         case STANDBY:
263             res |= execIptables(V4V6, op, LOCAL_STANDBY, "-m", "owner", "--uid-owner",
264                     uidStr, "-j", target, NULL);
265             break;
266         case POWERSAVE:
267             res |= execIptables(V4V6, op, LOCAL_POWERSAVE, "-m", "owner", "--uid-owner",
268                     uidStr, "-j", target, NULL);
269             break;
270         case NONE:
271             res |= execIptables(V4V6, op, LOCAL_INPUT, "-m", "owner", "--uid-owner", uidStr,
272                     "-j", target, NULL);
273             res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-m", "owner", "--uid-owner", uidStr,
274                     "-j", target, NULL);
275             break;
276         default:
277             ALOGW("Unknown child chain: %d", chain);
278             break;
279     }
280     return res;
281 }
282 
attachChain(const char * childChain,const char * parentChain)283 int FirewallController::attachChain(const char* childChain, const char* parentChain) {
284     return execIptables(V4V6, "-t", TABLE, "-A", parentChain, "-j", childChain, NULL);
285 }
286 
detachChain(const char * childChain,const char * parentChain)287 int FirewallController::detachChain(const char* childChain, const char* parentChain) {
288     return execIptables(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
289 }
290 
createChain(const char * childChain,const char * parentChain,FirewallType type)291 int FirewallController::createChain(const char* childChain,
292         const char* parentChain, FirewallType type) {
293     execIptablesSilently(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
294     std::vector<int32_t> uids;
295     return replaceUidChain(childChain, type == WHITELIST, uids);
296 }
297 
makeUidRules(IptablesTarget target,const char * name,bool isWhitelist,const std::vector<int32_t> & uids)298 std::string FirewallController::makeUidRules(IptablesTarget target, const char *name,
299         bool isWhitelist, const std::vector<int32_t>& uids) {
300     std::string commands;
301     StringAppendF(&commands, "*filter\n:%s -\n", name);
302 
303     // Allow TCP RSTs so we can cleanly close TCP connections of apps that no longer have network
304     // access. Both incoming and outgoing RSTs are allowed.
305     StringAppendF(&commands, "-A %s -p tcp --tcp-flags RST RST -j RETURN\n", name);
306 
307     if (isWhitelist) {
308         // Allow ICMPv6 packets necessary to make IPv6 connectivity work. http://b/23158230 .
309         if (target == V6) {
310             for (size_t i = 0; i < ARRAY_SIZE(ICMPV6_TYPES); i++) {
311                 StringAppendF(&commands, "-A %s -p icmpv6 --icmpv6-type %s -j RETURN\n",
312                        name, ICMPV6_TYPES[i]);
313             }
314         }
315 
316         // Always whitelist system UIDs.
317         StringAppendF(&commands,
318                 "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
319     }
320 
321     // Whitelist or blacklist the specified UIDs.
322     const char *action = isWhitelist ? "RETURN" : "DROP";
323     for (auto uid : uids) {
324         StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j %s\n", name, uid, action);
325     }
326 
327     // If it's a whitelist chain, add a default DROP at the end. This is not necessary for a
328     // blacklist chain, because all user-defined chains implicitly RETURN at the end.
329     if (isWhitelist) {
330         StringAppendF(&commands, "-A %s -j DROP\n", name);
331     }
332 
333     StringAppendF(&commands, "COMMIT\n\x04");  // EOT.
334 
335     return commands;
336 }
337 
replaceUidChain(const char * name,bool isWhitelist,const std::vector<int32_t> & uids)338 int FirewallController::replaceUidChain(
339         const char *name, bool isWhitelist, const std::vector<int32_t>& uids) {
340    std::string commands4 = makeUidRules(V4, name, isWhitelist, uids);
341    std::string commands6 = makeUidRules(V6, name, isWhitelist, uids);
342    return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
343 }
344