| // Copyright 2015 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "permission_broker/port_tracker.h" |
| |
| #include <sys/epoll.h> |
| #include <unistd.h> |
| |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| |
| namespace { |
| const int kMaxEvents = 10; |
| const int64 kLifelineIntervalSeconds = 5; |
| } |
| |
| namespace permission_broker { |
| |
| PortTracker::PortTracker(org::chromium::FirewalldProxyInterface* firewalld) |
| : task_runner_{base::MessageLoopForIO::current()->task_runner()}, |
| epfd_{-1}, |
| firewalld_{firewalld} {} |
| |
| // Test-only. |
| PortTracker::PortTracker(scoped_refptr<base::SequencedTaskRunner> task_runner, |
| org::chromium::FirewalldProxyInterface* firewalld) |
| : task_runner_{task_runner}, epfd_{-1}, firewalld_{firewalld} {} |
| |
| PortTracker::~PortTracker() { |
| if (epfd_ >= 0) { |
| close(epfd_); |
| } |
| } |
| |
| bool PortTracker::ProcessTcpPort(uint16_t port, int dbus_fd) { |
| // We use |lifeline_fd| to track the lifetime of the process requesting |
| // port access. |
| int lifeline_fd = AddLifelineFd(dbus_fd); |
| if (lifeline_fd < 0) { |
| LOG(ERROR) << "Tracking lifeline fd for TCP port " << port << " failed"; |
| return false; |
| } |
| |
| // Track the port. |
| tcp_ports_[lifeline_fd] = port; |
| |
| bool success; |
| firewalld_->PunchTcpHole(port, &success, nullptr); |
| if (!success) { |
| // If we fail to punch the hole in the firewall, stop tracking the lifetime |
| // of the process. |
| LOG(ERROR) << "Failed to punch hole for TCP port " << port; |
| DeleteLifelineFd(lifeline_fd); |
| tcp_ports_.erase(lifeline_fd); |
| return false; |
| } |
| return true; |
| } |
| |
| bool PortTracker::ProcessUdpPort(uint16_t port, int dbus_fd) { |
| // We use |lifeline_fd| to track the lifetime of the process requesting |
| // port access. |
| int lifeline_fd = AddLifelineFd(dbus_fd); |
| if (lifeline_fd < 0) { |
| LOG(ERROR) << "Tracking lifeline fd for UDP port " << port << " failed"; |
| return false; |
| } |
| |
| // Track the port. |
| udp_ports_[lifeline_fd] = port; |
| |
| bool success; |
| firewalld_->PunchUdpHole(port, &success, nullptr); |
| if (!success) { |
| // If we fail to punch the hole in the firewall, stop tracking the lifetime |
| // of the process. |
| LOG(ERROR) << "Failed to punch hole for UDP port " << port; |
| DeleteLifelineFd(lifeline_fd); |
| udp_ports_.erase(lifeline_fd); |
| return false; |
| } |
| return true; |
| } |
| |
| int PortTracker::AddLifelineFd(int dbus_fd) { |
| if (!InitializeEpollOnce()) { |
| return -1; |
| } |
| int fd = dup(dbus_fd); |
| |
| struct epoll_event epevent; |
| epevent.events = |
| EPOLLIN | EPOLLET; // EPOLLERR | EPOLLHUP are always waited for. |
| epevent.data.fd = fd; |
| LOG(INFO) << "Adding file descriptor " << fd << " to epoll instance"; |
| if (epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &epevent) != 0) { |
| PLOG(ERROR) << "epoll_ctl(EPOLL_CTL_ADD)"; |
| return -1; |
| } |
| |
| // If this is the first port request, start lifeline checks. |
| if (tcp_ports_.size() + udp_ports_.size() == 0) { |
| LOG(INFO) << "Starting lifeline checks"; |
| ScheduleLifelineCheck(); |
| } |
| |
| return fd; |
| } |
| |
| bool PortTracker::DeleteLifelineFd(int fd) { |
| if (epfd_ < 0) { |
| LOG(ERROR) << "epoll instance not created"; |
| return false; |
| } |
| |
| LOG(INFO) << "Deleting file descriptor " << fd << " from epoll instance"; |
| if (epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, NULL) != 0) { |
| PLOG(ERROR) << "epoll_ctl(EPOLL_CTL_DEL)"; |
| return false; |
| } |
| return true; |
| } |
| |
| void PortTracker::CheckLifelineFds() { |
| if (epfd_ < 0) { |
| return; |
| } |
| struct epoll_event epevents[kMaxEvents]; |
| int nready = epoll_wait(epfd_, epevents, kMaxEvents, 0 /* do not block */); |
| if (nready < 0) { |
| PLOG(ERROR) << "epoll_wait(0)"; |
| return; |
| } |
| if (nready == 0) { |
| ScheduleLifelineCheck(); |
| return; |
| } |
| |
| for (size_t eidx = 0; eidx < (size_t)nready; eidx++) { |
| uint32_t events = epevents[eidx].events; |
| int fd = epevents[eidx].data.fd; |
| if ((events & (EPOLLHUP | EPOLLERR))) { |
| // The process that requested this port has died/exited, |
| // so we need to plug the hole. |
| PlugFirewallHole(fd); |
| DeleteLifelineFd(fd); |
| } |
| } |
| |
| // If there's still processes to track, schedule lifeline checks. |
| if (tcp_ports_.size() + udp_ports_.size() > 0) { |
| ScheduleLifelineCheck(); |
| } else { |
| LOG(INFO) << "Stopping lifeline checks"; |
| } |
| } |
| |
| void PortTracker::ScheduleLifelineCheck() { |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&PortTracker::CheckLifelineFds, base::Unretained(this)), |
| base::TimeDelta::FromSeconds(kLifelineIntervalSeconds)); |
| } |
| |
| void PortTracker::PlugFirewallHole(int fd) { |
| bool success = false; |
| uint16_t port = 0; |
| if (tcp_ports_.find(fd) != tcp_ports_.end()) { |
| // It was a TCP port. |
| port = tcp_ports_[fd]; |
| firewalld_->PlugTcpHole(port, &success, nullptr); |
| tcp_ports_.erase(fd); |
| if (!success) { |
| LOG(ERROR) << "Failed to plug hole for TCP port " << port; |
| } |
| } else if (udp_ports_.find(fd) != udp_ports_.end()) { |
| // It was a UDP port. |
| port = udp_ports_[fd]; |
| firewalld_->PlugUdpHole(port, &success, nullptr); |
| udp_ports_.erase(fd); |
| if (!success) { |
| LOG(ERROR) << "Failed to plug hole for UDP port " << port; |
| } |
| } else { |
| LOG(ERROR) << "File descriptor " << fd << " was not being tracked"; |
| } |
| } |
| |
| bool PortTracker::InitializeEpollOnce() { |
| if (epfd_ < 0) { |
| // |size| needs to be > 0, but is otherwise ignored. |
| LOG(INFO) << "Creating epoll instance"; |
| epfd_ = epoll_create(1 /* size */); |
| if (epfd_ < 0) { |
| PLOG(ERROR) << "epoll_create()"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace permission_broker |