Re: [PATCH v3] nvme/069: add a test for multipath cdev lifetime

Shin'ichiro Kawasaki <[email protected]>
Newsgroups org.infradead.lists.linux-nvme,org.kernel.vger.linux-block
Message-ID <alh8G0J1kXHGaOPo@shinmob>
On Jul 15, 2026 / 15:55, John Garry wrote:
> From: John Garry <[email protected]>
> 
> In [0], a fix was proposed for the NS and NS head (multipath) cdev
> lifetime.
> 
> The issue was that fds for the nvme-generic cdev may exist after we tear
> down the nvme-subsystem. Issuing an ioctl on that cdev may expose a
> use-after-free.
> 
> This test recreates the method described in [0] to expose this issue
> for NS head cdev.
> 
> First a fd is created by opening the nvme-generic cdev. Next we tear down
> the nvme-subsystem. Finally we try to issue an ioctl on the cdev - without
> the kernel fix in [0], this should trigger a KASAN warn.
> 
> [0] https://lore.kernel.org/linux-nvme/[email protected]/T/#me118851584fbcbb960795d0f04e2262e5a295613
> 
> Signed-off-by: John Garry <[email protected]>

Hi John, thank you for the patch. I ran the test case and confirmed that it
triggers the KASAN. I also confirmed the kernel patch [0] avoids the KASAN.
Looks good from test run point of view.

Please find some review comments in-line.

...

> diff --git a/common/nvme b/common/nvme
> index 15e9c3f..902a33f 100644
> --- a/common/nvme
> +++ b/common/nvme
> @@ -300,6 +300,26 @@ _nvme_wait_subsys_removed() {
>  	done
>  }
>  
> +_nvme_find_subsys_nvme_generic() {
> +	local subsysnqn="$def_subsysnqn"

Nit: though it is not strictly required, I suggest to declare local variables.

	local subsyspath _subsysnqn subsyspathbase nvmegenericbase devicelink

> +
> +	for subsyspath in /sys/class/nvme-subsystem/*; do
> +		_subsysnqn=$(cat "${subsyspath}/subsysnqn" 2> /dev/null)
> +		if [ "$subsysnqn" == "$_subsysnqn" ]; then
> +			subsyspathbase="$(basename "$subsyspath")"
> +			for nvmegeneric in /sys/class/nvme-generic/*; do
> +				nvmegenericbase="$(basename "$nvmegeneric")"
> +				devicelink="$(readlink "$nvmegeneric/device")"
> +				if [[ "$devicelink" =~ $subsyspathbase ]]; then
> +					echo "$nvmegenericbase"
> +					break
> +				fi
> +			done
> +			break
> +		fi
> +	done
> +}
> +
>  _nvme_connect_subsys() {
>  	local subsysnqn="$def_subsysnqn"
>  	local hostnqn="$def_hostnqn"

...

> diff --git a/src/nvme-delay-ioctl.c b/src/nvme-delay-ioctl.c
> new file mode 100644
> index 0000000..854b702
> --- /dev/null
> +++ b/src/nvme-delay-ioctl.c
> @@ -0,0 +1,69 @@
> +// SPDX-License-Identifier: GPL-3.0+

Copyright is missing here.

...

> diff --git a/tests/nvme/069 b/tests/nvme/069
> new file mode 100755
> index 0000000..89f048c
> --- /dev/null
> +++ b/tests/nvme/069
> @@ -0,0 +1,61 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-3.0+
> +# Copyright (C) 2026 John Garry
> +#
> +# Test NVMe multipath nvme-generic cdev lifetime behaves as expected.
> +
> +. tests/nvme/rc
> +
> +DESCRIPTION="NVMe multipath cdev lifetime test"
> +CHECK_DMESG=1

This test case completes within a few seconds, so I suggest,

QUICK=1

> +
> +requires() {
> +	_nvme_requires
> +	_have_loop
> +	_have_module_param_value nvme_core multipath Y
> +	_require_nvme_trtype_is_fabrics
> +	_have_kernel_options KASAN
> +}
> +
> +set_conditions() {
> +	_set_nvme_trtype "$@"
> +}
> +
> +test() {
> +	echo "Running ${TEST_NAME}"
> +
> +	_setup_nvmet
> +
> +	local nvmedev

Nit: I suggest to declare nvmegeneric also:

	local nvmedev nvmegeneric

> +	local loops=0
> +	local quit=0
> +	_nvmet_target_setup
> +
> +	_nvme_connect_subsys
> +
> +	nvmedev=$(_find_nvme_dev "${def_subsysnqn}")
> +
> +	nvmegeneric=$(_nvme_find_subsys_nvme_generic)
> +
> +	trap 'quit=1' USR2
> +	# nvme-delay-ioctl will open the nvme-generic cdev, wait for teardown,
> +	# and then issue an ioctl.
> +	# We tear down the nvme-subsystem in the foreground so we can catch
> +	# if the ioctl triggers a use-after-free KASAN warn.
> +	src/nvme-delay-ioctl "/dev/$nvmegeneric" &
> +
> +	while [ "$quit" -ne 1 ]; do
> +		sleep 0.1
> +		((loops++))
> +		if [[ "$loops" == "100" ]]; then
> +			echo "no signal"
> +			break
> +		fi
> +	done
> +
> +	_nvme_disconnect_ctrl "${nvmedev}"
> +	_nvmet_target_cleanup
> +	wait < <(jobs -p)

My understanding is that wait command does not receive pids from stdin. Then I
think "jobs -p" output is ignored here. I guess you meant,

	wait $(jobs -p)

> +
> +	echo "Test complete"
> +}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.