blob: bc174f55930de489f51fd7d29cf7aa9efc8ab189 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lorgnette/firewall_manager.h"
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <new>
#include <string>
#include <utility>
#include <base/functional/callback.h>
#include <base/memory/scoped_refptr.h>
#include <brillo/errors/error.h>
#include <dbus/mock_object_proxy.h>
#include <dbus/object_proxy.h>
#include <dbus/permission_broker/dbus-constants.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "permission_broker/dbus-proxy-mocks.h"
using testing::_;
using testing::DoAll;
using testing::Invoke;
using testing::Return;
using testing::SetArgPointee;
using testing::WithArg;
namespace lorgnette {
namespace {
// Test interface for FirewallManager to request port access on.
constexpr char kTestInterface[] = "Test Interface";
// Well-known port for Canon scanners.
constexpr uint16_t kCanonBjnpPort = 8612;
// Port for epson2 scanners.
constexpr uint16_t kEpson2Port = 1865;
// Test ports to request access for.
constexpr uint16_t kFirstUpdPort = 4311;
constexpr uint16_t kSecondUpdPort = 4312;
} // namespace
class FirewallManagerTest : public testing::Test {
protected:
FirewallManagerTest() = default;
void SetUp() override {
raw_permission_broker_proxy_mock_ = static_cast<
testing::StrictMock<org::chromium::PermissionBrokerProxyMock>*>(
permission_broker_proxy_mock_.get());
}
void InitFirewallManager() {
scoped_refptr<dbus::MockObjectProxy> mock_proxy_ =
base::MakeRefCounted<dbus::MockObjectProxy>(
/*bus=*/nullptr, permission_broker::kPermissionBrokerServiceName,
dbus::ObjectPath(permission_broker::kPermissionBrokerServicePath));
EXPECT_CALL(*raw_permission_broker_proxy_mock_, GetObjectProxy())
.Times(2)
.WillRepeatedly(Return(mock_proxy_.get()));
// Save the callbacks so they can be run later if a test requires it.
EXPECT_CALL(*mock_proxy_, DoWaitForServiceToBeAvailable(_))
.WillOnce(WithArg<0>(Invoke(
[this](dbus::ObjectProxy::WaitForServiceToBeAvailableCallback*
callback) {
wait_for_service_to_be_available_callback_ = std::move(*callback);
})));
EXPECT_CALL(*mock_proxy_, SetNameOwnerChangedCallback(_))
.WillOnce(WithArg<0>(Invoke(
[this](
const dbus::ObjectProxy::NameOwnerChangedCallback& callback) {
set_name_owner_change_callback_ = callback;
})));
firewall_manager_.Init(std::move(permission_broker_proxy_mock_));
}
void RunWaitForServiceToBeAvailableCallback(bool service_available) {
std::move(wait_for_service_to_be_available_callback_)
.Run(service_available);
}
void RunSetNameOwnerChangedCallback(const std::string& old_owner,
const std::string& new_owner) {
set_name_owner_change_callback_.Run(old_owner, new_owner);
}
FirewallManager* firewall_manager() { return &firewall_manager_; }
org::chromium::PermissionBrokerProxyMock* permission_broker_proxy_mock()
const {
return raw_permission_broker_proxy_mock_;
}
private:
// Ownership of `permission_broker_proxy_mock_` is transferred to
// `firewall_manager_` if `InitFirewallManager()` is called.
std::unique_ptr<org::chromium::PermissionBrokerProxyInterface>
permission_broker_proxy_mock_ = std::make_unique<
testing::StrictMock<org::chromium::PermissionBrokerProxyMock>>();
// Allows tests to access the PermissionBrokerProxyMock if ownership of
// `permission_broker_proxy_mock_` has been transferred.
testing::StrictMock<org::chromium::PermissionBrokerProxyMock>*
raw_permission_broker_proxy_mock_ = nullptr;
FirewallManager firewall_manager_{kTestInterface};
dbus::ObjectProxy::WaitForServiceToBeAvailableCallback
wait_for_service_to_be_available_callback_;
dbus::ObjectProxy::NameOwnerChangedCallback set_name_owner_change_callback_;
};
// Test that FirewallManager can request access for all well-known PIXMA scanner
// ports.
TEST_F(FirewallManagerTest, RequestPixmaPortAccess) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kCanonBjnpPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken pixma_token = firewall_manager()->RequestPixmaPortAccess();
// FirewallManager should request to release the associated port when
// `pixma_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kCanonBjnpPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that FirewallManager can request access for the well-known Epson scanner
// port.
TEST_F(FirewallManagerTest, RequestEpsonPortAccess) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kEpson2Port, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken port_token = firewall_manager()->RequestEpsonPortAccess();
// FirewallManager should request to release the associated port when
// `port_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kEpson2Port, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that FirewallManager can request all ports needed for discovery.
TEST_F(FirewallManagerTest, RequestPortsForDiscovery) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kCanonBjnpPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kEpson2Port, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
std::vector<PortToken> tokens =
firewall_manager()->RequestPortsForDiscovery();
// FirewallManager should request to release the associated ports when
// `tokens` goes out of scope.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kCanonBjnpPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kEpson2Port, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that FirewallManager can request access for a specific port.
TEST_F(FirewallManagerTest, RequestUdpPortAccess) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
// FirewallManager should request to release the associated port when
// `udp_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that if the same port is requested multiple times, it won't get released
// until all the port tokens are destroyed.
TEST_F(FirewallManagerTest, RequestSamePort) {
InitFirewallManager();
// Even though the same port is requested twice, this should only be called
// once since the second request just increments the reference count.
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kCanonBjnpPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
// The other UDP port is only requested once.
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken pixma_token = firewall_manager()->RequestPixmaPortAccess();
{
// Request this same port again. When the returned port token goes out of
// scope, the port should not be released since there is an existing port
// token still in scope.
PortToken pixma_token_2 = firewall_manager()->RequestPixmaPortAccess();
// Now, request a different port, which will go out of scope and get
// released while the first port is still open. This checks that the count
// for each port is calculated separately.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
// This port will get requested and released.
PortToken udp_token =
firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
}
// FirewallManager should request to release the associated port once all
// tokens are destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kCanonBjnpPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that if the same port is requested multiple times (before
// FirewallManager is initialized), it won't get released until all the port
// tokens are destroyed.
TEST_F(FirewallManagerTest, RequestSamePortBeforeInitialization) {
PortToken first_token =
firewall_manager()->RequestUdpPortAccess(kCanonBjnpPort);
PortToken second_token =
firewall_manager()->RequestUdpPortAccess(kCanonBjnpPort);
PortToken third_token =
firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
InitFirewallManager();
// FirewallManager should have queued the requested ports, and upon connecting
// to PermissionBroker FirewallManager should immediately request access for
// the ports. The kCanonBjnpPort should only be requested once since the
// second time it just increments the counter.
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kCanonBjnpPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
RunWaitForServiceToBeAvailableCallback(/*service_available=*/true);
// FirewallManager should request to release the associated ports when the
// tokens are destroyed. Note that there should only be one request for
// kCanonBjnpPort because of the reference counting.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kCanonBjnpPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that FirewallManager handles PermissionBroker denying a port access
// request.
TEST_F(FirewallManagerTest, PortAccessRequestDenied) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(false), Return(true)));
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
// With PermissionBroker denying the port access request, we should not see
// FirewallManager sending a request to release the port when `udp_token` is
// destroyed.
}
// Test that FirewallManager handles PermissionBroker not responding to a port
// access request.
TEST_F(FirewallManagerTest, NoResponseToPortAccessRequest) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(WithArg<4>(Invoke([](brillo::ErrorPtr* error) {
*error = brillo::Error::Create(
base::Location(), "Domain", "Code", "Message");
})),
Return(false)));
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
// With PermissionBroker not responding to the port access request, we should
// not see FirewallManager sending a request to release the port when
// `udp_token` is destroyed.
}
// Test that FirewallManager handles PermissionBroker denying a port release
// request.
TEST_F(FirewallManagerTest, PortReleaseRequestDenied) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
// FirewallManager should request to release the associated port when
// `udp_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(false), Return(true)));
}
// Test that FirewallManager handles PermissionBroker not responding to a port
// release request.
TEST_F(FirewallManagerTest, NoResponseToPortReleaseRequest) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
// FirewallManager should request to release the associated port when
// `udp_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(WithArg<3>(Invoke([](brillo::ErrorPtr* error) {
*error = brillo::Error::Create(
base::Location(), "Domain", "Code", "Message");
})),
Return(false)));
}
// Test that ports can be requested before FirewallManager is initialized.
TEST_F(FirewallManagerTest, RequestPortsBeforeInitialization) {
PortToken first_token =
firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
PortToken second_token =
firewall_manager()->RequestUdpPortAccess(kSecondUpdPort);
InitFirewallManager();
// FirewallManager should have queued the requested ports, and upon connecting
// to PermissionBroker FirewallManager should immediately request access for
// the ports.
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kSecondUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
RunWaitForServiceToBeAvailableCallback(/*service_available=*/true);
// FirewallManager should request to release the associated ports when the
// tokens are destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kSecondUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that a service name change by PermissionBroker results in re-requesting
// port access.
TEST_F(FirewallManagerTest, ReRequestPortsAfterServiceNameChange) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.Times(2)
.WillRepeatedly(DoAll(SetArgPointee<3>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kSecondUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.Times(2)
.WillRepeatedly(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken first_token =
firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
PortToken second_token =
firewall_manager()->RequestUdpPortAccess(kSecondUpdPort);
RunSetNameOwnerChangedCallback("Old owner", "New owner");
// FirewallManager should request to release the associated ports when the
// tokens are destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kSecondUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that FirewallManager does not send any requests to PermissionBroker if
// FirewallManager is never initialized.
TEST_F(FirewallManagerTest, NeverInitialized) {
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
}
// Test that FirewallManager does not send a port access request if
// PermissionBroker is not available.
TEST_F(FirewallManagerTest, PermissionBrokerUnavailable) {
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
InitFirewallManager();
RunWaitForServiceToBeAvailableCallback(/*service_available=*/false);
// FirewallManager should request to release the associated port when
// `udp_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that FirewallManager does not re-request port access if a service name
// change by PermissionBroker results in no new owner.
TEST_F(FirewallManagerTest, ServiceNameChangeNoNewOwner) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
PortToken udp_token = firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
RunSetNameOwnerChangedCallback("Old owner", /*new_owner=*/"");
// FirewallManager should request to release the associated port when
// `udp_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
// Test that a PortToken can be constructed via the move constructor and
// ownership of the port is transferred.
TEST_F(FirewallManagerTest, PortTokenMoveConstructor) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kFirstUpdPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
std::unique_ptr<PortToken> moved_token;
{
// Normally, we would expect a port release request when `first_token` goes
// out of scope. However, since ownership of the port is transferred to
// `moved_token` with the move constructor, we do not expect the port
// release request until `moved_token` goes out of scope.
PortToken first_token =
firewall_manager()->RequestUdpPortAccess(kFirstUpdPort);
moved_token = std::make_unique<PortToken>(std::move(first_token));
}
// FirewallManager should request to release the associated port when
// `moved_token` is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kFirstUpdPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
TEST_F(FirewallManagerTest, RequestPortAccessIfNeeded) {
InitFirewallManager();
EXPECT_CALL(*permission_broker_proxy_mock(),
RequestUdpPortAccess(kCanonBjnpPort, kTestInterface, _, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(true)));
// A pixma scanner requires a port to be opened. This should return a valid
// port.
std::unique_ptr<PortToken> token =
firewall_manager()->RequestPortAccessIfNeeded("pixma:MF2600_1.2.3.4");
EXPECT_TRUE(token);
// FirewallManager should request to release the associated port when `token`
// is destroyed.
EXPECT_CALL(*permission_broker_proxy_mock(),
ReleaseUdpPort(kCanonBjnpPort, kTestInterface, _, _,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT))
.WillOnce(DoAll(SetArgPointee<2>(true), Return(true)));
}
TEST_F(FirewallManagerTest, RequestPortAccessNotNeeded) {
InitFirewallManager();
// This should not require a port to be opened.
std::unique_ptr<PortToken> token =
firewall_manager()->RequestPortAccessIfNeeded("pixma:04A91234_5555");
EXPECT_FALSE(token);
}
} // namespace lorgnette