mirror of
https://github.com/LizardByte/libvirtualhid.git
synced 2026-10-28 23:57:04 +09:00
359 lines
14 KiB
C++
359 lines
14 KiB
C++
/**
|
|
* @file tests/unit/test_windows_broker_validation.cpp
|
|
* @brief Unit tests for validation of untrusted Windows broker messages.
|
|
*/
|
|
|
|
// local includes
|
|
#include "broker_request_validation.hpp"
|
|
#include "keyboard_protocol.hpp"
|
|
#include "mouse_protocol.hpp"
|
|
#include "platform/windows/control_protocol.hpp"
|
|
|
|
// test includes
|
|
#include <gtest/gtest.h>
|
|
|
|
// standard includes
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <utility>
|
|
|
|
// lib includes
|
|
#include <libvirtualhid/types.hpp>
|
|
|
|
namespace {
|
|
|
|
LvhWindowsBrokerRequestHeader request_header(
|
|
LvhWindowsBrokerRequestType type,
|
|
std::uint32_t size
|
|
) {
|
|
return {
|
|
.version = LVH_WINDOWS_BROKER_PROTOCOL_VERSION,
|
|
.size = size,
|
|
.type = std::to_underlying(type),
|
|
.reserved0 = 0U,
|
|
};
|
|
}
|
|
|
|
LvhWindowsBrokerCreateDeviceRequest valid_create_request() {
|
|
lvh::CreateGamepadOptions options;
|
|
options.profile.device_type = lvh::DeviceType::gamepad;
|
|
options.profile.gamepad_kind = lvh::GamepadProfileKind::generic;
|
|
options.profile.bus_type = lvh::BusType::usb;
|
|
options.profile.vendor_id = 0x1209;
|
|
options.profile.product_id = 0x0001;
|
|
options.profile.version = 0x0001;
|
|
options.profile.input_report_size = 4;
|
|
options.profile.output_report_size = 2;
|
|
options.profile.name = "Test gamepad";
|
|
options.profile.manufacturer = "LizardByte";
|
|
options.profile.report_descriptor = {0x05, 0x01, 0x09, 0x05};
|
|
|
|
LvhWindowsBrokerCreateDeviceRequest request {};
|
|
request.header = request_header(
|
|
LvhWindowsBrokerRequestType::create_device,
|
|
sizeof(request)
|
|
);
|
|
request.client_control_handle = 1U;
|
|
request.device = lvh::detail::windows::make_create_device_request(1U, options);
|
|
return request;
|
|
}
|
|
|
|
LvhWindowsBrokerDestroyDeviceRequest valid_destroy_request() {
|
|
LvhWindowsSessionToken token {};
|
|
token.bytes[0] = 1U;
|
|
|
|
LvhWindowsBrokerDestroyDeviceRequest request {};
|
|
request.header = request_header(
|
|
LvhWindowsBrokerRequestType::destroy_device,
|
|
sizeof(request)
|
|
);
|
|
request.device = lvh::detail::windows::make_destroy_device_request(1U, token);
|
|
return request;
|
|
}
|
|
|
|
LvhWindowsBrokerLicenseRequest valid_license_request(LvhWindowsBrokerRequestType type) {
|
|
LvhWindowsBrokerLicenseRequest request {};
|
|
request.header = request_header(type, sizeof(request));
|
|
if (type == LvhWindowsBrokerRequestType::activate_license) {
|
|
std::memcpy(request.license_key.data(), "test-key", sizeof("test-key"));
|
|
std::memcpy(request.instance_name.data(), "test-machine", sizeof("test-machine"));
|
|
}
|
|
return request;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(WindowsBrokerValidationTest, ValidatesStatusHeaderAndReservedField) {
|
|
LvhWindowsBrokerStatusRequest request {};
|
|
request.header = request_header(
|
|
LvhWindowsBrokerRequestType::status,
|
|
sizeof(request)
|
|
);
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request.header.reserved0 = 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.header.reserved0 = 0U;
|
|
++request.header.size;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.header.size = sizeof(request);
|
|
++request.header.version;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.header.version = LVH_WINDOWS_BROKER_PROTOCOL_VERSION;
|
|
request.header.type = std::to_underlying(LvhWindowsBrokerRequestType::validate_license);
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, RejectsMalformedCreateFields) {
|
|
const auto valid = valid_create_request();
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(valid));
|
|
|
|
auto request = valid;
|
|
request.client_control_handle = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
++request.header.version;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
++request.device.version;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.client_device_id = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
++request.device.size;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.device_type = 99U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.bus_type = LVH_WINDOWS_BUS_UNKNOWN;
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.bus_type = LVH_WINDOWS_BUS_BLUETOOTH;
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.bus_type = 99U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.gamepad_kind = 99U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.flags = 0x80000000U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.hardware_ids.reserved0[0] = 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_sizes.report_descriptor_size = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_sizes.input_report_size = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_MAX_INPUT_REPORT_SIZE + 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_sizes.output_report_size = LVH_WINDOWS_MAX_OUTPUT_REPORT_SIZE + 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_sizes.report_descriptor_size = LVH_WINDOWS_MAX_REPORT_DESCRIPTOR_SIZE + 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_sizes.name_size = sizeof(request.device.name);
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.name[1] = '\0';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.manufacturer[1] = '\0';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
std::memcpy(request.device.stable_id.data(), "stable", sizeof("stable"));
|
|
request.device.report_sizes.stable_id_size = sizeof("stable") - 1U;
|
|
request.device.stable_id[1] = '\0';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.name[request.device.report_sizes.name_size] = 'x';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.report_descriptor[request.device.report_sizes.report_descriptor_size] = 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, ValidatesMouseCreateFields) {
|
|
auto request = valid_create_request();
|
|
request.device.device_type = LVH_WINDOWS_DEVICE_MOUSE;
|
|
request.device.gamepad_kind = LVH_WINDOWS_GAMEPAD_GENERIC;
|
|
request.device.flags = 0U;
|
|
request.device.hardware_ids.report_id = 0U;
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_MOUSE_INPUT_REPORT_SIZE;
|
|
request.device.report_sizes.output_report_size = 0U;
|
|
std::ranges::fill(request.device.report_descriptor, std::uint8_t {});
|
|
std::ranges::copy(
|
|
lvh::detail::windows::mouse_report_descriptor,
|
|
request.device.report_descriptor.begin()
|
|
);
|
|
request.device.report_sizes.report_descriptor_size =
|
|
static_cast<std::uint32_t>(lvh::detail::windows::mouse_report_descriptor.size());
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request.device.flags = LVH_WINDOWS_GAMEPAD_FLAG_SUPPORTS_RUMBLE;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.flags = 0U;
|
|
request.device.hardware_ids.report_id = 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.hardware_ids.report_id = 0U;
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_MOUSE_INPUT_REPORT_SIZE - 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_MOUSE_INPUT_REPORT_SIZE;
|
|
request.device.report_sizes.output_report_size = 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.report_sizes.output_report_size = 0U;
|
|
request.device.report_descriptor[0] = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, ValidatesKeyboardCreateFields) {
|
|
auto request = valid_create_request();
|
|
request.device.device_type = LVH_WINDOWS_DEVICE_KEYBOARD;
|
|
request.device.gamepad_kind = LVH_WINDOWS_GAMEPAD_GENERIC;
|
|
request.device.flags = 0U;
|
|
request.device.hardware_ids.report_id = 0U;
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_KEYBOARD_INPUT_REPORT_SIZE;
|
|
request.device.report_sizes.output_report_size = LVH_WINDOWS_KEYBOARD_OUTPUT_REPORT_SIZE;
|
|
std::ranges::fill(request.device.report_descriptor, std::uint8_t {});
|
|
std::ranges::copy(
|
|
lvh::detail::windows::keyboard_report_descriptor,
|
|
request.device.report_descriptor.begin()
|
|
);
|
|
request.device.report_sizes.report_descriptor_size =
|
|
static_cast<std::uint32_t>(lvh::detail::windows::keyboard_report_descriptor.size());
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request.device.flags = LVH_WINDOWS_GAMEPAD_FLAG_SUPPORTS_RUMBLE;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.flags = 0U;
|
|
request.device.hardware_ids.report_id = 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.hardware_ids.report_id = 0U;
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_KEYBOARD_INPUT_REPORT_SIZE - 1U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.report_sizes.input_report_size = LVH_WINDOWS_KEYBOARD_INPUT_REPORT_SIZE;
|
|
request.device.report_sizes.output_report_size = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
request.device.report_sizes.output_report_size = LVH_WINDOWS_KEYBOARD_OUTPUT_REPORT_SIZE;
|
|
request.device.report_descriptor[0] = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, RejectsMalformedDestroyFields) {
|
|
const auto valid = valid_destroy_request();
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(valid));
|
|
|
|
auto request = valid;
|
|
request.device.size = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
++request.header.version;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
++request.device.version;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
request.device.driver_device_id = 0U;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
|
|
request = valid;
|
|
std::ranges::fill(request.device.session_token.bytes, std::uint8_t {});
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request));
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, RequiresBoundedActivationStrings) {
|
|
const auto valid = valid_license_request(LvhWindowsBrokerRequestType::activate_license);
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(valid, LvhWindowsBrokerRequestType::activate_license));
|
|
|
|
auto request = valid;
|
|
std::ranges::fill(request.license_key, 'x');
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, LvhWindowsBrokerRequestType::activate_license));
|
|
|
|
request = valid;
|
|
std::ranges::fill(request.instance_name, 'x');
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, LvhWindowsBrokerRequestType::activate_license));
|
|
|
|
request = valid;
|
|
std::ranges::fill(request.license_key, '\0');
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, LvhWindowsBrokerRequestType::activate_license));
|
|
|
|
request = valid;
|
|
std::ranges::fill(request.instance_name, '\0');
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request, LvhWindowsBrokerRequestType::activate_license));
|
|
|
|
request = valid;
|
|
request.license_key[sizeof("test-key")] = 'x';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, LvhWindowsBrokerRequestType::activate_license));
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, RequiresEmptyValidationAndDeactivationPayloads) {
|
|
for (const auto type : {
|
|
LvhWindowsBrokerRequestType::validate_license,
|
|
LvhWindowsBrokerRequestType::deactivate_license,
|
|
}) {
|
|
auto request = valid_license_request(type);
|
|
EXPECT_TRUE(lvh::windows::broker_validation::valid_request(request, type));
|
|
|
|
request.license_key[0] = 'x';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, type));
|
|
|
|
request = valid_license_request(type);
|
|
request.instance_name[0] = 'x';
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, type));
|
|
}
|
|
}
|
|
|
|
TEST(WindowsBrokerValidationTest, RejectsInvalidLicenseHeadersAndRequestTypes) {
|
|
using enum LvhWindowsBrokerRequestType;
|
|
|
|
auto request = valid_license_request(validate_license);
|
|
++request.header.size;
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, validate_license));
|
|
|
|
for (const auto type : {
|
|
status,
|
|
create_device,
|
|
destroy_device,
|
|
}) {
|
|
request = valid_license_request(type);
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, type));
|
|
}
|
|
|
|
const auto unknown_type = static_cast<LvhWindowsBrokerRequestType>(999U);
|
|
request = valid_license_request(unknown_type);
|
|
EXPECT_FALSE(lvh::windows::broker_validation::valid_request(request, unknown_type));
|
|
}
|