iptables.cc revision daf7ab162bfec3c79ec36e976bc9082179f022a0
1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "firewalld/iptables.h"
6
7#include <linux/capability.h>
8
9#include <string>
10#include <vector>
11
12#include <base/logging.h>
13#include <base/strings/string_number_conversions.h>
14#include <base/strings/string_util.h>
15#include <base/strings/stringprintf.h>
16#include <chromeos/minijail/minijail.h>
17#include <chromeos/process.h>
18
19namespace {
20const char kIpTablesPath[] = "/sbin/iptables";
21const char kIp6TablesPath[] = "/sbin/ip6tables";
22
23const char kIpPath[] = "/bin/ip";
24
25const char kUnprivilegedUser[] = "nobody";
26const uint64_t kIpTablesCapMask =
27    CAP_TO_MASK(CAP_NET_ADMIN) | CAP_TO_MASK(CAP_NET_RAW);
28
29// Interface names must be shorter than 'IFNAMSIZ' chars.
30// See http://man7.org/linux/man-pages/man7/netdevice.7.html
31// 'IFNAMSIZ' is 16 in recent kernels.
32// See http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L26
33const size_t kInterfaceNameSize = 16;
34
35const char kMarkForUserTraffic[] = "1";
36
37const char kTableIdForUserTraffic[] = "1";
38
39bool IsValidInterfaceName(const std::string& iface) {
40  // |iface| should be shorter than |kInterfaceNameSize| chars and have only
41  // alphanumeric characters (embedded hypens are also permitted).
42  if (iface.length() >= kInterfaceNameSize) {
43    return false;
44  }
45  if (base::StartsWithASCII(iface, "-", true /* case_sensitive */) ||
46      base::EndsWith(iface, "-", true /* case_sensitive */)) {
47    return false;
48  }
49  for (auto c : iface) {
50    if (!std::isalnum(c) && (c != '-')) {
51      return false;
52    }
53  }
54  return true;
55}
56}  // namespace
57
58namespace firewalld {
59
60IpTables::~IpTables() {
61  // Plug all holes when destructed.
62  PlugAllHoles();
63}
64
65bool IpTables::PunchTcpHole(uint16_t in_port, const std::string& in_interface) {
66  return PunchHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
67}
68
69bool IpTables::PunchUdpHole(uint16_t in_port, const std::string& in_interface) {
70  return PunchHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
71}
72
73bool IpTables::PlugTcpHole(uint16_t in_port, const std::string& in_interface) {
74  return PlugHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
75}
76
77bool IpTables::PlugUdpHole(uint16_t in_port, const std::string& in_interface) {
78  return PlugHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
79}
80
81bool IpTables::RequestVpnSetup(const std::vector<std::string>& usernames,
82                               const std::string& interface) {
83  return ApplyVpnSetup(usernames, interface, true /* add */);
84}
85
86bool IpTables::RemoveVpnSetup(const std::vector<std::string>& usernames,
87                              const std::string& interface) {
88  return ApplyVpnSetup(usernames, interface, false /* delete */);
89}
90
91bool IpTables::PunchHole(uint16_t port,
92                         const std::string& interface,
93                         std::set<Hole>* holes,
94                         ProtocolEnum protocol) {
95  if (port == 0) {
96    // Port 0 is not a valid TCP/UDP port.
97    return false;
98  }
99
100  if (!IsValidInterfaceName(interface)) {
101    LOG(ERROR) << "Invalid interface name '" << interface << "'";
102    return false;
103  }
104
105  Hole hole = std::make_pair(port, interface);
106  if (holes->find(hole) != holes->end()) {
107    // We have already punched a hole for |port| on |interface|.
108    // Be idempotent: do nothing and succeed.
109    return true;
110  }
111
112  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
113  LOG(INFO) << "Punching hole for " << sprotocol << " port " << port
114            << " on interface '" << interface << "'";
115  if (!AddAcceptRules(protocol, port, interface)) {
116    // If the 'iptables' command fails, this method fails.
117    LOG(ERROR) << "Adding ACCEPT rules failed";
118    return false;
119  }
120
121  // Track the hole we just punched.
122  holes->insert(hole);
123
124  return true;
125}
126
127bool IpTables::PlugHole(uint16_t port,
128                        const std::string& interface,
129                        std::set<Hole>* holes,
130                        ProtocolEnum protocol) {
131  if (port == 0) {
132    // Port 0 is not a valid TCP/UDP port.
133    return false;
134  }
135
136  Hole hole = std::make_pair(port, interface);
137
138  if (holes->find(hole) == holes->end()) {
139    // There is no firewall hole for |port| on |interface|.
140    // Even though this makes |PlugHole| not idempotent,
141    // and Punch/Plug not entirely symmetrical, fail. It might help catch bugs.
142    return false;
143  }
144
145  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
146  LOG(INFO) << "Plugging hole for " << sprotocol << " port " << port
147            << " on interface '" << interface << "'";
148  if (!DeleteAcceptRules(protocol, port, interface)) {
149    // If the 'iptables' command fails, this method fails.
150    LOG(ERROR) << "Deleting ACCEPT rules failed";
151    return false;
152  }
153
154  // Stop tracking the hole we just plugged.
155  holes->erase(hole);
156
157  return true;
158}
159
160void IpTables::PlugAllHoles() {
161  // Copy the container so that we can remove elements from the original.
162  // TCP
163  std::set<Hole> holes = tcp_holes_;
164  for (auto hole : holes) {
165    PlugHole(hole.first /* port */, hole.second /* interface */, &tcp_holes_,
166             kProtocolTcp);
167  }
168
169  // UDP
170  holes = udp_holes_;
171  for (auto hole : holes) {
172    PlugHole(hole.first /* port */, hole.second /* interface */, &udp_holes_,
173             kProtocolUdp);
174  }
175
176  CHECK(tcp_holes_.size() == 0) << "Failed to plug all TCP holes.";
177  CHECK(udp_holes_.size() == 0) << "Failed to plug all UDP holes.";
178}
179
180bool IpTables::AddAcceptRules(ProtocolEnum protocol,
181                              uint16_t port,
182                              const std::string& interface) {
183  if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) {
184    LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'";
185    return false;
186  }
187  if (!AddAcceptRule(kIp6TablesPath, protocol, port, interface)) {
188    LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath << "'";
189    DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
190    return false;
191  }
192  return true;
193}
194
195bool IpTables::DeleteAcceptRules(ProtocolEnum protocol,
196                                 uint16_t port,
197                                 const std::string& interface) {
198  bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port,
199                                      interface);
200  bool ip6_success = DeleteAcceptRule(kIp6TablesPath, protocol, port,
201                                      interface);
202  return ip4_success && ip6_success;
203}
204
205bool IpTables::AddAcceptRule(const std::string& executable_path,
206                             ProtocolEnum protocol,
207                             uint16_t port,
208                             const std::string& interface) {
209  std::vector<std::string> argv;
210  argv.push_back(executable_path);
211  argv.push_back("-I");  // insert
212  argv.push_back("INPUT");
213  argv.push_back("-p");  // protocol
214  argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp");
215  argv.push_back("--dport");  // destination port
216  argv.push_back(std::to_string(port));
217  if (!interface.empty()) {
218    argv.push_back("-i");  // interface
219    argv.push_back(interface);
220  }
221  argv.push_back("-j");
222  argv.push_back("ACCEPT");
223
224  // Use CAP_NET_ADMIN|CAP_NET_RAW.
225  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
226}
227
228bool IpTables::DeleteAcceptRule(const std::string& executable_path,
229                                ProtocolEnum protocol,
230                                uint16_t port,
231                                const std::string& interface) {
232  std::vector<std::string> argv;
233  argv.push_back(executable_path);
234  argv.push_back("-D");  // delete
235  argv.push_back("INPUT");
236  argv.push_back("-p");  // protocol
237  argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp");
238  argv.push_back("--dport");  // destination port
239  argv.push_back(std::to_string(port));
240  if (interface != "") {
241    argv.push_back("-i");  // interface
242    argv.push_back(interface);
243  }
244  argv.push_back("-j");
245  argv.push_back("ACCEPT");
246
247  // Use CAP_NET_ADMIN|CAP_NET_RAW.
248  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
249}
250
251bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames,
252                             const std::string& interface,
253                             bool add) {
254  bool return_value = true;
255
256  if (!ApplyRuleForUserTraffic(add)) {
257    LOG(ERROR) << (add ? "Adding" : "Removing")
258               << " rule for user traffic failed";
259    if (add)
260      return false;
261    return_value = false;
262  }
263
264  if (!ApplyMasquerade(interface, add)) {
265    LOG(ERROR) << (add ? "Adding" : "Removing")
266               << " masquerade failed for interface "
267               << interface;
268    if (add) {
269      ApplyRuleForUserTraffic(false);
270      return false;
271    }
272    return_value = false;
273  }
274
275  std::vector<std::string> added_usernames;
276  for (const auto& username : usernames) {
277    if (!ApplyMarkForUserTraffic(username, add)) {
278      LOG(ERROR) << (add ? "Adding" : "Removing")
279                 << " mark failed for user "
280                 << username;
281      if (add) {
282        ApplyVpnSetup(added_usernames, interface, false);
283        return false;
284      }
285      return_value = false;
286    }
287    if (add) {
288      added_usernames.push_back(username);
289    }
290  }
291
292  return return_value;
293}
294
295bool IpTables::ApplyMasquerade(const std::string& interface, bool add) {
296  std::vector<std::string> argv;
297  argv.push_back(kIpTablesPath);
298  argv.push_back("-t");  // table
299  argv.push_back("nat");
300  argv.push_back(add ? "-A" : "-D");  // rule
301  argv.push_back("POSTROUTING");
302  argv.push_back("-o");  // output interface
303  argv.push_back(interface);
304  argv.push_back("-j");
305  argv.push_back("MASQUERADE");
306
307  // Use CAP_NET_ADMIN|CAP_NET_RAW.
308  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
309}
310
311bool IpTables::ApplyMarkForUserTraffic(const std::string& user_name,
312                                       bool add) {
313  std::vector<std::string> argv;
314  argv.push_back(kIpTablesPath);
315  argv.push_back("-t");  // table
316  argv.push_back("mangle");
317  argv.push_back(add ? "-A" : "-D");  // rule
318  argv.push_back("OUTPUT");
319  argv.push_back("-m");
320  argv.push_back("owner");
321  argv.push_back("--uid-owner");
322  argv.push_back(user_name);
323  argv.push_back("-j");
324  argv.push_back("MARK");
325  argv.push_back("--set-mark");
326  argv.push_back(kMarkForUserTraffic);
327
328  // Use CAP_NET_ADMIN|CAP_NET_RAW.
329  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
330}
331
332bool IpTables::ApplyRuleForUserTraffic(bool add) {
333  chromeos::ProcessImpl ip;
334  ip.AddArg(kIpPath);
335  ip.AddArg("rule");
336  ip.AddArg(add ? "add" : "delete");
337  ip.AddArg("fwmark");
338  ip.AddArg(kMarkForUserTraffic);
339  ip.AddArg("table");
340  ip.AddArg(kTableIdForUserTraffic);
341
342  return ip.Run() == 0;
343}
344
345int IpTables::ExecvNonRoot(const std::vector<std::string>& argv,
346                           uint64_t capmask) {
347  chromeos::Minijail* m = chromeos::Minijail::GetInstance();
348  minijail* jail = m->New();
349  m->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser);
350  m->UseCapabilities(jail, capmask);
351
352  std::vector<char*> args;
353  for (const auto& arg : argv) {
354    args.push_back(const_cast<char*>(arg.c_str()));
355  }
356  args.push_back(nullptr);
357
358  int status;
359  bool ran = m->RunSyncAndDestroy(jail, args, &status);
360  return ran ? status : -1;
361}
362
363}  // namespace firewalld
364