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    res |= createChain(LOCAL_DOZABLE, getFirewallType(DOZABLE));
67    res |= createChain(LOCAL_STANDBY, getFirewallType(STANDBY));
68    res |= createChain(LOCAL_POWERSAVE, getFirewallType(POWERSAVE));
69    return res;
70}
71
72int FirewallController::enableFirewall(FirewallType ftype) {
73    int res = 0;
74    if (mFirewallType != ftype) {
75        // flush any existing rules
76        disableFirewall();
77
78        if (ftype == WHITELIST) {
79            // create default rule to drop all traffic
80            res |= execIptables(V4V6, "-A", LOCAL_INPUT, "-j", "DROP", NULL);
81            res |= execIptables(V4V6, "-A", LOCAL_OUTPUT, "-j", "REJECT", NULL);
82            res |= execIptables(V4V6, "-A", LOCAL_FORWARD, "-j", "REJECT", NULL);
83        }
84
85        // Set this after calling disableFirewall(), since it defaults to WHITELIST there
86        mFirewallType = ftype;
87    }
88    return res;
89}
90
91int FirewallController::disableFirewall(void) {
92    int res = 0;
93
94    mFirewallType = WHITELIST;
95
96    // flush any existing rules
97    res |= execIptables(V4V6, "-F", LOCAL_INPUT, NULL);
98    res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
99    res |= execIptables(V4V6, "-F", LOCAL_FORWARD, NULL);
100
101    return res;
102}
103
104int FirewallController::enableChildChains(ChildChain chain, bool enable) {
105    int res = 0;
106    const char* name;
107    switch(chain) {
108        case DOZABLE:
109            name = LOCAL_DOZABLE;
110            break;
111        case STANDBY:
112            name = LOCAL_STANDBY;
113            break;
114        case POWERSAVE:
115            name = LOCAL_POWERSAVE;
116            break;
117        default:
118            return res;
119    }
120
121    std::string command = "*filter\n";
122    for (const char *parent : { LOCAL_INPUT, LOCAL_OUTPUT }) {
123        StringAppendF(&command, "%s %s -j %s\n", (enable ? "-A" : "-D"), parent, name);
124    }
125    StringAppendF(&command, "COMMIT\n");
126
127    return execIptablesRestore(V4V6, command);
128}
129
130int FirewallController::isFirewallEnabled(void) {
131    // TODO: verify that rules are still in place near top
132    return -1;
133}
134
135int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
136    if (mFirewallType == BLACKLIST) {
137        // Unsupported in BLACKLIST mode
138        return -1;
139    }
140
141    if (!isIfaceName(iface)) {
142        errno = ENOENT;
143        return -1;
144    }
145
146    const char* op;
147    if (rule == ALLOW) {
148        op = "-I";
149    } else {
150        op = "-D";
151    }
152
153    int res = 0;
154    res |= execIptables(V4V6, op, LOCAL_INPUT, "-i", iface, "-j", "RETURN", NULL);
155    res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-o", iface, "-j", "RETURN", NULL);
156    return res;
157}
158
159int FirewallController::setEgressSourceRule(const char* addr, FirewallRule rule) {
160    if (mFirewallType == BLACKLIST) {
161        // Unsupported in BLACKLIST mode
162        return -1;
163    }
164
165    IptablesTarget target = V4;
166    if (strchr(addr, ':')) {
167        target = V6;
168    }
169
170    const char* op;
171    if (rule == ALLOW) {
172        op = "-I";
173    } else {
174        op = "-D";
175    }
176
177    int res = 0;
178    res |= execIptables(target, op, LOCAL_INPUT, "-d", addr, "-j", "RETURN", NULL);
179    res |= execIptables(target, op, LOCAL_OUTPUT, "-s", addr, "-j", "RETURN", NULL);
180    return res;
181}
182
183int FirewallController::setEgressDestRule(const char* addr, int protocol, int port,
184        FirewallRule rule) {
185    if (mFirewallType == BLACKLIST) {
186        // Unsupported in BLACKLIST mode
187        return -1;
188    }
189
190    IptablesTarget target = V4;
191    if (strchr(addr, ':')) {
192        target = V6;
193    }
194
195    char protocolStr[16];
196    sprintf(protocolStr, "%d", protocol);
197
198    char portStr[16];
199    sprintf(portStr, "%d", port);
200
201    const char* op;
202    if (rule == ALLOW) {
203        op = "-I";
204    } else {
205        op = "-D";
206    }
207
208    int res = 0;
209    res |= execIptables(target, op, LOCAL_INPUT, "-s", addr, "-p", protocolStr,
210            "--sport", portStr, "-j", "RETURN", NULL);
211    res |= execIptables(target, op, LOCAL_OUTPUT, "-d", addr, "-p", protocolStr,
212            "--dport", portStr, "-j", "RETURN", NULL);
213    return res;
214}
215
216FirewallType FirewallController::getFirewallType(ChildChain chain) {
217    switch(chain) {
218        case DOZABLE:
219            return WHITELIST;
220        case STANDBY:
221            return BLACKLIST;
222        case POWERSAVE:
223            return WHITELIST;
224        case NONE:
225            return mFirewallType;
226        default:
227            return BLACKLIST;
228    }
229}
230
231int FirewallController::setUidRule(ChildChain chain, int uid, FirewallRule rule) {
232    const char* op;
233    const char* target;
234    FirewallType firewallType = getFirewallType(chain);
235    if (firewallType == WHITELIST) {
236        target = "RETURN";
237        // When adding, insert RETURN rules at the front, before the catch-all DROP at the end.
238        op = (rule == ALLOW)? "-I" : "-D";
239    } else { // BLACKLIST mode
240        target = "DROP";
241        // When adding, append DROP rules at the end, after the RETURN rule that matches TCP RSTs.
242        op = (rule == DENY)? "-A" : "-D";
243    }
244
245    std::vector<std::string> chainNames;
246    switch(chain) {
247        case DOZABLE:
248            chainNames = { LOCAL_DOZABLE };
249            break;
250        case STANDBY:
251            chainNames = { LOCAL_STANDBY };
252            break;
253        case POWERSAVE:
254            chainNames = { LOCAL_POWERSAVE };
255            break;
256        case NONE:
257            chainNames = { LOCAL_INPUT, LOCAL_OUTPUT };
258            break;
259        default:
260            ALOGW("Unknown child chain: %d", chain);
261            return -1;
262    }
263
264    std::string command = "*filter\n";
265    for (std::string chainName : chainNames) {
266        StringAppendF(&command, "%s %s -m owner --uid-owner %d -j %s\n",
267                      op, chainName.c_str(), uid, target);
268    }
269    StringAppendF(&command, "COMMIT\n");
270
271    return execIptablesRestore(V4V6, command);
272}
273
274int FirewallController::createChain(const char* chain, FirewallType type) {
275    static const std::vector<int32_t> NO_UIDS;
276    return replaceUidChain(chain, type == WHITELIST, NO_UIDS);
277}
278
279std::string FirewallController::makeUidRules(IptablesTarget target, const char *name,
280        bool isWhitelist, const std::vector<int32_t>& uids) {
281    std::string commands;
282    StringAppendF(&commands, "*filter\n:%s -\n", name);
283
284    // Whitelist chains have UIDs at the beginning, and new UIDs are added with '-I'.
285    if (isWhitelist) {
286        for (auto uid : uids) {
287            StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j RETURN\n", name, uid);
288        }
289
290        // Always whitelist system UIDs.
291        StringAppendF(&commands,
292                "-A %s -m owner --uid-owner %d-%d -j RETURN\n", name, 0, MAX_SYSTEM_UID);
293    }
294
295    // Always allow networking on loopback.
296    StringAppendF(&commands, "-A %s -i lo -j RETURN\n", name);
297    StringAppendF(&commands, "-A %s -o lo -j RETURN\n", name);
298
299    // Allow TCP RSTs so we can cleanly close TCP connections of apps that no longer have network
300    // access. Both incoming and outgoing RSTs are allowed.
301    StringAppendF(&commands, "-A %s -p tcp --tcp-flags RST RST -j RETURN\n", name);
302
303    if (isWhitelist) {
304        // Allow ICMPv6 packets necessary to make IPv6 connectivity work. http://b/23158230 .
305        if (target == V6) {
306            for (size_t i = 0; i < ARRAY_SIZE(ICMPV6_TYPES); i++) {
307                StringAppendF(&commands, "-A %s -p icmpv6 --icmpv6-type %s -j RETURN\n",
308                       name, ICMPV6_TYPES[i]);
309            }
310        }
311    }
312
313    // Blacklist chains have UIDs at the end, and new UIDs are added with '-A'.
314    if (!isWhitelist) {
315        for (auto uid : uids) {
316            StringAppendF(&commands, "-A %s -m owner --uid-owner %d -j DROP\n", name, uid);
317        }
318    }
319
320    // If it's a whitelist chain, add a default DROP at the end. This is not necessary for a
321    // blacklist chain, because all user-defined chains implicitly RETURN at the end.
322    if (isWhitelist) {
323        StringAppendF(&commands, "-A %s -j DROP\n", name);
324    }
325
326    StringAppendF(&commands, "COMMIT\n");
327
328    return commands;
329}
330
331int FirewallController::replaceUidChain(
332        const char *name, bool isWhitelist, const std::vector<int32_t>& uids) {
333   std::string commands4 = makeUidRules(V4, name, isWhitelist, uids);
334   std::string commands6 = makeUidRules(V6, name, isWhitelist, uids);
335   return execIptablesRestore(V4, commands4.c_str()) | execIptablesRestore(V6, commands6.c_str());
336}
337