blob: f5f2ae216bf09794fea784c44854f6835ce12e76 [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>
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090015#include <base/callback.h>
hscham4ce3c992021-02-19 16:37:23 +090016#include <base/callback_helpers.h>
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090017#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
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +090024#include "patchpanel/net_util.h"
25
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090026namespace {
27
28// Interface names must be shorter than 'IFNAMSIZ' chars.
29// See http://man7.org/linux/man-pages/man7/netdevice.7.html
30// 'IFNAMSIZ' is 16 in recent kernels.
Tom Hughes249adef2020-08-24 18:21:52 -070031// See
32// 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 +090033constexpr size_t kInterfaceNameSize = 16;
34
35// Interface names are passed directly to the 'iptables' command. Rather than
36// auditing 'iptables' source code to see how it handles malformed names,
37// do some sanitization on the names beforehand.
38bool IsValidInterfaceName(const std::string& iface) {
39 // |iface| should be shorter than |kInterfaceNameSize| chars and have only
40 // alphanumeric characters (embedded hypens and periods are also permitted).
41 if (iface.length() >= kInterfaceNameSize) {
42 return false;
43 }
44 if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) ||
45 base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) ||
46 base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) ||
47 base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) {
48 return false;
49 }
50 for (auto c : iface) {
51 if (!std::isalnum(c) && (c != '-') && (c != '.')) {
52 return false;
53 }
54 }
55 return true;
56}
57
58} // namespace
59
60namespace patchpanel {
61
62const char kIpTablesPath[] = "/sbin/iptables";
63const char kIp6TablesPath[] = "/sbin/ip6tables";
64
65const std::string ProtocolName(Protocol proto) {
66 if (proto == ModifyPortRuleRequest::INVALID_PROTOCOL) {
67 NOTREACHED() << "Unexpected L4 protocol value";
68 }
69 return base::ToLowerASCII(ModifyPortRuleRequest::Protocol_Name(proto));
70}
71
72bool Firewall::AddAcceptRules(Protocol protocol,
73 uint16_t port,
74 const std::string& interface) {
75 if (port == 0U) {
76 LOG(ERROR) << "Port 0 is not a valid port";
77 return false;
78 }
79
80 if (!IsValidInterfaceName(interface)) {
81 LOG(ERROR) << "Invalid interface name '" << interface << "'";
82 return false;
83 }
84
85 if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) {
86 LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'";
87 return false;
88 }
89
90 if (!AddAcceptRule(kIp6TablesPath, protocol, port, interface)) {
91 LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath
92 << "', aborting operation";
93 DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
94 return false;
95 }
96
97 return true;
98}
99
100bool Firewall::DeleteAcceptRules(Protocol protocol,
101 uint16_t port,
102 const std::string& interface) {
103 if (port == 0U) {
104 LOG(ERROR) << "Port 0 is not a valid port";
105 return false;
106 }
107
108 if (!IsValidInterfaceName(interface)) {
109 LOG(ERROR) << "Invalid interface name '" << interface << "'";
110 return false;
111 }
112
Tom Hughes249adef2020-08-24 18:21:52 -0700113 bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
114 bool ip6_success =
115 DeleteAcceptRule(kIp6TablesPath, protocol, port, interface);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900116 return ip4_success && ip6_success;
117}
118
119bool Firewall::AddIpv4ForwardRule(Protocol protocol,
120 const std::string& input_ip,
121 uint16_t port,
122 const std::string& interface,
123 const std::string& dst_ip,
124 uint16_t dst_port) {
Tom Hughes249adef2020-08-24 18:21:52 -0700125 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
126 "-I")) {
127 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900128 }
129
130 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-A")) {
Tom Hughes249adef2020-08-24 18:21:52 -0700131 ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
132 "-D");
133 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900134 }
135
136 return true;
137}
138
139bool Firewall::DeleteIpv4ForwardRule(Protocol protocol,
140 const std::string& input_ip,
141 uint16_t port,
142 const std::string& interface,
143 const std::string& dst_ip,
144 uint16_t dst_port) {
145 bool success = true;
Tom Hughes249adef2020-08-24 18:21:52 -0700146 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
147 "-D")) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900148 success = false;
149 }
150 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-D")) {
151 success = false;
152 }
153 return success;
154}
155
156bool Firewall::ModifyIpv4DNATRule(Protocol protocol,
157 const std::string& input_ip,
158 uint16_t port,
159 const std::string& interface,
160 const std::string& dst_ip,
161 uint16_t dst_port,
162 const std::string& operation) {
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900163 if (!input_ip.empty() && GetIpFamily(input_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900164 LOG(ERROR) << "Invalid input IPv4 address '" << input_ip << "'";
165 return false;
166 }
167
168 if (port == 0U) {
169 LOG(ERROR) << "Port 0 is not a valid port";
170 return false;
171 }
172
173 if (!IsValidInterfaceName(interface) || interface.empty()) {
174 LOG(ERROR) << "Invalid interface name '" << interface << "'";
175 return false;
176 }
177
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900178 if (GetIpFamily(dst_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900179 LOG(ERROR) << "Invalid destination IPv4 address '" << dst_ip << "'";
180 return false;
181 }
182
183 if (dst_port == 0U) {
184 LOG(ERROR) << "Destination port 0 is not a valid port";
185 return false;
186 }
187
188 // Only support deleting existing forwarding rules or inserting rules in the
189 // first position: ARC++ generic inbound DNAT rule always need to go last.
190 if (operation != "-I" && operation != "-D") {
191 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
192 return false;
193 }
194
Tom Hughes249adef2020-08-24 18:21:52 -0700195 std::vector<std::string> argv{
196 kIpTablesPath,
197 "-t",
198 "nat",
199 operation,
200 "PREROUTING",
201 "-i",
202 interface,
203 "-p", // protocol
204 ProtocolName(protocol),
205 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900206 if (!input_ip.empty()) {
207 argv.push_back("-d"); // input destination ip
208 argv.push_back(input_ip);
209 }
210 argv.push_back("--dport"); // input destination port
211 argv.push_back(std::to_string(port));
212 argv.push_back("-j");
213 argv.push_back("DNAT");
214 argv.push_back("--to-destination"); // new output destination ip:port
215 argv.push_back(dst_ip + ":" + std::to_string(dst_port));
216 argv.push_back("-w"); // Wait for xtables lock.
217 return RunInMinijail(argv) == 0;
218}
219
220bool Firewall::ModifyIpv4ForwardChain(Protocol protocol,
221 const std::string& interface,
222 const std::string& dst_ip,
223 uint16_t dst_port,
224 const std::string& operation) {
225 if (!IsValidInterfaceName(interface) || interface.empty()) {
226 LOG(ERROR) << "Invalid interface name '" << interface << "'";
227 return false;
228 }
229
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900230 if (GetIpFamily(dst_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900231 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