All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andrea Parri <parri.andrea@gmail.com>
To: Shin'ichiro Kawasaki <shinichiro.kawasaki@wdc.com>,
	linux-block@vger.kernel.org
Cc: Andrea Parri <parri.andrea@gmail.com>,
	Christoph Hellwig <hch@lst.de>, Keith Busch <kbusch@kernel.org>,
	Sagi Grimberg <sagi@grimberg.me>,
	Chaitanya Kulkarni <kch@nvidia.com>,
	Daniel Wagner <dwagner@suse.de>,
	linux-nvme@lists.infradead.org
Subject: [PATCH blktests 3/4] nvme/072: test nvmet zone report initialization and length
Date: Wed, 14 Oct 2026 19:00:40 +0200	[thread overview]
Message-ID: <20261014170046.2516-4-parri.andrea@gmail.com> (raw)
In-Reply-To: <20261014170046.2516-1-parri.andrea@gmail.com>

Export a zoned null_blk device through the NVMe target and verify that
Zone Management Receive zeroes reserved and unused report bytes and
rejects a transfer length inconsistent with NUMD. The test runs on every
configured transport, with RDMA restricted to 4 KiB page kernels so that
the preparatory write exceeds the default inline-data limit. Add a small
passthrough helper because nvme-cli does not expose the reserved report
bytes.

The test fails on the unpatched kernel and passes with the fix applied.

Assisted-by: LLM
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
 src/.gitignore         |   1 +
 src/Makefile           |   1 +
 src/nvme-zone-report.c | 165 +++++++++++++++++++++++++++++++++++++++++
 tests/nvme/072         |  68 +++++++++++++++++
 tests/nvme/072.out     |   2 +
 5 files changed, 237 insertions(+)
 create mode 100644 src/nvme-zone-report.c
 create mode 100755 tests/nvme/072
 create mode 100644 tests/nvme/072.out

diff --git a/src/.gitignore b/src/.gitignore
index 2429da9d9cf44..8119476416937 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -19,3 +19,4 @@
 /nvme-passthru-admin-uring
 /nvme-delay-ioctl
 /nvme-sentinel-read
+/nvme-zone-report
diff --git a/src/Makefile b/src/Makefile
index 78433f0762b4a..7c4fd600ace1d 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -25,6 +25,7 @@ C_TARGETS := \
 	nvme-delay-ioctl \
 	nvme-passthrough-meta \
 	nvme-sentinel-read \
+	nvme-zone-report \
 	ioctl-lbmd-query \
 	nbdsetsize \
 	openclose \
