// Copyright 2014 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "iptables.h" #include <linux/capability.h> #include <string> #include <vector> #include <base/bind.h> #include <base/bind_helpers.h> #include <base/callback.h> #include <base/logging.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> #include <brillo/minijail/minijail.h> #include <brillo/process.h> namespace { using IpTablesCallback = base::Callback<bool(const std::string&, bool)>; #if defined(__ANDROID__) const char kIpTablesPath[] = "/system/bin/iptables"; const char kIp6TablesPath[] = "/system/bin/ip6tables"; const char kIpPath[] = "/system/bin/ip"; #else const char kIpTablesPath[] = "/sbin/iptables"; const char kIp6TablesPath[] = "/sbin/ip6tables"; const char kIpPath[] = "/bin/ip"; const char kUnprivilegedUser[] = "nobody"; #endif // __ANDROID__ const char kIPv4[] = "IPv4"; const char kIPv6[] = "IPv6"; const uint64_t kIpTablesCapMask = CAP_TO_MASK(CAP_NET_ADMIN) | CAP_TO_MASK(CAP_NET_RAW); // Interface names must be shorter than 'IFNAMSIZ' chars. // See http://man7.org/linux/man-pages/man7/netdevice.7.html // 'IFNAMSIZ' is 16 in recent kernels. // See http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L26 const size_t kInterfaceNameSize = 16; const char kMarkForUserTraffic[] = "1"; const char kTableIdForUserTraffic[] = "1"; bool IsValidInterfaceName(const std::string& iface) { // |iface| should be shorter than |kInterfaceNameSize| chars and have only // alphanumeric characters (embedded hypens and periods are also permitted). if (iface.length() >= kInterfaceNameSize) { return false; } if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) || base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) || base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) || base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) { return false; } for (auto c : iface) { if (!std::isalnum(c) && (c != '-') && (c != '.')) { return false; } } return true; } bool RunForAllArguments(const IpTablesCallback& iptables_cmd, const std::vector<std::string>& arguments, bool add) { bool success = true; for (const auto& argument : arguments) { if (!iptables_cmd.Run(argument, add)) { // On failure, only abort if rules are being added. // If removing a rule fails, attempt the remaining removals but still // return 'false'. success = false; if (add) break; } } return success; } } // namespace namespace firewalld { IpTables::IpTables() { } IpTables::~IpTables() { // Plug all holes when destructed. PlugAllHoles(); } bool IpTables::PunchTcpHole(uint16_t in_port, const std::string& in_interface) { return PunchHole(in_port, in_interface, &tcp_holes_, kProtocolTcp); } bool IpTables::PunchUdpHole(uint16_t in_port, const std::string& in_interface) { return PunchHole(in_port, in_interface, &udp_holes_, kProtocolUdp); } bool IpTables::PlugTcpHole(uint16_t in_port, const std::string& in_interface) { return PlugHole(in_port, in_interface, &tcp_holes_, kProtocolTcp); } bool IpTables::PlugUdpHole(uint16_t in_port, const std::string& in_interface) { return PlugHole(in_port, in_interface, &udp_holes_, kProtocolUdp); } bool IpTables::RequestVpnSetup(const std::vector<std::string>& usernames, const std::string& interface) { return ApplyVpnSetup(usernames, interface, true /* add */); } bool IpTables::RemoveVpnSetup(const std::vector<std::string>& usernames, const std::string& interface) { return ApplyVpnSetup(usernames, interface, false /* delete */); } bool IpTables::PunchHole(uint16_t port, const std::string& interface, std::set<Hole>* holes, ProtocolEnum protocol) { if (port == 0) { // Port 0 is not a valid TCP/UDP port. return false; } if (!IsValidInterfaceName(interface)) { LOG(ERROR) << "Invalid interface name '" << interface << "'"; return false; } Hole hole = std::make_pair(port, interface); if (holes->find(hole) != holes->end()) { // We have already punched a hole for |port| on |interface|. // Be idempotent: do nothing and succeed. return true; } std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP"; LOG(INFO) << "Punching hole for " << sprotocol << " port " << port << " on interface '" << interface << "'"; if (!AddAcceptRules(protocol, port, interface)) { // If the 'iptables' command fails, this method fails. LOG(ERROR) << "Adding ACCEPT rules failed."; return false; } // Track the hole we just punched. holes->insert(hole); return true; } bool IpTables::PlugHole(uint16_t port, const std::string& interface, std::set<Hole>* holes, ProtocolEnum protocol) { if (port == 0) { // Port 0 is not a valid TCP/UDP port. return false; } Hole hole = std::make_pair(port, interface); if (holes->find(hole) == holes->end()) { // There is no firewall hole for |port| on |interface|. // Even though this makes |PlugHole| not idempotent, // and Punch/Plug not entirely symmetrical, fail. It might help catch bugs. return false; } std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP"; LOG(INFO) << "Plugging hole for " << sprotocol << " port " << port << " on interface '" << interface << "'"; if (!DeleteAcceptRules(protocol, port, interface)) { // If the 'iptables' command fails, this method fails. LOG(ERROR) << "Deleting ACCEPT rules failed."; return false; } // Stop tracking the hole we just plugged. holes->erase(hole); return true; } void IpTables::PlugAllHoles() { // Copy the container so that we can remove elements from the original. // TCP std::set<Hole> holes = tcp_holes_; for (auto hole : holes) { PlugHole(hole.first /* port */, hole.second /* interface */, &tcp_holes_, kProtocolTcp); } // UDP holes = udp_holes_; for (auto hole : holes) { PlugHole(hole.first /* port */, hole.second /* interface */, &udp_holes_, kProtocolUdp); } CHECK(tcp_holes_.size() == 0) << "Failed to plug all TCP holes."; CHECK(udp_holes_.size() == 0) << "Failed to plug all UDP holes."; } bool IpTables::AddAcceptRules(ProtocolEnum protocol, uint16_t port, const std::string& interface) { if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) { LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'"; return false; } if (AddAcceptRule(kIp6TablesPath, protocol, port, interface)) { // This worked, record this fact and insist that it works thereafter. ip6_enabled_ = true; } else if (ip6_enabled_) { // It's supposed to work, fail. LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath << "', aborting operation."; DeleteAcceptRule(kIpTablesPath, protocol, port, interface); return false; } else { // It never worked, just ignore it. LOG(WARNING) << "Could not add ACCEPT rule using '" << kIp6TablesPath << "', ignoring."; } return true; } bool IpTables::DeleteAcceptRules(ProtocolEnum protocol, uint16_t port, const std::string& interface) { bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port, interface); bool ip6_success = !ip6_enabled_ || DeleteAcceptRule(kIp6TablesPath, protocol, port, interface); return ip4_success && ip6_success; } bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames, const std::string& interface, bool add) { bool success = true; std::vector<std::string> added_usernames; if (!ApplyRuleForUserTraffic(add)) { if (add) { ApplyRuleForUserTraffic(false /* remove */); return false; } success = false; } if (!ApplyMasquerade(interface, add)) { if (add) { ApplyVpnSetup(added_usernames, interface, false /* remove */); return false; } success = false; } for (const auto& username : usernames) { if (!ApplyMarkForUserTraffic(username, add)) { if (add) { ApplyVpnSetup(added_usernames, interface, false /* remove */); return false; } success = false; } if (add) { added_usernames.push_back(username); } } return success; } bool IpTables::ApplyMasquerade(const std::string& interface, bool add) { const IpTablesCallback apply_masquerade = base::Bind(&IpTables::ApplyMasqueradeWithExecutable, base::Unretained(this), interface); return RunForAllArguments( apply_masquerade, {kIpTablesPath, kIp6TablesPath}, add); } bool IpTables::ApplyMarkForUserTraffic(const std::string& username, bool add) { const IpTablesCallback apply_mark = base::Bind(&IpTables::ApplyMarkForUserTrafficWithExecutable, base::Unretained(this), username); return RunForAllArguments(apply_mark, {kIpTablesPath, kIp6TablesPath}, add); } bool IpTables::ApplyRuleForUserTraffic(bool add) { const IpTablesCallback apply_rule = base::Bind( &IpTables::ApplyRuleForUserTrafficWithVersion, base::Unretained(this)); return RunForAllArguments(apply_rule, {kIPv4, kIPv6}, add); } bool IpTables::AddAcceptRule(const std::string& executable_path, ProtocolEnum protocol, uint16_t port, const std::string& interface) { std::vector<std::string> argv; argv.push_back(executable_path); argv.push_back("-I"); // insert argv.push_back("INPUT"); argv.push_back("-p"); // protocol argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp"); argv.push_back("--dport"); // destination port argv.push_back(std::to_string(port)); if (!interface.empty()) { argv.push_back("-i"); // interface argv.push_back(interface); } argv.push_back("-j"); argv.push_back("ACCEPT"); argv.push_back("-w"); // Wait for xtables lock. // Use CAP_NET_ADMIN|CAP_NET_RAW. return ExecvNonRoot(argv, kIpTablesCapMask) == 0; } bool IpTables::DeleteAcceptRule(const std::string& executable_path, ProtocolEnum protocol, uint16_t port, const std::string& interface) { std::vector<std::string> argv; argv.push_back(executable_path); argv.push_back("-D"); // delete argv.push_back("INPUT"); argv.push_back("-p"); // protocol argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp"); argv.push_back("--dport"); // destination port argv.push_back(std::to_string(port)); if (interface != "") { argv.push_back("-i"); // interface argv.push_back(interface); } argv.push_back("-j"); argv.push_back("ACCEPT"); argv.push_back("-w"); // Wait for xtables lock. // Use CAP_NET_ADMIN|CAP_NET_RAW. return ExecvNonRoot(argv, kIpTablesCapMask) == 0; } bool IpTables::ApplyMasqueradeWithExecutable(const std::string& interface, const std::string& executable_path, bool add) { std::vector<std::string> argv; argv.push_back(executable_path); argv.push_back("-t"); // table argv.push_back("nat"); argv.push_back(add ? "-A" : "-D"); // rule argv.push_back("POSTROUTING"); argv.push_back("-o"); // output interface argv.push_back(interface); argv.push_back("-j"); argv.push_back("MASQUERADE"); // Use CAP_NET_ADMIN|CAP_NET_RAW. bool success = ExecvNonRoot(argv, kIpTablesCapMask) == 0; if (!success) { LOG(ERROR) << (add ? "Adding" : "Removing") << " masquerade failed for interface " << interface << " using '" << executable_path << "'"; } return success; } bool IpTables::ApplyMarkForUserTrafficWithExecutable( const std::string& username, const std::string& executable_path, bool add) { std::vector<std::string> argv; argv.push_back(executable_path); argv.push_back("-t"); // table argv.push_back("mangle"); argv.push_back(add ? "-A" : "-D"); // rule argv.push_back("OUTPUT"); argv.push_back("-m"); argv.push_back("owner"); argv.push_back("--uid-owner"); argv.push_back(username); argv.push_back("-j"); argv.push_back("MARK"); argv.push_back("--set-mark"); argv.push_back(kMarkForUserTraffic); // Use CAP_NET_ADMIN|CAP_NET_RAW. bool success = ExecvNonRoot(argv, kIpTablesCapMask) == 0; if (!success) { LOG(ERROR) << (add ? "Adding" : "Removing") << " mark failed for user " << username << " using '" << kIpTablesPath << "'"; } return success; } bool IpTables::ApplyRuleForUserTrafficWithVersion(const std::string& ip_version, bool add) { brillo::ProcessImpl ip; ip.AddArg(kIpPath); if (ip_version == kIPv6) ip.AddArg("-6"); ip.AddArg("rule"); ip.AddArg(add ? "add" : "delete"); ip.AddArg("fwmark"); ip.AddArg(kMarkForUserTraffic); ip.AddArg("table"); ip.AddArg(kTableIdForUserTraffic); bool success = ip.Run() == 0; if (!success) { LOG(ERROR) << (add ? "Adding" : "Removing") << " rule for " << ip_version << " user traffic failed"; } return success; } int IpTables::ExecvNonRoot(const std::vector<std::string>& argv, uint64_t capmask) { brillo::Minijail* m = brillo::Minijail::GetInstance(); minijail* jail = m->New(); #if !defined(__ANDROID__) // TODO(garnold) This needs to be re-enabled once we figure out which // unprivileged user we want to use. m->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser); #endif // __ANDROID__ m->UseCapabilities(jail, capmask); std::vector<char*> args; for (const auto& arg : argv) { args.push_back(const_cast<char*>(arg.c_str())); } args.push_back(nullptr); int status; bool ran = m->RunSyncAndDestroy(jail, args, &status); return ran ? status : -1; } } // namespace firewalld