blob: a097458f90afe7ed0b16cd0e9953e7a222625f22 [file] [log] [blame]
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +09001// Copyright 2017 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 "patchpanel/firewall.h"
6
7#include <arpa/inet.h>
8#include <linux/capability.h>
9#include <netinet/in.h>
10
11#include <string>
12#include <vector>
13
14#include <base/bind.h>
15#include <base/bind_helpers.h>
16#include <base/callback.h>
17#include <base/logging.h>
Qijiang Fan886c4692021-02-19 11:54:10 +090018#include <base/notreached.h>
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090019#include <base/strings/string_number_conversions.h>
20#include <base/strings/string_util.h>
21#include <base/strings/stringprintf.h>
22#include <brillo/minijail/minijail.h>
23
24namespace {
25
26// Interface names must be shorter than 'IFNAMSIZ' chars.
27// See http://man7.org/linux/man-pages/man7/netdevice.7.html
28// 'IFNAMSIZ' is 16 in recent kernels.
Tom Hughes249adef2020-08-24 18:21:52 -070029// See
30// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/if.h?h=v4.14#n33
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090031constexpr size_t kInterfaceNameSize = 16;
32
33// Interface names are passed directly to the 'iptables' command. Rather than
34// auditing 'iptables' source code to see how it handles malformed names,
35// do some sanitization on the names beforehand.
36bool IsValidInterfaceName(const std::string& iface) {
37 // |iface| should be shorter than |kInterfaceNameSize| chars and have only
38 // alphanumeric characters (embedded hypens and periods are also permitted).
39 if (iface.length() >= kInterfaceNameSize) {
40 return false;
41 }
42 if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) ||
43 base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) ||
44 base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) ||
45 base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) {
46 return false;
47 }
48 for (auto c : iface) {
49 if (!std::isalnum(c) && (c != '-') && (c != '.')) {
50 return false;
51 }
52 }
53 return true;
54}
55
56} // namespace
57
58namespace patchpanel {
59
60const char kIpTablesPath[] = "/sbin/iptables";
61const char kIp6TablesPath[] = "/sbin/ip6tables";
62
63const std::string ProtocolName(Protocol proto) {
64 if (proto == ModifyPortRuleRequest::INVALID_PROTOCOL) {
65 NOTREACHED() << "Unexpected L4 protocol value";
66 }
67 return base::ToLowerASCII(ModifyPortRuleRequest::Protocol_Name(proto));
68}
69
70bool Firewall::AddAcceptRules(Protocol protocol,
71 uint16_t port,
72 const std::string& interface) {
73 if (port == 0U) {
74 LOG(ERROR) << "Port 0 is not a valid port";
75 return false;
76 }
77
78 if (!IsValidInterfaceName(interface)) {
79 LOG(ERROR) << "Invalid interface name '" << interface << "'";
80 return false;
81 }
82
83 if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) {
84 LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'";
85 return false;
86 }
87
88 if (!AddAcceptRule(kIp6TablesPath, protocol, port, interface)) {
89 LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath
90 << "', aborting operation";
91 DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
92 return false;
93 }
94
95 return true;
96}
97
98bool Firewall::DeleteAcceptRules(Protocol protocol,
99 uint16_t port,
100 const std::string& interface) {
101 if (port == 0U) {
102 LOG(ERROR) << "Port 0 is not a valid port";
103 return false;
104 }
105
106 if (!IsValidInterfaceName(interface)) {
107 LOG(ERROR) << "Invalid interface name '" << interface << "'";
108 return false;
109 }
110
Tom Hughes249adef2020-08-24 18:21:52 -0700111 bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
112 bool ip6_success =
113 DeleteAcceptRule(kIp6TablesPath, protocol, port, interface);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900114 return ip4_success && ip6_success;
115}
116
117bool Firewall::AddIpv4ForwardRule(Protocol protocol,
118 const std::string& input_ip,
119 uint16_t port,
120 const std::string& interface,
121 const std::string& dst_ip,
122 uint16_t dst_port) {
Tom Hughes249adef2020-08-24 18:21:52 -0700123 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
124 "-I")) {
125 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900126 }
127
128 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-A")) {
Tom Hughes249adef2020-08-24 18:21:52 -0700129 ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
130 "-D");
131 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900132 }
133
134 return true;
135}
136
137bool Firewall::DeleteIpv4ForwardRule(Protocol protocol,
138 const std::string& input_ip,
139 uint16_t port,
140 const std::string& interface,
141 const std::string& dst_ip,
142 uint16_t dst_port) {
143 bool success = true;
Tom Hughes249adef2020-08-24 18:21:52 -0700144 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
145 "-D")) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900146 success = false;
147 }
148 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-D")) {
149 success = false;
150 }
151 return success;
152}
153
154bool Firewall::ModifyIpv4DNATRule(Protocol protocol,
155 const std::string& input_ip,
156 uint16_t port,
157 const std::string& interface,
158 const std::string& dst_ip,
159 uint16_t dst_port,
160 const std::string& operation) {
161 struct in_addr addr;
162 if (!input_ip.empty() && inet_pton(AF_INET, input_ip.c_str(), &addr) != 1) {
163 LOG(ERROR) << "Invalid input IPv4 address '" << input_ip << "'";
164 return false;
165 }
166
167 if (port == 0U) {
168 LOG(ERROR) << "Port 0 is not a valid port";
169 return false;
170 }
171
172 if (!IsValidInterfaceName(interface) || interface.empty()) {
173 LOG(ERROR) << "Invalid interface name '" << interface << "'";
174 return false;
175 }
176
177 if (inet_pton(AF_INET, dst_ip.c_str(), &addr) != 1) {
178 LOG(ERROR) << "Invalid destination IPv4 address '" << dst_ip << "'";
179 return false;
180 }
181
182 if (dst_port == 0U) {
183 LOG(ERROR) << "Destination port 0 is not a valid port";
184 return false;
185 }
186
187 // Only support deleting existing forwarding rules or inserting rules in the
188 // first position: ARC++ generic inbound DNAT rule always need to go last.
189 if (operation != "-I" && operation != "-D") {
190 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
191 return false;
192 }
193
Tom Hughes249adef2020-08-24 18:21:52 -0700194 std::vector<std::string> argv{
195 kIpTablesPath,
196 "-t",
197 "nat",
198 operation,
199 "PREROUTING",
200 "-i",
201 interface,
202 "-p", // protocol
203 ProtocolName(protocol),
204 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900205 if (!input_ip.empty()) {
206 argv.push_back("-d"); // input destination ip
207 argv.push_back(input_ip);
208 }
209 argv.push_back("--dport"); // input destination port
210 argv.push_back(std::to_string(port));
211 argv.push_back("-j");
212 argv.push_back("DNAT");
213 argv.push_back("--to-destination"); // new output destination ip:port
214 argv.push_back(dst_ip + ":" + std::to_string(dst_port));
215 argv.push_back("-w"); // Wait for xtables lock.
216 return RunInMinijail(argv) == 0;
217}
218
219bool Firewall::ModifyIpv4ForwardChain(Protocol protocol,
220 const std::string& interface,
221 const std::string& dst_ip,
222 uint16_t dst_port,
223 const std::string& operation) {
224 if (!IsValidInterfaceName(interface) || interface.empty()) {
225 LOG(ERROR) << "Invalid interface name '" << interface << "'";
226 return false;
227 }
228
229 struct in_addr addr;
230 if (inet_pton(AF_INET, dst_ip.c_str(), &addr) != 1) {
231 LOG(ERROR) << "Invalid IPv4 destination address '" << dst_ip << "'";
232 return false;
233 }
234
235 if (dst_port == 0U) {
236 LOG(ERROR) << "Destination port 0 is not a valid port";
237 return false;
238 }
239
240 // Order does not matter for the FORWARD chain: both -A or -I are possible.
241 if (operation != "-A" && operation != "-I" && operation != "-D") {
242 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
243 return false;
244 }
245
246 std::vector<std::string> argv{
247 kIpTablesPath,
248 "-t",
249 "filter",
250 operation,
251 "FORWARD",
252 "-i",
253 interface,
254 "-p", // protocol
255 ProtocolName(protocol),
256 "-d", // destination ip
257 dst_ip,
258 "--dport", // destination port
259 std::to_string(dst_port),
260 "-j",
261 "ACCEPT",
Tom Hughes249adef2020-08-24 18:21:52 -0700262 "-w",
263 }; // Wait for xtables lock.
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900264 return RunInMinijail(argv) == 0;
265}
266
267bool Firewall::AddLoopbackLockdownRules(Protocol protocol, uint16_t port) {
268 if (port == 0U) {
269 LOG(ERROR) << "Port 0 is not a valid port";
270 return false;
271 }
272
273 if (!AddLoopbackLockdownRule(kIpTablesPath, protocol, port)) {
274 LOG(ERROR) << "Could not add loopback REJECT rule using '" << kIpTablesPath
275 << "'";
276 return false;
277 }
278
279 if (!AddLoopbackLockdownRule(kIp6TablesPath, protocol, port)) {
280 LOG(ERROR) << "Could not add loopback REJECT rule using '" << kIp6TablesPath
281 << "', aborting operation";
282 DeleteLoopbackLockdownRule(kIpTablesPath, protocol, port);
283 return false;
284 }
285
286 return true;
287}
288
289bool Firewall::DeleteLoopbackLockdownRules(Protocol protocol, uint16_t port) {
290 if (port == 0U) {
291 LOG(ERROR) << "Port 0 is not a valid port";
292 return false;
293 }
294
295 bool ip4_success = DeleteLoopbackLockdownRule(kIpTablesPath, protocol, port);
296 bool ip6_success = DeleteLoopbackLockdownRule(kIp6TablesPath, protocol, port);
297 return ip4_success && ip6_success;
298}
299
300bool Firewall::AddAcceptRule(const std::string& executable_path,
301 Protocol protocol,
302 uint16_t port,
303 const std::string& interface) {
Tom Hughes249adef2020-08-24 18:21:52 -0700304 std::vector<std::string> argv{
305 executable_path,
306 "-I", // insert
307 "INPUT",
308 "-p", // protocol
309 ProtocolName(protocol),
310 "--dport", // destination port
311 std::to_string(port),
312 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900313 if (!interface.empty()) {
314 argv.push_back("-i"); // interface
315 argv.push_back(interface);
316 }
317 argv.push_back("-j");
318 argv.push_back("ACCEPT");
319 argv.push_back("-w"); // Wait for xtables lock.
320
321 return RunInMinijail(argv) == 0;
322}
323
324bool Firewall::DeleteAcceptRule(const std::string& executable_path,
325 Protocol protocol,
326 uint16_t port,
327 const std::string& interface) {
Tom Hughes249adef2020-08-24 18:21:52 -0700328 std::vector<std::string> argv{
329 executable_path,
330 "-D", // delete
331 "INPUT",
332 "-p", // protocol
333 ProtocolName(protocol),
334 "--dport", // destination port
335 std::to_string(port),
336 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900337 if (!interface.empty()) {
338 argv.push_back("-i"); // interface
339 argv.push_back(interface);
340 }
341 argv.push_back("-j");
342 argv.push_back("ACCEPT");
343 argv.push_back("-w"); // Wait for xtables lock.
344
345 return RunInMinijail(argv) == 0;
346}
347
348bool Firewall::AddLoopbackLockdownRule(const std::string& executable_path,
349 Protocol protocol,
350 uint16_t port) {
351 std::vector<std::string> argv{
352 executable_path,
353 "-I", // insert
354 "OUTPUT",
355 "-p", // protocol
356 ProtocolName(protocol),
357 "--dport", // destination port
358 std::to_string(port),
359 "-o", // output interface
360 "lo",
361 "-m", // match extension
362 "owner",
363 "!",
364 "--uid-owner",
365 "chronos",
366 "-j",
367 "REJECT",
368 "-w", // Wait for xtables lock.
369 };
370
371 return RunInMinijail(argv) == 0;
372}
373
374bool Firewall::DeleteLoopbackLockdownRule(const std::string& executable_path,
375 Protocol protocol,
376 uint16_t port) {
377 std::vector<std::string> argv{
378 executable_path,
379 "-D", // delete
380 "OUTPUT",
381 "-p", // protocol
382 ProtocolName(protocol),
383 "--dport", // destination port
384 std::to_string(port),
385 "-o", // output interface
386 "lo",
387 "-m", // match extension
388 "owner",
389 "!",
390 "--uid-owner",
391 "chronos",
392 "-j",
393 "REJECT",
394 "-w", // Wait for xtables lock.
395 };
396
397 return RunInMinijail(argv) == 0;
398}
399
400int Firewall::RunInMinijail(const std::vector<std::string>& argv) {
401 brillo::Minijail* m = brillo::Minijail::GetInstance();
402 minijail* jail = m->New();
403
404 std::vector<char*> args;
405 for (const auto& arg : argv) {
406 args.push_back(const_cast<char*>(arg.c_str()));
407 }
408 args.push_back(nullptr);
409
410 int status;
411 return m->RunSyncAndDestroy(jail, args, &status) ? status : -1;
412}
413
414} // namespace patchpanel