diff --git a/src/nvme-zone-report.c b/src/nvme-zone-report.c
new file mode 100644
index 0000000000000..951b1d03d4a75
--- /dev/null
+++ b/src/nvme-zone-report.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-3.0+
+// Copyright (C) 2026 Andrea Parri
+
+/* Check that unused bytes in an NVMe zone report are zeroed. */
+#define _GNU_SOURCE
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/nvme_ioctl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define REPORT_LEN		4096
+/*
+ * Larger than the default RDMA in-capsule data size on 4 KiB page kernels.
+ * nvme/072 skips RDMA on other page sizes, where this write could stay
+ * inline rather than use buffers allocated and freed per command.
+ */
+#define POISON_LEN		8192
+#define WRITE_OPCODE		0x01
+#define ZONE_REPORT_OPCODE	0x7a
+#define PARTIAL_REPORT		(1U << 16)
+#define ZONE_REPORT_HEADER	64
+#define ZONE_DESC_LEN		64
+#define ZONE_START_OFFSET	16
+#define NR_ATTEMPTS		64
+
+static int poison_target(int fd, unsigned int iteration)
+{
+	unsigned char data[POISON_LEN];
+	struct nvme_passthru_cmd cmd = {
+		.opcode = WRITE_OPCODE,
+		.nsid = 1,
+		.addr = (uintptr_t)data,
+		.data_len = sizeof(data),
+		.cdw10 = iteration * (POISON_LEN / 512),
+		.cdw12 = POISON_LEN / 512 - 1,
+	};
+
+	memset(data, 0x5a, sizeof(data));
+	return ioctl(fd, NVME_IOCTL_IO_CMD, &cmd);
+}
+
+static uint64_t read_le64(const unsigned char *p)
+{
+	uint64_t value;
+
+	memcpy(&value, p, sizeof(value));
+	return le64toh(value);
+}
+
+int main(int argc, char **argv)
+{
+	unsigned char *report;
+	unsigned long long slba;
+	char *end;
+	void *p;
+	int fd, i, j, ret, rc = 0;
+
+	if (argc != 3) {
+		fprintf(stderr, "usage: %s /dev/nvmeXnY last-zone-slba\n",
+			argv[0]);
+		return 2;
+	}
+
+	errno = 0;
+	slba = strtoull(argv[2], &end, 0);
+	if (errno || *end) {
+		fprintf(stderr, "invalid SLBA: %s\n", argv[2]);
+		return 2;
+	}
+
+	/*
+	 * Page-align the report so that the kernel maps it for the transfer
+	 * instead of bouncing it: a bounce buffer is zero-filled, which would
+	 * hide the sentinel and any bytes the target left unwritten.
+	 */
+	ret = posix_memalign(&p, 4096, REPORT_LEN);
+	if (ret) {
+		fprintf(stderr, "posix_memalign: %s\n", strerror(ret));
+		return 2;
+	}
+	report = p;
+
+	fd = open(argv[1], O_RDWR);
+	if (fd < 0) {
+		perror(argv[1]);
+		free(report);
+		return 2;
+	}
+
+	for (i = 0; i < NR_ATTEMPTS; i++) {
+		struct nvme_passthru_cmd cmd = {
+			.opcode = ZONE_REPORT_OPCODE,
+			.nsid = 1,
+			.addr = (uintptr_t)report,
+			.data_len = REPORT_LEN,
+			.cdw10 = (uint32_t)slba,
+			.cdw11 = (uint32_t)(slba >> 32),
+			.cdw12 = REPORT_LEN / 4 - 1,
+			.cdw13 = PARTIAL_REPORT,
+		};
+		unsigned int nonzero = 0, stale = 0, poisoned = 0;
+		unsigned char sentinel = i & 1 ? 0xa5 : 0x3c;
+
+		ret = poison_target(fd, i);
+		if (ret) {
+			if (ret < 0)
+				perror("poison_target");
+			else
+				fprintf(stderr, "poison write status: %#x\n", ret);
+			rc = 2;
+			goto out;
+		}
+
+		memset(report, sentinel, REPORT_LEN);
+		ret = ioctl(fd, NVME_IOCTL_IO_CMD, &cmd);
+		if (ret) {
+			if (ret < 0)
+				perror("NVME_IOCTL_IO_CMD");
+			else
+				fprintf(stderr, "NVMe status: %#x\n", ret);
+			rc = 2;
+			goto out;
+		}
+
+		if (read_le64(report) != 1 ||
+		    read_le64(report + ZONE_REPORT_HEADER +
+			      ZONE_START_OFFSET) != slba) {
+			fprintf(stderr, "unexpected zone report header or SLBA\n");
+			rc = 1;
+			goto out;
+		}
+
+		for (j = 8; j < REPORT_LEN; j++) {
+			if (j >= ZONE_REPORT_HEADER &&
+			    j < ZONE_REPORT_HEADER + ZONE_DESC_LEN)
+				continue;
+			if (!report[j])
+				continue;
+			nonzero++;
+			if (report[j] == sentinel)
+				stale++;
+			if (report[j] == 0x5a)
+				poisoned++;
+		}
+
+		fprintf(stderr, "attempt %d: %u nonzero bytes "
+			"(%u poison, %u sentinel)\n", i + 1, nonzero,
+			poisoned, stale);
+		if (nonzero) {
+			rc = 1;
+			goto out;
+		}
+	}
+
+out:
+	close(fd);
+	free(report);
+	return rc;
+}
diff --git a/tests/nvme/072 b/tests/nvme/072
new file mode 100755
index 0000000000000..c7bcf93ab4d20
--- /dev/null
+++ b/tests/nvme/072
@@ -0,0 +1,68 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0+
+# Copyright (C) 2026 Andrea Parri
+#
+# Check that NVMe target zone reports are fully initialized and that their
+# transfer length matches NUMD.
+#
+# Regression test for patch "nvmet: zns: don't leak stale memory in zone
+# management receive".
+
+. tests/nvme/rc
+. common/null_blk
+
+DESCRIPTION="check NVMe target zone report initialization and length"
+QUICK=1
+
+requires() {
+	_nvme_requires
+	_have_loop
+	_require_kernel_nvme_target
+	_have_module null_blk
+	_have_kernel_options BLK_DEV_ZONED
+	_have_src_program nvme-zone-report
+
+	# RDMA defaults to PAGE_SIZE bytes of inline data. The helper's
+	# 8 KiB preparatory write must exceed that limit.
+	if [[ "$nvme_trtype" == "rdma" ]]; then
+		_page_size_equals 4096
+	fi
+}
+
+set_conditions() {
+	_set_nvme_trtype "$@"
+}
+
+test() {
+	echo "Running ${TEST_NAME}"
+
+	local block_size dev last_sector ret sectors slba zone_sectors
+
+	_setup_nvmet
+	_register_test_cleanup _cleanup_nvmet_zoned_target
+	if ! _nvmet_zoned_target_setup; then
+		return 1
+	fi
+	dev="/dev/$(_find_nvme_ns "$def_subsys_uuid")"
+
+	sectors=$(cat /sys/block/nullb0/size)
+	zone_sectors=$(cat /sys/block/nullb0/queue/chunk_sectors)
+	block_size=$(cat /sys/block/nullb0/queue/logical_block_size)
+	last_sector=$(((sectors - 1) / zone_sectors * zone_sectors))
+	slba=$((last_sector * 512 / block_size))
+
+	src/nvme-zone-report "$dev" "$slba" >>"$FULL" 2>&1
+	ret=$?
+	case $ret in
+	0) ;;
+	1) echo "zone report contained stale data" ;;
+	*) echo "zone report checker failed" ;;
+	esac
+
+	_check_zone_report "$dev" 4096 1023 0
+	_check_zone_report "$dev" 8192 1023 15
+
+	_nvmet_zoned_target_cleanup
+
+	echo "Test complete"
+}
diff --git a/tests/nvme/072.out b/tests/nvme/072.out
new file mode 100644
index 0000000000000..3f765b30a22b0
--- /dev/null
+++ b/tests/nvme/072.out
@@ -0,0 +1,2 @@
+Running nvme/072
+Test complete
-- 
2.53.0



  parent reply	other threads:[~2026-10-14 17:01 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 17:00 [PATCH blktests 0/4] nvme: add nvmet zone report and FC regression tests Andrea Parri
2026-10-14 17:00 ` [PATCH blktests 1/4] nvme/rc: add zoned null_blk target helpers Andrea Parri
2026-10-20 10:31   ` Shin'ichiro Kawasaki
2026-10-14 17:00 ` [PATCH blktests 2/4] nvme/071: test that nvmet sends no data for a failed read Andrea Parri
2026-10-20 10:35   ` Shin'ichiro Kawasaki
2026-10-20 15:17     ` Andrea Parri
2026-10-14 17:00 ` Andrea Parri [this message]
2026-10-20 10:38   ` [PATCH blktests 3/4] nvme/072: test nvmet zone report initialization and length Shin'ichiro Kawasaki
2026-10-14 17:00 ` [PATCH blktests 4/4] nvme/073: test that nvmet rejects a zone report NUMD that wraps Andrea Parri
2026-10-20 10:41 ` [PATCH blktests 0/4] nvme: add nvmet zone report and FC regression tests Shin'ichiro Kawasaki
2026-10-20 15:18   ` Andrea Parri

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261014170046.2516-4-parri.andrea@gmail.com \
    --to=parri.andrea@gmail.com \
    --cc=dwagner@suse.de \
    --cc=hch@lst.de \
    --cc=kbusch@kernel.org \
    --cc=kch@nvidia.com \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-nvme@lists.infradead.org \
    --cc=sagi@grimberg.me \
    --cc=shinichiro.kawasaki@wdc.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.