Summary of changes: - Add tools/testing/selftests/drivers/misc/vmw_zerocopy: a kselftest_harness test covering the ioctl input-validation paths, plus its Makefile and config fragment - Add the target to tools/testing/selftests/Makefile
Cover the input-validation paths of the vmw_zerocopy ioctl added earlier in this series: an unknown ioctl number, a bad message pointer, an unknown message type, a non-zero reserved word, neither buffer supplied, zero-length and oversized data and metadata buffers, a wrapping address+length, a wrapping PAGE_ALIGN() of the end address, an unmapped buffer, and zero-length and oversized raw payloads. These are all rejected before any datagram is sent, so they need no hypervisor peer. Two further tests submit a well-formed message and assert only that any failure is not a validation error, which holds whether or not a peer is listening; they also check that repeated sends do not depend on per-call setup surviving. The whole test therefore runs on any guest where the module loads, and skips cleanly when /dev/vmw_zc is absent so it does not fail on kernels built without CONFIG_VMW_ZC. Signed-off-by: Rishi Chhibber <[email protected]> Reviewed-by: Alexey Makhalov <[email protected]> Reviewed-by: Vishnu Dasa <[email protected]> --- tools/testing/selftests/Makefile | 1 + .../drivers/misc/vmw_zerocopy/.gitignore | 1 + .../drivers/misc/vmw_zerocopy/Makefile | 20 ++ .../drivers/misc/vmw_zerocopy/config | 2 + .../misc/vmw_zerocopy/test_vmw_zerocopy.c | 276 ++++++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/config create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 2d960626750e..325723e92bf3 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -20,6 +20,7 @@ TARGETS += devices/error_logs TARGETS += devices/probe TARGETS += dmabuf-heaps TARGETS += drivers/dma-buf +TARGETS += drivers/misc/vmw_zerocopy TARGETS += drivers/ntsync TARGETS += drivers/s390x/uvdevice TARGETS += drivers/net diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore b/tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore new file mode 100644 index 000000000000..68941a8c0553 --- /dev/null +++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore @@ -0,0 +1 @@ +test_vmw_zerocopy diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile b/tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile new file mode 100644 index 000000000000..195826f72f9c --- /dev/null +++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +include ../../../../../build/Build.include + +UNAME_M := $(shell uname -m) + +ifeq ($(filter $(UNAME_M),x86_64 i686 i386),) +nothing: +.PHONY: all clean run_tests install +.SILENT: +else + +TEST_GEN_PROGS := test_vmw_zerocopy + +top_srcdir ?= ../../../../../.. + +CFLAGS += -Wall -Werror $(KHDR_INCLUDES) + +include ../../../lib.mk + +endif diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/config b/tools/testing/selftests/drivers/misc/vmw_zerocopy/config new file mode 100644 index 000000000000..03e4a430c5ff --- /dev/null +++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/config @@ -0,0 +1,2 @@ +CONFIG_VMWARE_VMCI=m +CONFIG_VMW_ZC=m diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c b/tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c new file mode 100644 index 000000000000..af8035c4fa0f --- /dev/null +++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * selftest for the VMware zero-copy buffer sharing UAPI + * + * Copyright (c) 2026 Broadcom. All Rights Reserved. The term + * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * Every input check in the driver runs before the message reaches the + * transport, so all of the tests below are meaningful without a hypervisor + * peer listening: they assert that bad input is rejected with the documented + * errno. Tests that would require a live peer only assert "not one of the + * input-validation errnos", since the transport result depends on the host. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <linux/vmw_zerocopy.h> + +#include "../../../kselftest_harness.h" + +#define VMW_ZC_PATH "/dev/" VMW_ZC_DEVICE_NAME + +/* Driver-private, but fixed by the UAPI's documented buffer limits. */ +#define VMW_ZC_TEST_MAX_BUFFER 65536 + +FIXTURE(vmw_zc) { + int fd; + void *data; + void *metadata; + size_t page_size; +}; + +FIXTURE_SETUP(vmw_zc) +{ + self->page_size = (size_t)getpagesize(); + + self->fd = open(VMW_ZC_PATH, O_RDWR); + if (self->fd < 0) + SKIP(return, "cannot open %s: %s", VMW_ZC_PATH, + strerror(errno)); + + self->data = mmap(NULL, self->page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, self->data); + + self->metadata = mmap(NULL, self->page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, self->metadata); + + memset(self->data, 0xa5, self->page_size); +} + +FIXTURE_TEARDOWN(vmw_zc) +{ + if (self->data) + munmap(self->data, self->page_size); + if (self->metadata) + munmap(self->metadata, self->page_size); + if (self->fd >= 0) + close(self->fd); +} + +/* An unknown ioctl number must be rejected, not silently accepted. */ +TEST_F(vmw_zc, unknown_ioctl) +{ + struct vmw_zc_guest_message msg = { }; + + EXPECT_EQ(-1, ioctl(self->fd, _IOW(VMW_ZC_IOCTL_MAGIC, 0x7f, + struct vmw_zc_guest_message), &msg)); + EXPECT_EQ(ENOTTY, errno); +} + +TEST_F(vmw_zc, bad_message_pointer) +{ + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, NULL)); + EXPECT_EQ(EFAULT, errno); +} + +TEST_F(vmw_zc, unknown_message_type) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = 0xffff; + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +/* + * The destination is owned by the kernel: @reserved is not an address and a + * caller that treats it as one (e.g. a binary built against the older header + * where this word was peer_id) must fail loudly rather than be misrouted. + */ +TEST_F(vmw_zc, reserved_must_be_zero) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.reserved = 1; + msg.u.data.buffer = (__u64)(uintptr_t)self->data; + msg.u.data.buffer_length = 64; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +TEST_F(vmw_zc, neither_buffer_nor_metadata) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +TEST_F(vmw_zc, zero_length_buffer) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.buffer = (__u64)(uintptr_t)self->data; + msg.u.data.buffer_length = 0; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +TEST_F(vmw_zc, oversized_buffer) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.buffer = (__u64)(uintptr_t)self->data; + msg.u.data.buffer_length = VMW_ZC_TEST_MAX_BUFFER + 1; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +TEST_F(vmw_zc, oversized_metadata) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.metadata = (__u64)(uintptr_t)self->metadata; + msg.u.data.metadata_length = VMW_ZC_TEST_MAX_BUFFER + 1; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +/* + * A near-top address must be rejected by the range check before any page-count + * arithmetic runs, whether it wraps on start + length or only on the + * subsequent page alignment. + */ +TEST_F(vmw_zc, address_length_overflow) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.buffer = ~(__u64)0 - 15; + msg.u.data.buffer_length = 4096; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EFAULT, errno); +} + +/* + * The page-alignment-only wrap: start + length does not overflow, but rounding + * the end up to a page boundary would, which previously yielded nr_pages == 1. + */ +TEST_F(vmw_zc, address_page_align_overflow) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.buffer = ~(__u64)0 - 0x7ff; + msg.u.data.buffer_length = 0x800; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EFAULT, errno); +} + +TEST_F(vmw_zc, unmapped_buffer) +{ + struct vmw_zc_guest_message msg = { }; + void *gap; + + gap = mmap(NULL, self->page_size, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, gap); + ASSERT_EQ(0, munmap(gap, self->page_size)); + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.buffer = (__u64)(uintptr_t)gap; + msg.u.data.buffer_length = 64; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EFAULT, errno); +} + +TEST_F(vmw_zc, zero_length_raw) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_RAW; + msg.u.raw_buffer.raw_len = 0; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +TEST_F(vmw_zc, oversized_raw) +{ + struct vmw_zc_guest_message msg = { }; + + msg.message_type = VMW_ZC_MSG_RAW; + msg.u.raw_buffer.raw_len = VMW_ZC_MAX_RAW_BUFFER_LEN + 1; + + EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg)); + EXPECT_EQ(EINVAL, errno); +} + +/* + * A well-formed send passes every input check, so it must not fail with one + * of the validation errnos. Whether it ultimately succeeds depends on a peer + * being present, which this test cannot assume. + */ +TEST_F(vmw_zc, well_formed_send_passes_validation) +{ + struct vmw_zc_guest_message msg = { }; + int rc; + + msg.message_type = VMW_ZC_MSG_USER_BUFFER; + msg.u.data.buffer = (__u64)(uintptr_t)self->data; + msg.u.data.buffer_length = 64; + msg.u.data.metadata = (__u64)(uintptr_t)self->metadata; + msg.u.data.metadata_length = 64; + + rc = ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg); + if (rc < 0) { + EXPECT_NE(EINVAL, errno); + EXPECT_NE(EFAULT, errno); + EXPECT_NE(ENOTTY, errno); + TH_LOG("send returned %s; a hypervisor peer may not be present", + strerror(errno)); + } +} + +/* Repeated sends must not depend on any per-call setup surviving. */ +TEST_F(vmw_zc, repeated_raw_sends) +{ + struct vmw_zc_guest_message msg = { }; + int i; + + msg.message_type = VMW_ZC_MSG_RAW; + msg.u.raw_buffer.raw_len = VMW_ZC_MAX_RAW_BUFFER_LEN; + memset(msg.u.raw_buffer.raw_buffer, 0x5a, + sizeof(msg.u.raw_buffer.raw_buffer)); + + for (i = 0; i < 16; i++) { + if (ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg) < 0) { + EXPECT_NE(EINVAL, errno); + EXPECT_NE(ENOTTY, errno); + } + } +} + +TEST_HARNESS_MAIN -- 2.52.0

