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