Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Charlie Jenkins via B4 Relay"
	<devnull+thecharlesjenkins.gmail.com@kernel.org>
Cc: kvm@vger.kernel.org
Subject: Re: [PATCH v2 10/16] KVM: riscv: selftests: Add mmio test
Date: Mon, 22 Jun 2026 04:18:20 +0000	[thread overview]
Message-ID: <20260622041820.DFC491F00A3E@smtp.kernel.org> (raw)
In-Reply-To: <20260621-riscv_insn_table-v2-10-7f0810ee2274@gmail.com>

Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:

New issues:
- [High] The test pattern for MMIO writes uses a 32-bit literal, causing it to evaluate to 0 when shifted on 64-bit architectures.
- [Medium] The compressed store instruction `c.sdsp` is entirely omitted from the test suite.
- [Medium] Standard 16-bit loads (`lh` and `lhu`) are erroneously restricted to 64-bit RISC-V builds.
- [Medium] The KVM device file descriptor returned by `kvm_create_device` is leaked, which also leaks the KVM VM.
- [Medium] The selftest fails to gracefully skip when KVM_DEV_TYPE_TEST is not supported by the kernel.

Pre-existing issues:
- [Critical] The KVM MMIO test device contains a heap buffer overflow and out-of-bounds read vulnerability due to a mismatch between the registered device size and its internal cache array capacity.
- [Critical] The mmio_test device fails to unregister itself from the KVM MMIO bus upon release, leading to a Use-After-Free vulnerability.
- [High] `mmio_test_create()` registers the device to the KVM IO bus without holding the required `kvm->slots_lock`.
- [High] The error cleanup path in `kvm_init()` unwinds initializations in the wrong order.
- [High] The return value of `kvm_io_bus_register_dev()` is ignored, potentially leaking an unregistered device.
- [High] The `struct kvm_device` allocated for the test device is permanently leaked.
--

commit 91109e879d023e4a9b60888f79545187037508bf
Author: Charlie Jenkins <thecharlesjenkins@gmail.com>
KVM: riscv: selftests: Add mmio test

This commit adds a new MMIO selftest for KVM on RISC-V. It introduces test
cases for various read and write instructions, including compressed
instructions, to ensure KVM properly emulates MMIO accesses.

> diff --git a/tools/testing/selftests/kvm/riscv/mmio_test.c b/tools/testing/selftests/kvm/riscv/mmio_test.c
> new file mode 100644
> index 0000000000000..9726860a269a3
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/riscv/mmio_test.c
> @@ -0,0 +1,184 @@
[ ... ]
> +test_standard_read(4, lw, lw, "")
> +test_standard_read(4, c.lw, c_lw, ".option arch,+c\n")
> +test_sp_read(4, c.lwsp, c_lwsp)
> +
> +#if __riscv_xlen == 64
> +test_standard_read(2, lh, lh, "")
> +test_standard_read(2, lhu, lhu, "")

[Severity: Medium]
Are lh and lhu intentionally restricted to 64-bit architectures here?

Since these are standard 16-bit loads in the RV32I base instruction set,
should they be moved outside the __riscv_xlen == 64 check so they are also
tested on 32-bit platforms?

