blob: 440ded877480fa368b01c4a35fa4d5fb43d7c7cd [file] [log] [blame]
// 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