| // 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. |
| |
| |
| #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 |