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
31using android::base::StringAppendF;
32
33auto FirewallController::execIptables = ::execIptables;
34auto FirewallController::execIptablesSilently = ::execIptablesSilently;
35auto FirewallController::execIptablesRestore = ::execIptablesRestore;
36
37const char* FirewallController::TABLE = "filter";
38
39const char* FirewallController::LOCAL_INPUT = "fw_INPUT";
40const char* FirewallController::LOCAL_OUTPUT = "fw_OUTPUT";
41const char* FirewallController::LOCAL_FORWARD = "fw_FORWARD";
42
43const char* FirewallController::LOCAL_DOZABLE = "fw_dozable";
44const char* FirewallController::LOCAL_STANDBY = "fw_standby";
45const 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).
50const char* FirewallController::ICMPV6_TYPES[] = {
51    "packet-too-big",
52    "router-solicitation",
53    "router-advertisement",
54    "neighbour-solicitation",
55    "neighbour-advertisement",
56    "redirect",
57};
58
59FirewallController::FirewallController(void) {
60    // If no rules are set, it's in BLACKLIST mode
61    mFirewallType = BLACKLIST;
62}
63
64int 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
79int 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
98int 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
111int 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
138int FirewallController::isFirewallEnabled(void) {
139    // TODO: verify that rules are still in place near top
140    return -1;
141}
142
143int 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
167int 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
191int 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
224FirewallType 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
239int 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
283int FirewallController::attachChain(const char* childChain, const char* parentChain) {
284    return execIptables(V4V6, "-t", TABLE, "-A", parentChain, "-j", childChain, NULL);
285}
286
287int FirewallController::detachChain(const char* childChain, const char* parentChain) {
288    return execIptables(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
289}
290
291int 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
298std::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    // Always allow networking on loopback.
304    StringAppendF(&commands, "-A %s -i lo -o lo -j RETURN\n", name);
305
306    // Allow TCP RSTs so we can cleanly close TCP connections of apps that no longer have network
307    // access. Both incoming and outgoing RSTs are allowed.
308    StringAppendF(&commands, "-A %s -p tcp --tcp-flags RST RST -j RETURN\n", name);
309
310    if (isWhitelist) {
311        // Allow ICMPv6 packets necessary to make IPv6 connectivity work. http://b/23158230 .
312        if (target == V6) {
313            for (size_t i = 0; i < ARRAY_SIZE(ICMPV6_TYPES); i++) {
314                StringAppendF(&commands, "-A %s -p icmpv6 --icmpv6-type %s -j RETURN\n",
315                       name, ICMPV6_TYPES[i]);
316            }
317        }
318
319        // Always whitelist system UIDs.
320        StringAppendF(&commands,
321                "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
322    }
323
324    // Whitelist or blacklist the specified UIDs.
325    const char *action = isWhitelist ? "RETURN" : "DROP";
326    for (auto uid : uids) {
327        StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j %s\n", name, uid, action);
328    }
329
330    // If it's a whitelist chain, add a default DROP at the end. This is not necessary for a
331    // blacklist chain, because all user-defined chains implicitly RETURN at the end.
332    if (isWhitelist) {
333        StringAppendF(&commands, "-A %s -j DROP\n", name);
334    }
335
336    StringAppendF(&commands, "COMMIT\n\x04");  // EOT.
337
338    return commands;
339}
340
341int FirewallController::replaceUidChain(
342        const char *name, bool isWhitelist, const std::vector<int32_t>& uids) {
343   std::string commands4 = makeUidRules(V4, name, isWhitelist, uids);
344   std::string commands6 = makeUidRules(V6, name, isWhitelist, uids);
345   return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
346}
347