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