// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "components/exo/wayland/zaura_output_manager.h"
#include <sys/socket.h>
#include <cstdint>
#include "components/exo/wayland/server_util.h"
#include "components/exo/wayland/test/test_client.h"
#include "components/exo/wayland/test/wayland_server_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace exo::wayland {
namespace {
using ::testing::_;
class MockAuraOutputManagerListener {
public:
static void OnDone(void* data,
zaura_output_manager* output_manager,
wl_output* output) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnDone(output);
}
MOCK_METHOD(void, MockOnDone, (wl_output * output));
static void OnDisplayId(void* data,
zaura_output_manager* output_manager,
wl_output* output,
uint32_t display_id_hi,
uint32_t display_id_lo) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnDisplayId(output, display_id_hi, display_id_lo);
}
MOCK_METHOD(void,
MockOnDisplayId,
(wl_output * output,
uint32_t display_id_hi,
uint32_t display_id_lo));
static void OnLogicalPosition(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t x,
int32_t y) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnLogicalPosition(output, x, y);
}
MOCK_METHOD(void,
MockOnLogicalPosition,
(wl_output * output, int32_t x, int32_t y));
static void OnLogicalSize(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t width,
int32_t height) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnLogicalSize(output, width, height);
}
MOCK_METHOD(void,
MockOnLogicalSize,
(wl_output * output, int32_t width, int32_t height));
static void OnPhysicalSize(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t width,
int32_t height) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnPhysicalSize(output, width, height);
}
MOCK_METHOD(void,
MockOnPhysicalSize,
(wl_output * output, int32_t width, int32_t height));
static void OnInsets(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnInsets(output, top, left, bottom, right);
}
MOCK_METHOD(void,
MockOnInsets,
(wl_output * output,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right));
static void OnDeviceScaleFactor(void* data,
zaura_output_manager* output_manager,
wl_output* output,
uint32_t scale_as_uint) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnDeviceScaleFactor(output, scale_as_uint);
}
MOCK_METHOD(void,
MockOnDeviceScaleFactor,
(wl_output * output, uint32_t scale_as_uint));
static void OnLogicalTransform(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t transform) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnLogicalTransform(output, transform);
}
MOCK_METHOD(void,
MockOnLogicalTransform,
(wl_output * output, int32_t transform));
static void OnPanelTransform(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t transform) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnPanelTransform(output, transform);
}
MOCK_METHOD(void,
MockOnPanelTransform,
(wl_output * output, int32_t transform));
static void OnName(void* data,
zaura_output_manager* output_manager,
wl_output* output,
const char* name) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnName(output, name);
}
MOCK_METHOD(void, MockOnName, (wl_output * output, const char* name));
static void OnDescription(void* data,
zaura_output_manager* output_manager,
wl_output* output,
const char* description) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnDescription(output, description);
}
MOCK_METHOD(void,
MockOnDescription,
(wl_output * output, const char* description));
static void OnActivated(void* data,
zaura_output_manager* output_manager,
wl_output* output) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnActivated(output);
}
MOCK_METHOD(void, MockOnActivated, (wl_output * output));
static void OnOverscanInsets(void* data,
zaura_output_manager* output_manager,
wl_output* output,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right) {
auto* self = static_cast<MockAuraOutputManagerListener*>(data);
self->MockOnOverscanInsets(output, top, left, bottom, right);
}
MOCK_METHOD(void,
MockOnOverscanInsets,
(wl_output * output,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right));
};
class AuraOutputManagerTest : public test::WaylandServerTest {
public:
AuraOutputManagerTest() = default;
AuraOutputManagerTest(const AuraOutputManagerTest&) = delete;
AuraOutputManagerTest& operator=(const AuraOutputManagerTest&) = delete;
~AuraOutputManagerTest() override = default;
std::unique_ptr<test::TestClient> InitOnClientThread() override {
mock_aura_output_manager_ =
std::make_unique<::testing::NiceMock<MockAuraOutputManagerListener>>();
auto test_client = test::WaylandServerTest::InitOnClientThread();
static constexpr zaura_output_manager_listener
zaura_output_manager_listener = {
&MockAuraOutputManagerListener::OnDone,
&MockAuraOutputManagerListener::OnDisplayId,
&MockAuraOutputManagerListener::OnLogicalPosition,
&MockAuraOutputManagerListener::OnLogicalSize,
&MockAuraOutputManagerListener::OnPhysicalSize,
&MockAuraOutputManagerListener::OnInsets,
&MockAuraOutputManagerListener::OnDeviceScaleFactor,
&MockAuraOutputManagerListener::OnLogicalTransform,
&MockAuraOutputManagerListener::OnPanelTransform,
&MockAuraOutputManagerListener::OnName,
&MockAuraOutputManagerListener::OnDescription,
&MockAuraOutputManagerListener::OnActivated,
&MockAuraOutputManagerListener::OnOverscanInsets};
zaura_output_manager_add_listener(test_client->aura_output_manager(),
&zaura_output_manager_listener,
mock_aura_output_manager_.get());
return test_client;
}
// Creates a wl_client instance for this test.
void CreateClient() {
ASSERT_FALSE(client_);
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds_);
client_ = wl_client_create(server_->GetWaylandDisplay(), fds_[0]);
}
// Destroys the wl_client instance for this test if it exists.
void DestroyClient() {
if (client_) {
wl_client_destroy(client_.ExtractAsDangling());
close(fds_[1]);
client_ = nullptr;
}
}
protected:
std::unique_ptr<MockAuraOutputManagerListener> mock_aura_output_manager_;
int fds_[2] = {0, 0};
raw_ptr<wl_client> client_ = nullptr;
};
} // namespace
// Regression test for crbug.com/1433187. Ensures AuraOutputManager::Get() does
// not cause UAF crashes by attempting to iterate over resources belonging to a
// client that has started destruction.
TEST_F(AuraOutputManagerTest, GetterReturnsNullAfterClientDestroyed) {
CreateClient();
EXPECT_FALSE(IsClientDestroyed(client_));
// Create a resource associated with the client.
wl_resource* output_resource =
wl_resource_create(client_, &wl_output_interface, 2, 0);
wl_resource_set_user_data(output_resource, client_);
// Ensure that calls to AuraOutputManager::Get() after client destruction
// does not result in UAF crashes. This callback will run after the client's
// destruction sequence begins and associated resources are freed.
auto wl_resource_callback = [](wl_resource* resource) {
wl_client* client =
static_cast<wl_client*>(wl_resource_get_user_data(resource));
EXPECT_TRUE(IsClientDestroyed(client));
EXPECT_FALSE(AuraOutputManager::Get(client));
};
wl_resource_set_destructor(output_resource, wl_resource_callback);
DestroyClient();
}
TEST_F(AuraOutputManagerTest, SendOverscanInsets) {
wl_output* client_output = nullptr;
{
// Start with no overscan.
UpdateDisplay("800x600");
PostToClientAndWait(
[&](test::TestClient* client) { client_output = client->output(); });
::testing::InSequence seq;
const gfx::Insets no_overscan =
display_manager()
->GetDisplayInfo(GetPrimaryDisplay().id())
.GetOverscanInsetsInPixel();
EXPECT_TRUE(no_overscan.IsEmpty());
EXPECT_CALL(*mock_aura_output_manager_,
MockOnOverscanInsets(client_output, no_overscan.top(),
no_overscan.left(), no_overscan.bottom(),
no_overscan.right()));
EXPECT_CALL(*mock_aura_output_manager_, MockOnDone(client_output));
display_manager()->NotifyDidProcessDisplayChanges(
{/*added_displays=*/{},
/*removed_displays=*/{},
/*display_metrics_changes=*/{{GetPrimaryDisplay(), 0xFFFFFFFF}}});
PostToClientAndWait([] {});
::testing::Mock::VerifyAndClearExpectations(
mock_aura_output_manager_.get());
}
{
// With zoom to verify dip to physical pixel conversion.
UpdateDisplay("800x600*2.0");
PostToClientAndWait([] {});
::testing::InSequence seq;
const gfx::Insets overscan_in_dip = gfx::Insets::TLBR(5, 2, 10, 7);
const gfx::Insets overscan_in_pixel =
gfx::ScaleToFlooredInsets(overscan_in_dip, 2.0);
EXPECT_CALL(*mock_aura_output_manager_,
MockOnOverscanInsets(client_output, overscan_in_pixel.top(),
overscan_in_pixel.left(),
overscan_in_pixel.bottom(),
overscan_in_pixel.right()));
EXPECT_CALL(*mock_aura_output_manager_, MockOnDone(client_output));
display_manager()->SetOverscanInsets(GetPrimaryDisplay().id(),
overscan_in_dip);
PostToClientAndWait([] {});
::testing::Mock::VerifyAndClearExpectations(
mock_aura_output_manager_.get());
}
{
// With rotation.
UpdateDisplay("800x600*2.0/r");
PostToClientAndWait([] {});
::testing::InSequence seq;
const gfx::Insets overscan_in_dip = gfx::Insets::TLBR(7, 9, 5, 4);
const gfx::Insets overscan_in_pixel =
gfx::ScaleToFlooredInsets(overscan_in_dip, 2.0);
EXPECT_CALL(*mock_aura_output_manager_,
MockOnOverscanInsets(client_output, overscan_in_pixel.top(),
overscan_in_pixel.left(),
overscan_in_pixel.bottom(),
overscan_in_pixel.right()));
EXPECT_CALL(*mock_aura_output_manager_, MockOnDone(client_output));
display_manager()->SetOverscanInsets(GetPrimaryDisplay().id(),
overscan_in_dip);
PostToClientAndWait([] {});
::testing::Mock::VerifyAndClearExpectations(
mock_aura_output_manager_.get());
}
}
} // namespace exo::wayland