blob: d075635f7d658d8dede25066a78427e354c77df6 [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
Hugo Benichiad5947b2021-09-03 16:56:40 +090023#include "patchpanel/datapath.h"
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
Hugo Benichi283a7812021-06-08 00:47:54 +090035// The name of the filter table for iptables and ip6tables commands.
36constexpr char kFilterTable[] = "filter";
37// The name of the nat table for iptables and ip6tables commands.
38constexpr char kNatTable[] = "nat";
39
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090040// Interface names are passed directly to the 'iptables' command. Rather than
41// auditing 'iptables' source code to see how it handles malformed names,
42// do some sanitization on the names beforehand.
43bool IsValidInterfaceName(const std::string& iface) {
44 // |iface| should be shorter than |kInterfaceNameSize| chars and have only
45 // alphanumeric characters (embedded hypens and periods are also permitted).
46 if (iface.length() >= kInterfaceNameSize) {
47 return false;
48 }
49 if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) ||
50 base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) ||
51 base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) ||
52 base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) {
53 return false;
54 }
55 for (auto c : iface) {
56 if (!std::isalnum(c) && (c != '-') && (c != '.')) {
57 return false;
58 }
59 }
60 return true;
61}
62
63} // namespace
64
65namespace patchpanel {
66
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090067const std::string ProtocolName(Protocol proto) {
68 if (proto == ModifyPortRuleRequest::INVALID_PROTOCOL) {
69 NOTREACHED() << "Unexpected L4 protocol value";
70 }
71 return base::ToLowerASCII(ModifyPortRuleRequest::Protocol_Name(proto));
72}
73
Hugo Benichi283a7812021-06-08 00:47:54 +090074Firewall::Firewall() : Firewall(new MinijailedProcessRunner()) {}
75
76Firewall::Firewall(MinijailedProcessRunner* process_runner) {
77 process_runner_.reset(process_runner);
78}
79
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090080bool Firewall::AddAcceptRules(Protocol protocol,
81 uint16_t port,
82 const std::string& interface) {
83 if (port == 0U) {
84 LOG(ERROR) << "Port 0 is not a valid port";
85 return false;
86 }
87
88 if (!IsValidInterfaceName(interface)) {
89 LOG(ERROR) << "Invalid interface name '" << interface << "'";
90 return false;
91 }
92
Hugo Benichi283a7812021-06-08 00:47:54 +090093 if (!AddAcceptRule(IPv4, protocol, port, interface)) {
94 LOG(ERROR) << "Could not add IPv4 ACCEPT rule";
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +090095 return false;
96 }
97
Hugo Benichi283a7812021-06-08 00:47:54 +090098 if (!AddAcceptRule(IPv6, protocol, port, interface)) {
99 LOG(ERROR) << "Could not add IPv6 ACCEPT rule";
100 DeleteAcceptRule(IPv4, protocol, port, interface);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900101 return false;
102 }
103
104 return true;
105}
106
107bool Firewall::DeleteAcceptRules(Protocol protocol,
108 uint16_t port,
109 const std::string& interface) {
110 if (port == 0U) {
111 LOG(ERROR) << "Port 0 is not a valid port";
112 return false;
113 }
114
115 if (!IsValidInterfaceName(interface)) {
116 LOG(ERROR) << "Invalid interface name '" << interface << "'";
117 return false;
118 }
119
Hugo Benichi283a7812021-06-08 00:47:54 +0900120 bool ip4_success = DeleteAcceptRule(IPv4, protocol, port, interface);
121 bool ip6_success = DeleteAcceptRule(IPv6, protocol, port, interface);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900122 return ip4_success && ip6_success;
123}
124
125bool Firewall::AddIpv4ForwardRule(Protocol protocol,
126 const std::string& input_ip,
127 uint16_t port,
128 const std::string& interface,
129 const std::string& dst_ip,
130 uint16_t dst_port) {
Tom Hughes249adef2020-08-24 18:21:52 -0700131 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
132 "-I")) {
133 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900134 }
135
136 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-A")) {
Tom Hughes249adef2020-08-24 18:21:52 -0700137 ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
138 "-D");
139 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900140 }
141
142 return true;
143}
144
145bool Firewall::DeleteIpv4ForwardRule(Protocol protocol,
146 const std::string& input_ip,
147 uint16_t port,
148 const std::string& interface,
149 const std::string& dst_ip,
150 uint16_t dst_port) {
151 bool success = true;
Tom Hughes249adef2020-08-24 18:21:52 -0700152 if (!ModifyIpv4DNATRule(protocol, input_ip, port, interface, dst_ip, dst_port,
153 "-D")) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900154 success = false;
155 }
156 if (!ModifyIpv4ForwardChain(protocol, interface, dst_ip, dst_port, "-D")) {
157 success = false;
158 }
159 return success;
160}
161
162bool Firewall::ModifyIpv4DNATRule(Protocol protocol,
163 const std::string& input_ip,
164 uint16_t port,
165 const std::string& interface,
166 const std::string& dst_ip,
167 uint16_t dst_port,
168 const std::string& operation) {
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900169 if (!input_ip.empty() && GetIpFamily(input_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900170 LOG(ERROR) << "Invalid input IPv4 address '" << input_ip << "'";
171 return false;
172 }
173
174 if (port == 0U) {
175 LOG(ERROR) << "Port 0 is not a valid port";
176 return false;
177 }
178
179 if (!IsValidInterfaceName(interface) || interface.empty()) {
180 LOG(ERROR) << "Invalid interface name '" << interface << "'";
181 return false;
182 }
183
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900184 if (GetIpFamily(dst_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900185 LOG(ERROR) << "Invalid destination IPv4 address '" << dst_ip << "'";
186 return false;
187 }
188
189 if (dst_port == 0U) {
190 LOG(ERROR) << "Destination port 0 is not a valid port";
191 return false;
192 }
193
194 // Only support deleting existing forwarding rules or inserting rules in the
195 // first position: ARC++ generic inbound DNAT rule always need to go last.
196 if (operation != "-I" && operation != "-D") {
197 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
198 return false;
199 }
200
Tom Hughes249adef2020-08-24 18:21:52 -0700201 std::vector<std::string> argv{
Tom Hughes249adef2020-08-24 18:21:52 -0700202 operation,
Hugo Benichiad5947b2021-09-03 16:56:40 +0900203 kIngressPortForwardingChain,
Tom Hughes249adef2020-08-24 18:21:52 -0700204 "-i",
205 interface,
206 "-p", // protocol
207 ProtocolName(protocol),
208 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900209 if (!input_ip.empty()) {
210 argv.push_back("-d"); // input destination ip
211 argv.push_back(input_ip);
212 }
213 argv.push_back("--dport"); // input destination port
214 argv.push_back(std::to_string(port));
215 argv.push_back("-j");
216 argv.push_back("DNAT");
217 argv.push_back("--to-destination"); // new output destination ip:port
218 argv.push_back(dst_ip + ":" + std::to_string(dst_port));
219 argv.push_back("-w"); // Wait for xtables lock.
Hugo Benichi283a7812021-06-08 00:47:54 +0900220 return RunIptables(IPv4, kNatTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900221}
222
223bool Firewall::ModifyIpv4ForwardChain(Protocol protocol,
224 const std::string& interface,
225 const std::string& dst_ip,
226 uint16_t dst_port,
227 const std::string& operation) {
228 if (!IsValidInterfaceName(interface) || interface.empty()) {
229 LOG(ERROR) << "Invalid interface name '" << interface << "'";
230 return false;
231 }
232
Jason Jeremy Imanf4fb64f2021-04-20 21:54:19 +0900233 if (GetIpFamily(dst_ip) != AF_INET) {
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900234 LOG(ERROR) << "Invalid IPv4 destination address '" << dst_ip << "'";
235 return false;
236 }
237
238 if (dst_port == 0U) {
239 LOG(ERROR) << "Destination port 0 is not a valid port";
240 return false;
241 }
242
243 // Order does not matter for the FORWARD chain: both -A or -I are possible.
244 if (operation != "-A" && operation != "-I" && operation != "-D") {
245 LOG(ERROR) << "Invalid chain operation '" << operation << "'";
246 return false;
247 }
248
249 std::vector<std::string> argv{
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900250 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.
Hugo Benichi283a7812021-06-08 00:47:54 +0900264 return RunIptables(IPv4, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900265}
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
Hugo Benichi283a7812021-06-08 00:47:54 +0900273 if (!AddLoopbackLockdownRule(IPv4, protocol, port)) {
274 LOG(ERROR) << "Could not add loopback IPv4 REJECT rule";
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900275 return false;
276 }
277
Hugo Benichi283a7812021-06-08 00:47:54 +0900278 if (!AddLoopbackLockdownRule(IPv6, protocol, port)) {
279 LOG(ERROR) << "Could not add loopback IPv6 REJECT rule";
280 DeleteLoopbackLockdownRule(IPv4, protocol, port);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900281 return false;
282 }
283
284 return true;
285}
286
287bool Firewall::DeleteLoopbackLockdownRules(Protocol protocol, uint16_t port) {
288 if (port == 0U) {
289 LOG(ERROR) << "Port 0 is not a valid port";
290 return false;
291 }
292
Hugo Benichi283a7812021-06-08 00:47:54 +0900293 bool ip4_success = DeleteLoopbackLockdownRule(IPv4, protocol, port);
294 bool ip6_success = DeleteLoopbackLockdownRule(IPv6, protocol, port);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900295 return ip4_success && ip6_success;
296}
297
Hugo Benichi283a7812021-06-08 00:47:54 +0900298bool Firewall::AddAcceptRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900299 Protocol protocol,
300 uint16_t port,
301 const std::string& interface) {
Tom Hughes249adef2020-08-24 18:21:52 -0700302 std::vector<std::string> argv{
Tom Hughes249adef2020-08-24 18:21:52 -0700303 "-I", // insert
Hugo Benichiad5947b2021-09-03 16:56:40 +0900304 kIngressPortFirewallChain,
Tom Hughes249adef2020-08-24 18:21:52 -0700305 "-p", // protocol
306 ProtocolName(protocol),
307 "--dport", // destination port
308 std::to_string(port),
309 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900310 if (!interface.empty()) {
311 argv.push_back("-i"); // interface
312 argv.push_back(interface);
313 }
314 argv.push_back("-j");
315 argv.push_back("ACCEPT");
316 argv.push_back("-w"); // Wait for xtables lock.
317
Hugo Benichi283a7812021-06-08 00:47:54 +0900318 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900319}
320
Hugo Benichi283a7812021-06-08 00:47:54 +0900321bool Firewall::DeleteAcceptRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900322 Protocol protocol,
323 uint16_t port,
324 const std::string& interface) {
Tom Hughes249adef2020-08-24 18:21:52 -0700325 std::vector<std::string> argv{
Tom Hughes249adef2020-08-24 18:21:52 -0700326 "-D", // delete
Hugo Benichiad5947b2021-09-03 16:56:40 +0900327 kIngressPortFirewallChain,
Tom Hughes249adef2020-08-24 18:21:52 -0700328 "-p", // protocol
329 ProtocolName(protocol),
330 "--dport", // destination port
331 std::to_string(port),
332 };
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900333 if (!interface.empty()) {
334 argv.push_back("-i"); // interface
335 argv.push_back(interface);
336 }
337 argv.push_back("-j");
338 argv.push_back("ACCEPT");
339 argv.push_back("-w"); // Wait for xtables lock.
340
Hugo Benichi283a7812021-06-08 00:47:54 +0900341 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900342}
343
Hugo Benichi283a7812021-06-08 00:47:54 +0900344bool Firewall::AddLoopbackLockdownRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900345 Protocol protocol,
346 uint16_t port) {
347 std::vector<std::string> argv{
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900348 "-I", // insert
Hugo Benichiad5947b2021-09-03 16:56:40 +0900349 kEgressPortFirewallChain,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900350 "-p", // protocol
351 ProtocolName(protocol),
352 "--dport", // destination port
353 std::to_string(port),
354 "-o", // output interface
355 "lo",
356 "-m", // match extension
357 "owner",
358 "!",
359 "--uid-owner",
360 "chronos",
361 "-j",
362 "REJECT",
363 "-w", // Wait for xtables lock.
364 };
365
Hugo Benichi283a7812021-06-08 00:47:54 +0900366 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900367}
368
Hugo Benichi283a7812021-06-08 00:47:54 +0900369bool Firewall::DeleteLoopbackLockdownRule(IpFamily ip_family,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900370 Protocol protocol,
371 uint16_t port) {
372 std::vector<std::string> argv{
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900373 "-D", // delete
Hugo Benichiad5947b2021-09-03 16:56:40 +0900374 kEgressPortFirewallChain,
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900375 "-p", // protocol
376 ProtocolName(protocol),
377 "--dport", // destination port
378 std::to_string(port),
379 "-o", // output interface
380 "lo",
381 "-m", // match extension
382 "owner",
383 "!",
384 "--uid-owner",
385 "chronos",
386 "-j",
387 "REJECT",
388 "-w", // Wait for xtables lock.
389 };
390
Hugo Benichi283a7812021-06-08 00:47:54 +0900391 // TODO: add IPv4 or IPv6
392 return RunIptables(ip_family, kFilterTable, argv);
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900393}
394
Hugo Benichi283a7812021-06-08 00:47:54 +0900395bool Firewall::RunIptables(IpFamily ip_family,
396 const std::string& table,
397 const std::vector<std::string>& argv) {
398 if (ip_family == IPv4)
399 return process_runner_->iptables(table, argv, false) == 0;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900400
Hugo Benichi283a7812021-06-08 00:47:54 +0900401 if (ip_family == IPv6)
402 return process_runner_->ip6tables(table, argv, false) == 0;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900403
Hugo Benichi283a7812021-06-08 00:47:54 +0900404 return false;
Jason Jeremy Iman54c046f2020-06-23 23:12:00 +0900405}
406
407} // namespace patchpanel