> +test_standard_read(4, lwu, lwu, "")
> +test_standard_read(8, ld, ld, "")
[ ... ]
> +#define test_standard_write(len, write, read, name, options)					\
> +static void test_##name(void)									\
> +{												\
> +	unsigned long result;									\
> +	unsigned long reference = (0x55555555UL >> ((sizeof(long) - len) * 8));			\

[Severity: High]
Does this bitwise shift evaluate to 0 on 64-bit systems?

When __riscv_xlen == 64, sizeof(long) is 8. For a 32-bit store (len=4),
this shifts 0x55555555UL right by 32 bits, resulting in 0.

For len=1 and len=2, it shifts by 56 and 48 bits, also resulting in 0.

Could this mask sign-extension or truncation issues by only ever verifying
a value of 0?

Should this use a 64-bit literal like 0x5555555555555555ULL instead?

> +	/* Check that we can write and then read the same value. */				\
> +	asm volatile (										\
[ ... ]
> +#define test_sp_write(len, write, read, name)							\
> +static void test_##name(void)									\
> +{												\
> +	unsigned long result;									\
> +	unsigned long tmp;									\
> +	unsigned long reference = (0x55555555UL >> ((sizeof(long) - len) * 8));			\

[Severity: High]
Similar to test_standard_write, does this shift evaluate to 0 on 64-bit
systems for compressed stack-pointer relative writes?

> +	/* Check that we can write and then read the same value. */				\
[ ... ]
> +static void run(void *guest_code, char *instruction)
> +{
> +	struct ucall uc;
> +	struct kvm_vm *vm;
> +	struct kvm_vcpu *vcpu;
> +
> +	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
> +	kvm_create_device(vm, KVM_DEV_TYPE_TEST);

[Severity: Medium]
Does this test need to gracefully skip if KVM_DEV_TYPE_TEST is unavailable?

kvm_create_device() uses TEST_ASSERT internally and will abort the test
if the kernel lacks support.

Would it be safer to check support with __kvm_create_device() and use
TEST_REQUIRE() to exit with a skip code in CI environments running older
kernels?

[Severity: Medium]
Does this leak the KVM device file descriptor returned by kvm_create_device()?

Since the file descriptor holds a reference to the KVM VM (kvm_get_kvm),
failing to close it will prevent the VM from being destroyed when
kvm_vm_free(vm) is called.

Since run() iterates for multiple instructions, will this accumulate file
descriptor and kernel memory leaks?

> +
> +	virt_map(vm, (unsigned long)MMIO_TEST_REGION, MMIO_TEST_REGION, 1);
[ ... ]
> +void test_mmio_read_sign_extension(void)
> +{
> +	run(test_lb, "lb");
[ ... ]
> +	run(test_c_lwsp, "c.lwsp");
> +
> +#if __riscv_xlen == 64
> +	run(test_lh, "lh");
> +	run(test_lhu, "lhu");

[Severity: Medium]
Are the executions for lh and lhu also intentionally restricted to 64-bit
builds here?

> +	run(test_lwu, "lwu");
[ ... ]
> +void test_mmio_write(void)
> +{
> +	run(test_sb, "sb");
> +	run(test_sh, "sh");
> +	run(test_sw, "sw");
> +	run(test_c_sw, "c.sw");
> +	run(test_c_swsp, "c.swsp");
> +
> +#if __riscv_xlen == 64
> +	run(test_sd, "sd");
> +	run(test_c_sd, "c.sd");

[Severity: Medium]
Is the compressed store instruction c.sdsp omitted from the 64-bit write
tests?

The commit message notes that all emulated load and store instructions are
checked, but c.sdsp appears to be missing an instantiation via
test_sp_write and execution via run(test_c_sdsp, "c.sdsp").

> +#endif
> +}

While reviewing how this selftest interacts with KVM_DEV_TYPE_TEST, I
noticed several pre-existing issues in the test device implementation itself
(virt/kvm/mmio_test.c and virt/kvm/kvm_main.c). I have included them here
for completeness:

virt/kvm/mmio_test.c:mmio_test_create():
    mmio_test->size = 0x16;

[Severity: Critical]
This is a pre-existing issue, but does mmio_test->size exceed the capacity
of the internal cache array?

The internal cache array is sized at 16 bytes (char cache[16];), but the
size field is initialized to 0x16 (22 bytes).

When a guest accesses an MMIO address near the end of the registered region,
the addr - start + len calculation inside mmio_write() and mmio_read()
can exceed 16.

Can this allow an unprivileged guest to trigger an out-of-bounds memcpy(),
leading to host heap corruption or information leaks?


virt/kvm/mmio_test.c:mmio_test_release():
static void mmio_test_release(struct kvm_device *dev)
{
    kfree(dev->private);
}

[Severity: Critical]
This is a pre-existing issue, but is there a use-after-free vulnerability here?

The release callback frees the backing dev->private structure, but it does
not call kvm_io_bus_unregister_dev().

The KVM MMIO bus retains a dangling pointer to the freed structure.

Will any subsequent MMIO access to the registered range by an active vCPU
dereference this freed pointer?


[Severity: High]
This is a pre-existing issue, but does mmio_test_release() leak the
struct kvm_device?

kvm_ioctl_create_device() dynamically allocates the struct kvm_device
using kzalloc().

The device implementation's .release or .destroy callback is responsible
for freeing this memory, but it appears to be omitted here.

Does this cause a permanent leak every time a test device is closed?


virt/kvm/mmio_test.c:mmio_test_create():
    kvm_io_bus_register_dev(dev->kvm, KVM_MMIO_BUS, mmio_test->start,
                            mmio_test->size, &mmio_test->dev);

[Severity: High]
This is a pre-existing issue, but is kvm_io_bus_register_dev() called
without holding the required kvm->slots_lock?

mmio_test_create() is invoked via kvm_ioctl_create_device(), which only
holds kvm->lock.

kvm_io_bus_register_dev() strictly requires kvm->slots_lock and enforces
it via lockdep.

Will this cause an immediate lockdep splat and a data race against concurrent
MMIO bus updates?


[Severity: High]
This is a pre-existing issue, but is the return value of
kvm_io_bus_register_dev() safely ignored?

The function can fail (e.g., due to -ENOMEM or hitting NR_IOBUS_DEVS).

Since mmio_test_create() ignores the return value and unconditionally
returns 0, will userspace receive a valid fd for a device that was never
actually attached to the MMIO bus?


virt/kvm/kvm_main.c:kvm_init():
err_gmem:
    kvm_vfio_ops_exit();
err_vfio:
    kvm_mmio_test_ops_exit();
err_mmio_test:
    kvm_async_pf_deinit();

[Severity: High]
This is a pre-existing issue, but are the error unwinding labels interleaved
incorrectly?

kvm_init() initializes components in the order: VFIO, MMIO_TEST, and GMEM.

If kvm_gmem_init() fails, it jumps to err_gmem which executes
kvm_vfio_ops_exit() followed by kvm_mmio_test_ops_exit(), which is the
reverse of the correct unwinding order.

Furthermore, if kvm_vfio_ops_init() fails, it jumps to err_vfio and
attempts to exit mmio_test (which hasn't been initialized yet).

If kvm_mmio_test_ops_init() fails, it skips unwinding VFIO entirely.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260621-riscv_insn_table-v2-0-7f0810ee2274@gmail.com?part=10

  reply	other threads:[~2026-06-22  4:18 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-22  4:01 [PATCH v2 00/16] riscv: Generate riscv instruction functions Charlie Jenkins via B4 Relay
2026-06-22  4:01 ` [PATCH v2 01/16] riscv: Introduce instruction table generation Charlie Jenkins via B4 Relay
2026-06-22  4:13   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 02/16] riscv: alternatives: Use generated instruction headers for patching code Charlie Jenkins via B4 Relay
2026-06-22  4:28   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 03/16] riscv: kgdb: Use generated instruction headers Charlie Jenkins via B4 Relay
2026-06-22  4:01 ` [PATCH v2 04/16] riscv: Add kprobes instruction simulation KUnit Charlie Jenkins via B4 Relay
2026-06-22  4:19   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 05/16] riscv: kprobes: Use generated instruction headers Charlie Jenkins via B4 Relay
2026-06-22  4:01 ` [PATCH v2 06/16] riscv: cfi: " Charlie Jenkins via B4 Relay
2026-06-22  4:35   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 07/16] riscv: Use generated instruction headers for misaligned loads/stores Charlie Jenkins via B4 Relay
2026-06-22  4:18   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 08/16] riscv: kvm: Use generated instruction headers for csr code Charlie Jenkins via B4 Relay
2026-06-22  4:18   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 09/16] KVM: device: Add test device Charlie Jenkins via B4 Relay
2026-06-22  4:13   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 10/16] KVM: riscv: selftests: Add mmio test Charlie Jenkins via B4 Relay
2026-06-22  4:18   ` sashiko-bot [this message]
2026-06-22  4:01 ` [PATCH v2 11/16] riscv: kvm: Use generated instruction headers for mmio emulation Charlie Jenkins via B4 Relay
2026-06-22  4:27   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 12/16] riscv: kvm: Add emulated test csr Charlie Jenkins via B4 Relay
2026-06-22  4:23   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 13/16] KVM: riscv: selftests: Add csr emulation test Charlie Jenkins via B4 Relay
2026-06-22  4:24   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 14/16] riscv: kvm: Use generated instruction headers for csr emulation Charlie Jenkins via B4 Relay
2026-06-22  4:30   ` sashiko-bot
2026-06-22  4:01 ` [PATCH v2 15/16] riscv: kexec: Use generated instruction headers for kexec relocations Charlie Jenkins via B4 Relay
2026-06-22  4:01 ` [PATCH v2 16/16] riscv: Remove unused instruction headers Charlie Jenkins via B4 Relay

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=20260622041820.DFC491F00A3E@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=devnull+thecharlesjenkins.gmail.com@kernel.org \
    --cc=kvm@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox