blob: 70e1b84577d1ad46e7ed83fc2f1c02afc1dafc79 [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>
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090022
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +090023#include "patchpanel/net_util.h"
24
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090025namespace {
26
27// Interface names must be shorter than 'IFNAMSIZ' chars.
28// See http://man7.org/linux/man-pages/man7/netdevice.7.html
29// 'IFNAMSIZ' is 16 in recent kernels.
Tom Hughes249adef2020-08-24 18:21:52 -070030// See
31// 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 +090032constexpr size_t kInterfaceNameSize = 16;
33
Hugo Benichi283a7812021-06-08 00:47:54 +090034// The name of the filter table for iptables and ip6tables commands.
35constexpr char kFilterTable[] = "filter";
36// The name of the nat table for iptables and ip6tables commands.
37constexpr char kNatTable[] = "nat";
38
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090039// Interface names are passed directly to the 'iptables' command. Rather than
40// auditing 'iptables' source code to see how it handles malformed names,
41// do some sanitization on the names beforehand.
42bool IsValidInterfaceName(const std::string& iface) {
43 // |iface| should be shorter than |kInterfaceNameSize| chars and have only
44 // alphanumeric characters (embedded hypens and periods are also permitted).
45 if (iface.length() >= kInterfaceNameSize) {
46 return false;
47 }
48 if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) ||
49 base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) ||
50 base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) ||
51 base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) {
52 return false;
53 }
54 for (auto c : iface) {
55 if (!std::isalnum(c) && (c != '-') && (c != '.')) {
56 return false;
57 }
58 }
59 return true;
60}
61
62} // namespace
63
64namespace patchpanel {
65
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090066const std::string ProtocolName(Protocol proto) {
67 if (proto == ModifyPortRuleRequest::INVALID_PROTOCOL) {
68 NOTREACHED() << "Unexpected L4 protocol value";
69 }
70 return base::ToLowerASCII(ModifyPortRuleRequest::Protocol_Name(proto));
71}
72
Hugo Benichi283a7812021-06-08 00:47:54 +090073Firewall::Firewall() : Firewall(new MinijailedProcessRunner()) {}
74
75Firewall::Firewall(MinijailedProcessRunner* process_runner) {
76 process_runner_.reset(process_runner);
77}
78
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090079bool Firewall::AddAcceptRules(Protocol protocol,
80 uint16_t port,
81 const std::string& interface) {
82 if (port == 0U) {
83 LOG(ERROR) << "Port 0 is not a valid port";
84 return false;
85 }
86
87 if (!IsValidInterfaceName(interface)) {
88 LOG(ERROR) << "Invalid interface name '" << interface << "'";
89 return false;
90 }
91
Hugo Benichi283a7812021-06-08 00:47:54 +090092 if (!AddAcceptRule(IPv4, protocol, port, interface)) {
93 LOG(ERROR) << "Could not add IPv4 ACCEPT rule";
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090094 return false;
95 }
96
Hugo Benichi283a7812021-06-08 00:47:54 +090097 if (!AddAcceptRule(IPv6, protocol, port, interface)) {
98 LOG(ERROR) << "Could not add IPv6 ACCEPT rule";
99 DeleteAcceptRule(IPv4, protocol, port, interface);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900100 return false;
101 }
102
103 return true;
104}
105
106bool Firewall::DeleteAcceptRules(Protocol protocol,
107 uint16_t port,
108 const std::string& interface) {
109 if (port == 0U) {
110 LOG(ERROR) << "Port 0 is not a valid port";
111 return false;
112 }
113
114 if (!IsValidInterfaceName(interface)) {
115 LOG(ERROR) << "Invalid interface name '" << interface << "'";
116 return false;
117 }
118
Hugo Benichi283a7812021-06-08 00:47:54 +0900119 bool ip4_success = DeleteAcceptRule(IPv4, protocol, port, interface);
120 bool ip6_success = DeleteAcceptRule(IPv6, protocol, port, interface);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900121 return ip4_success && ip6_success;
122}
123
124bool Firewall::AddIpv4ForwardRule(Protocol protocol,
125 const std::string& input_ip,
126 uint16_t port,
127 const std::string& interface,
128 const std::string& dst_ip,
129 uint16_t dst_port) {
Tom Hughes249adef2020-08-24 18:21:52 -0700130 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
131 "-I")) {
132 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900133 }
134
135 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-A")) {
Tom Hughes249adef2020-08-24 18:21:52 -0700136 ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
137 "-D");
138 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900139 }
140
141 return true;
142}
143
144bool Firewall::DeleteIpv4ForwardRule(Protocol protocol,
145 const std::string& input_ip,
146 uint16_t port,
147 const std::string& interface,
148 const std::string& dst_ip,
149 uint16_t dst_port) {
150 bool success = true;
Tom Hughes249adef2020-08-24 18:21:52 -0700151 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
152 "-D")) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900153 success = false;
154 }
155 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-D")) {
156 success = false;
157 }
158 return success;
159}
160
161bool Firewall::ModifyIpv4DNATRule(Protocol protocol,
162 const std::string& input_ip,
163 uint16_t port,
164 const std::string& interface,
165 const std::string& dst_ip,
166 uint16_t dst_port,
167 const std::string& operation) {
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900168 if (!input_ip.empty() && GetIpFamily(input_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900169 LOG(ERROR) << "Invalid input IPv4 address '" << input_ip << "'";
170 return false;
171 }
172
173 if (port == 0U) {
174 LOG(ERROR) << "Port 0 is not a valid port";
175 return false;
176 }
177
178 if (!IsValidInterfaceName(interface) || interface.empty()) {
179 LOG(ERROR) << "Invalid interface name '" << interface << "'";
180 return false;
181 }
182
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900183 if (GetIpFamily(dst_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900184 LOG(ERROR) << "Invalid destination IPv4 address '" << dst_ip << "'";
185 return false;
186 }
187
188 if (dst_port == 0U) {
189 LOG(ERROR) << "Destination port 0 is not a valid port";
190 return false;
191 }
192
193 // Only support deleting existing forwarding rules or inserting rules in the
194 // first position: ARC++ generic inbound DNAT rule always need to go last.
195 if (operation != "-I" && operation != "-D") {
196 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
197 return false;
198 }
199
Tom Hughes249adef2020-08-24 18:21:52 -0700200 std::vector<std::string> argv{
Tom Hughes249adef2020-08-24 18:21:52 -0700201 operation,
Hugo Benichiac47f612021-08-30 12:28:35 +0900202 "ingress_port_forwarding",
Tom Hughes249adef2020-08-24 18:21:52 -0700203 "-i",
204 interface,
205 "-p", // protocol
206 ProtocolName(protocol),
207 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900208 if (!input_ip.empty()) {
209 argv.push_back("-d"); // input destination ip
210 argv.push_back(input_ip);
211 }
212 argv.push_back("--dport"); // input destination port
213 argv.push_back(std::to_string(port));
214 argv.push_back("-j");
215 argv.push_back("DNAT");
216 argv.push_back("--to-destination"); // new output destination ip:port
217 argv.push_back(dst_ip + ":" + std::to_string(dst_port));
218 argv.push_back("-w"); // Wait for xtables lock.
Hugo Benichi283a7812021-06-08 00:47:54 +0900219 return RunIptables(IPv4, kNatTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900220}
221
222bool Firewall::ModifyIpv4ForwardChain(Protocol protocol,
223 const std::string& interface,
224 const std::string& dst_ip,
225 uint16_t dst_port,
226 const std::string& operation) {
227 if (!IsValidInterfaceName(interface) || interface.empty()) {
228 LOG(ERROR) << "Invalid interface name '" << interface << "'";
229 return false;
230 }
231
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900232 if (GetIpFamily(dst_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900233 LOG(ERROR) << "Invalid IPv4 destination address '" << dst_ip << "'";
234 return false;
235 }
236
237 if (dst_port == 0U) {
238 LOG(ERROR) << "Destination port 0 is not a valid port";
239 return false;
240 }
241
242 // Order does not matter for the FORWARD chain: both -A or -I are possible.
243 if (operation != "-A" && operation != "-I" && operation != "-D") {
244 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
245 return false;
246 }
247
248 std::vector<std::string> argv{
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900249 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.
Hugo Benichi283a7812021-06-08 00:47:54 +0900263 return RunIptables(IPv4, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900264}
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
Hugo Benichi283a7812021-06-08 00:47:54 +0900272 if (!AddLoopbackLockdownRule(IPv4, protocol, port)) {
273 LOG(ERROR) << "Could not add loopback IPv4 REJECT rule";
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900274 return false;
275 }
276
Hugo Benichi283a7812021-06-08 00:47:54 +0900277 if (!AddLoopbackLockdownRule(IPv6, protocol, port)) {
278 LOG(ERROR) << "Could not add loopback IPv6 REJECT rule";
279 DeleteLoopbackLockdownRule(IPv4, protocol, port);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900280 return false;
281 }
282
283 return true;
284}
285
286bool Firewall::DeleteLoopbackLockdownRules(Protocol protocol, uint16_t port) {
287 if (port == 0U) {
288 LOG(ERROR) << "Port 0 is not a valid port";
289 return false;
290 }
291
Hugo Benichi283a7812021-06-08 00:47:54 +0900292 bool ip4_success = DeleteLoopbackLockdownRule(IPv4, protocol, port);
293 bool ip6_success = DeleteLoopbackLockdownRule(IPv6, protocol, port);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900294 return ip4_success && ip6_success;
295}
296
Hugo Benichi283a7812021-06-08 00:47:54 +0900297bool Firewall::AddAcceptRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900298 Protocol protocol,
299 uint16_t port,
300 const std::string& interface) {
Tom Hughes249adef2020-08-24 18:21:52 -0700301 std::vector<std::string> argv{
Tom Hughes249adef2020-08-24 18:21:52 -0700302 "-I", // insert
303 "INPUT",
304 "-p", // protocol
305 ProtocolName(protocol),
306 "--dport", // destination port
307 std::to_string(port),
308 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900309 if (!interface.empty()) {
310 argv.push_back("-i"); // interface
311 argv.push_back(interface);
312 }
313 argv.push_back("-j");
314 argv.push_back("ACCEPT");
315 argv.push_back("-w"); // Wait for xtables lock.
316
Hugo Benichi283a7812021-06-08 00:47:54 +0900317 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900318}
319
Hugo Benichi283a7812021-06-08 00:47:54 +0900320bool Firewall::DeleteAcceptRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900321 Protocol protocol,
322 uint16_t port,
323 const std::string& interface) {
Tom Hughes249adef2020-08-24 18:21:52 -0700324 std::vector<std::string> argv{
Tom Hughes249adef2020-08-24 18:21:52 -0700325 "-D", // delete
326 "INPUT",
327 "-p", // protocol
328 ProtocolName(protocol),
329 "--dport", // destination port
330 std::to_string(port),
331 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900332 if (!interface.empty()) {
333 argv.push_back("-i"); // interface
334 argv.push_back(interface);
335 }
336 argv.push_back("-j");
337 argv.push_back("ACCEPT");
338 argv.push_back("-w"); // Wait for xtables lock.
339
Hugo Benichi283a7812021-06-08 00:47:54 +0900340 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900341}
342
Hugo Benichi283a7812021-06-08 00:47:54 +0900343bool Firewall::AddLoopbackLockdownRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900344 Protocol protocol,
345 uint16_t port) {
346 std::vector<std::string> argv{
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900347 "-I", // insert
348 "OUTPUT",
349 "-p", // protocol
350 ProtocolName(protocol),
351 "--dport", // destination port
352 std::to_string(port),
353 "-o", // output interface
354 "lo",
355 "-m", // match extension
356 "owner",
357 "!",
358 "--uid-owner",
359 "chronos",
360 "-j",
361 "REJECT",
362 "-w", // Wait for xtables lock.
363 };
364
Hugo Benichi283a7812021-06-08 00:47:54 +0900365 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900366}
367
Hugo Benichi283a7812021-06-08 00:47:54 +0900368bool Firewall::DeleteLoopbackLockdownRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900369 Protocol protocol,
370 uint16_t port) {
371 std::vector<std::string> argv{
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900372 "-D", // delete
373 "OUTPUT",
374 "-p", // protocol
375 ProtocolName(protocol),
376 "--dport", // destination port
377 std::to_string(port),
378 "-o", // output interface
379 "lo",
380 "-m", // match extension
381 "owner",
382 "!",
383 "--uid-owner",
384 "chronos",
385 "-j",
386 "REJECT",
387 "-w", // Wait for xtables lock.
388 };
389
Hugo Benichi283a7812021-06-08 00:47:54 +0900390 // TODO: add IPv4 or IPv6
391 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900392}
393
Hugo Benichi283a7812021-06-08 00:47:54 +0900394bool Firewall::RunIptables(IpFamily ip_family,
395 const std::string& table,
396 const std::vector<std::string>& argv) {
397 if (ip_family == IPv4)
398 return process_runner_->iptables(table, argv, false) == 0;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900399
Hugo Benichi283a7812021-06-08 00:47:54 +0900400 if (ip_family == IPv6)
401 return process_runner_->ip6tables(table, argv, false) == 0;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900402
Hugo Benichi283a7812021-06-08 00:47:54 +0900403 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900404}
405
406} // namespace patchpanel