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


Reply via email to