* [PATCH] KVM: selftests: Test partial KVM_CLEAR_DIRTY_LOG requests
@ 2026-09-14 15:53 Tharit Tangkijwanichakul
[not found] ` <20260914161000.E3CD21F000FF@smtp.kernel.org>
0 siblings, 1 reply; 2+ messages in thread
From: Tharit Tangkijwanichakul @ 2026-09-14 15:53 UTC (permalink / raw)
To: Paolo Bonzini, Shuah Khan
Cc: Sean Christopherson, kvm, linux-kselftest, linux-kernel,
linux-kernel-mentees, skhan, me, jkoolstra,
Tharit Tangkijwanichakul
The clear-log mode invokes KVM_CLEAR_DIRTY_LOG with first_page set to
zero and num_pages covering the entire memory slot. But the clear-log
allows targeted clearing. So, the test does not verify that KVM
correctly applies a bitmap relative to a nonzero first_page,
or that pages not selected by a partial request remain dirty.
Add a focused test that creates a memory slot with
KVM_DIRTY_LOG_INITIALLY_SET, then submits a 64-page clear request
starting at page 64 with only bitmap bit 1 set. Verify that memslot page
65 becomes clean while every other host-page bit remains dirty.
Write to page 65 from the guest and verify that dirty tracking was
re-enabled and the page becomes dirty again.
Signed-off-by: Tharit Tangkijwanichakul <tharitt97@gmail.com>
---
Tested on x86_64 with 4K host and guest pages:
./dirty_log_test -M clear-log
PASS
Tested on arm64 Rock 5B with 4K host pages, including 4K, 16K and
64K guest page modes:
./dirty_log_test -M clear-log
PASS
---
tools/testing/selftests/kvm/dirty_log_test.c | 104 +++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/tools/testing/selftests/kvm/dirty_log_test.c b/tools/testing/selftests/kvm/dirty_log_test.c
index af5eb0334a74..a12b538c3144 100644
--- a/tools/testing/selftests/kvm/dirty_log_test.c
+++ b/tools/testing/selftests/kvm/dirty_log_test.c
@@ -815,6 +815,106 @@ static void run_test(enum vm_guest_mode mode, void *arg)
kvm_vm_free(vm);
}
+/* Use the 2nd 64-page range to exercise a non-zero offset. */
+#define TEST_CLEAR_LOG_FIRST_PAGE 64
+#define TEST_CLEAR_LOG_NUM_PAGES 64
+#define TEST_CLEAR_LOG_BIT 1
+#define TEST_CLEAR_LOG_PAGE (TEST_CLEAR_LOG_FIRST_PAGE + \
+ TEST_CLEAR_LOG_BIT)
+static void guest_code_clear_log(void)
+{
+ u64 addr = guest_test_virt_mem + TEST_CLEAR_LOG_PAGE * host_page_size;
+
+ while (true) {
+ vcpu_arch_put_guest(*(u64 *)addr, 1);
+ GUEST_SYNC(1);
+ }
+}
+
+/*
+ * Verify KVM_CLEAR_DIRTY_LOG only clears (and re-arms) the requested range:
+ * with KVM_DIRTY_LOG_INITIALLY_SET every page starts dirty; clearing a single
+ * page must leave its neighbours dirty and must write-protect the page so the
+ * next guest write re-dirties it.
+ */
+static void run_clear_log_partial_test(enum vm_guest_mode mode, void *arg)
+{
+ unsigned long *bmap, *clear_bmap;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ u64 page;
+
+ guest_num_pages = vm_adjust_num_guest_pages(mode, 3 * BITS_PER_LONG);
+
+ vm = create_vm(mode, &vcpu, 2 * guest_num_pages, guest_code_clear_log);
+
+ guest_page_size = vm->page_size;
+ host_page_size = getpagesize();
+ host_num_pages = vm_num_host_pages(mode, guest_num_pages);
+
+ guest_test_virt_mem = DEFAULT_GUEST_TEST_MEM;
+ guest_test_phys_mem = (vm->max_gfn - guest_num_pages) * guest_page_size;
+ guest_test_phys_mem = align_down(guest_test_phys_mem, host_page_size);
+
+ bmap = bitmap_zalloc(host_num_pages);
+ clear_bmap = bitmap_zalloc(BITS_PER_LONG);
+
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ guest_test_phys_mem, TEST_MEM_SLOT_INDEX,
+ guest_num_pages, KVM_MEM_LOG_DIRTY_PAGES);
+ virt_map(vm, guest_test_virt_mem, guest_test_phys_mem, guest_num_pages);
+
+ sync_global_to_guest(vm, host_page_size);
+ sync_global_to_guest(vm, guest_test_virt_mem);
+
+ kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
+ for (page = 0; page < host_num_pages; page++)
+ TEST_ASSERT(test_bit_le(page, bmap),
+ "Page %lu should be dirty after INITIALLY_SET", page);
+
+ __set_bit_le(TEST_CLEAR_LOG_BIT, clear_bmap);
+ kvm_vm_clear_dirty_log(vm, TEST_MEM_SLOT_INDEX, clear_bmap,
+ TEST_CLEAR_LOG_FIRST_PAGE,
+ TEST_CLEAR_LOG_NUM_PAGES);
+
+ kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
+ for (page = 0; page < host_num_pages; page++) {
+ if (page == TEST_CLEAR_LOG_PAGE)
+ TEST_ASSERT(!test_bit_le(page, bmap),
+ "Cleared page %lu should be clean", page);
+ else
+ TEST_ASSERT(test_bit_le(page, bmap),
+ "Page %lu should still be dirty", page);
+ }
+
+ /* The cleared page is now write-protected; a guest write must re-arm it */
+ vcpu_run(vcpu);
+ TEST_ASSERT(get_ucall(vcpu, NULL) == UCALL_SYNC,
+ "Unexpected guest exit: %s",
+ exit_reason_str(vcpu->run->exit_reason));
+
+ kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
+ TEST_ASSERT(test_bit_le(TEST_CLEAR_LOG_PAGE, bmap),
+ "Page %lu should be dirty again after re-arming",
+ (u64)TEST_CLEAR_LOG_PAGE);
+
+ free(bmap);
+ free(clear_bmap);
+ kvm_vm_free(vm);
+}
+
+static void test_clear_log_partial(void)
+{
+ host_log_mode = LOG_MODE_CLEAR_LOG;
+ if (!log_mode_supported()) {
+ print_skip("Log mode '%s' not supported",
+ log_modes[host_log_mode].name);
+ return;
+ }
+
+ for_each_guest_mode(run_clear_log_partial_test, NULL);
+}
+
static void help(char *name)
{
puts("");
@@ -913,5 +1013,9 @@ int main(int argc, char *argv[])
for_each_guest_mode(run_test, &p);
}
+ if (host_log_mode_option == LOG_MODE_ALL ||
+ host_log_mode_option == LOG_MODE_CLEAR_LOG)
+ test_clear_log_partial();
+
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] KVM: selftests: Test partial KVM_CLEAR_DIRTY_LOG requests
[not found] ` <20260914161000.E3CD21F000FF@smtp.kernel.org>
@ 2026-09-16 15:51 ` Tharit Tangkijwanichakul
0 siblings, 0 replies; 2+ messages in thread
From: Tharit Tangkijwanichakul @ 2026-09-16 15:51 UTC (permalink / raw)
To: sashiko-reviews
Cc: kvm, Paolo Bonzini, Shuah Khan, Sean Christopherson,
linux-kselftest, linux-kernel, linux-kernel-mentees, me,
jkoolstra
> > +/* Use the 2nd 64-page range to exercise a non-zero offset. */
> > +#define TEST_CLEAR_LOG_FIRST_PAGE 64
> > +#define TEST_CLEAR_LOG_NUM_PAGES 64
> > +#define TEST_CLEAR_LOG_BIT 1
> > +#define TEST_CLEAR_LOG_PAGE (TEST_CLEAR_LOG_FIRST_PAGE + \
> > + TEST_CLEAR_LOG_BIT)
> > +static void guest_code_clear_log(void)
> > +{
> > + u64 addr = guest_test_virt_mem + TEST_CLEAR_LOG_PAGE * host_page_size;
> > +
> > + while (true) {
> > + vcpu_arch_put_guest(*(u64 *)addr, 1);
>
> [Severity: Medium]
> Will this result in an out-of-bounds guest memory access on architectures where
> the host page size is larger than the guest page size?
Agreed.
The allocation is sized in guest pages, but the clear request and
guest write offset are expressed in host pages.
I'll fix this in v2 by starting with 3*64=192 host pages and
converting that count to guest pages before applying the allocation
adjustment.
> > + kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
> > + for (page = 0; page < host_num_pages; page++)
> > + TEST_ASSERT(test_bit_le(page, bmap),
> > + "Page %lu should be dirty after INITIALLY_SET", page);
>
> [Severity: Medium]
> Does this assertion assume that KVM_DIRTY_LOG_INITIALLY_SET is always supported
> if KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 is present?
> Sashiko AI review · https://sashiko.dev/#/patchset/20260914155301.14499-1-tharitt97@gmail.com?part=1
Agreed.
I'll check for the KVM_DIRTY_LOG_INITIALLY_SET at the start of the
test, and skip the test if it is unavailable.
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-16 15:51 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 15:53 [PATCH] KVM: selftests: Test partial KVM_CLEAR_DIRTY_LOG requests Tharit Tangkijwanichakul
[not found] ` <20260914161000.E3CD21F000FF@smtp.kernel.org>
2026-09-16 15:51 ` Tharit Tangkijwanichakul
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox