Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v2 0/4] KVM: Reset steal time accounting on vCPU pid change
@ 2026-09-04 17:55 Dongli Zhang
  2026-09-04 17:55 ` [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu Dongli Zhang
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 17:55 UTC (permalink / raw)
  To: kvm, kvmarm, loongarch, kvm-riscv, linux-kselftest
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, zhaotianrui, maobibo, chenhuacai, anup, atish.patra,
	seanjc, pbonzini, shuah, dwmw2, joe.jin

v1: https://lore.kernel.org/all/20260816053630.527528-1-dongli.zhang@oracle.com

v1->v2:
  - move the last_steal field in the main vcpu structure (suggested by
    Marc Zyngier).
  - Reset last_steal from the caller of kvm_arch_vcpu_run_pid_change().

KVM does not support vCPU hotplug. When a vCPU is removed, its
corresponding data structures are not freed by KVM. Instead, QEMU destroys
only the userspace state and the vCPU thread, while the KVM vCPU fd remains
open and parked in QEMU.

As a result, vcpu->arch.st.last_steal is not reset. If the same vCPU is
later re-created by QEMU, last_steal retains its old value, while
current->sched_info.run_delay starts from zero since a new vCPU thread is
created. This causes current->sched_info.run_delay - vcpu->arch.st.last_steal
to produce a large, bogus value.

For instance, current->sched_info.run_delay can become smaller than
vcpu->arch.st.last_steal (see line 3804) if a QEMU vCPU is re-added after
it has previously been removed.

As a result, st->steal restarts from a very small value, close to
current->sched_info.run_delay.

3720 static void record_steal_time(struct kvm_vcpu *vcpu)
3721 {
... ...
3803         unsafe_get_user(steal, &st->steal, out);
3804         steal += current->sched_info.run_delay -
3805                 vcpu->arch.st.last_steal;
3806         vcpu->arch.st.last_steal = current->sched_info.run_delay;
3807         unsafe_put_user(steal, &st->steal, out);


This patchset resets last_steal when the vCPU PID changes, as suggested by
Sean.

Although David suggested accounting the run_delay left over from the
previous vCPU PID, this series does not do that. It would be easy to make
that work if KVM could simply assume every transition is a vCPU PID change.
In practice, KVM does not always have enough information about the previous
vCPU PID, e.g. after live migration, unless a new ioctl is introduced. For
now, this series simply resets last_steal.

Although David also suggested doing the same for Xen-on-KVM vCPUs, this
series does not reset last_steal for Xen vCPUs. That change itself would
not be difficult, but Xen uses a different mechanism to account downtime,
including runnable time and offline time when a vCPU is not running. It may
therefore need no additional ioctl, or a smaller ioctl extension, to account
run_delay left over from the previous PID.

As I have access to only x86 and arm64 KVM hosts, I created and validated
the selftest on those two architectures only.

Dongli Zhang (4)
  KVM: Move last_steal to common struct kvm_vcpu
  KVM: Reset last_steal on vCPU pid change
  KVM: selftests: Test steal time across vCPU pid changes on x86
  KVM: selftests: Add arm64 coverage for steal time pid changes

 arch/arm64/include/asm/kvm_host.h               |   1 -
 arch/arm64/kvm/Kconfig                          |   1 +
 arch/arm64/kvm/pvtime.c                         |   8 +-
 arch/loongarch/include/asm/kvm_host.h           |   1 -
 arch/loongarch/kvm/Kconfig                      |   1 +
 arch/loongarch/kvm/exit.c                       |   2 +-
 arch/loongarch/kvm/vcpu.c                       |   6 +-
 arch/riscv/include/asm/kvm_host.h               |   1 -
 arch/riscv/kvm/Kconfig                          |   1 +
 arch/riscv/kvm/vcpu_sbi_sta.c                   |  10 +-
 arch/x86/include/asm/kvm_host.h                 |   1 -
 arch/x86/kvm/Kconfig                            |   1 +
 arch/x86/kvm/x86.c                              |   5 +-
 include/linux/kvm_host.h                        |   4 +
 tools/testing/selftests/kvm/Makefile.kvm        |   2 +
 .../selftests/kvm/steal_time_change_pid.c       | 216 +++++++++++++++++++
 virt/kvm/Kconfig                                |   3 +
 virt/kvm/kvm_main.c                             |   4 +
 18 files changed, 248 insertions(+), 20 deletions(-)

base-commit: 8ab1afb2eb246ab15b301cd255b5943d208a93c1

Thank you very much!

Dongli Zhang


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu
  2026-09-04 17:55 [PATCH v2 0/4] KVM: Reset steal time accounting on vCPU pid change Dongli Zhang
@ 2026-09-04 17:55 ` Dongli Zhang
  2026-09-04 18:07   ` sashiko-bot
  2026-09-04 17:55 ` [PATCH v2 2/4] KVM: Reset last_steal on vCPU pid change Dongli Zhang
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 17:55 UTC (permalink / raw)
  To: kvm, kvmarm, loongarch, kvm-riscv, linux-kselftest
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, zhaotianrui, maobibo, chenhuacai, anup, atish.patra,
	seanjc, pbonzini, shuah, dwmw2, joe.jin

KVM caches per-vCPU host task's run_delay in per-architecture struct
kvm_vcpu_arch at last_steal.

Move the cache to struct kvm_vcpu and convert x86, arm64, riscv, and
loongarch to use the common field.

Add HAVE_KVM_PV_STEAL_TIME so last_steal is used only for architectures
that implement KVM stealtime.

Suggested-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com>
---
Marc suggested reusing CONFIG_HAVE_PV_STEAL_CLOCK_GEN, but I introduced
HAVE_KVM_PV_STEAL_TIME instead.

As I have access to only x86 and arm64 KVM hosts, I created and validated
the selftest on those two architectures only.

 arch/arm64/include/asm/kvm_host.h     |  1 -
 arch/arm64/kvm/Kconfig                |  1 +
 arch/arm64/kvm/pvtime.c               |  8 ++++----
 arch/loongarch/include/asm/kvm_host.h |  1 -
 arch/loongarch/kvm/Kconfig            |  1 +
 arch/loongarch/kvm/exit.c             |  2 +-
 arch/loongarch/kvm/vcpu.c             |  6 +++---
 arch/riscv/include/asm/kvm_host.h     |  1 -
 arch/riscv/kvm/Kconfig                |  1 +
 arch/riscv/kvm/vcpu_sbi_sta.c         | 10 +++++-----
 arch/x86/include/asm/kvm_host.h       |  1 -
 arch/x86/kvm/Kconfig                  |  1 +
 arch/x86/kvm/x86.c                    |  5 ++---
 include/linux/kvm_host.h              |  4 ++++
 virt/kvm/Kconfig                      |  3 +++
 15 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27fe0cd5b2d7..36cb7dee988c 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -937,7 +937,6 @@ struct kvm_vcpu_arch {
 
 	/* Guest PV state */
 	struct {
-		u64 last_steal;
 		gpa_t base;
 	} steal;
 
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449154f9a485..f5855bfc681c 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -34,6 +34,7 @@ menuconfig KVM
 	select HAVE_KVM_IRQ_BYPASS
 	select HAVE_KVM_READONLY_MEM
 	select HAVE_KVM_VCPU_RUN_PID_CHANGE
+	select HAVE_KVM_PV_STEAL_TIME
 	select SCHED_INFO
 	select GUEST_PERF_EVENTS if PERF_EVENTS
 	select KVM_GUEST_MEMFD
diff --git a/arch/arm64/kvm/pvtime.c b/arch/arm64/kvm/pvtime.c
index 4ceabaa4c30b..a67d93845d79 100644
--- a/arch/arm64/kvm/pvtime.c
+++ b/arch/arm64/kvm/pvtime.c
@@ -14,7 +14,7 @@ void kvm_update_stolen_time(struct kvm_vcpu *vcpu)
 {
 	struct kvm *kvm = vcpu->kvm;
 	u64 base = vcpu->arch.steal.base;
-	u64 last_steal = vcpu->arch.steal.last_steal;
+	u64 last_steal = vcpu->last_steal;
 	u64 offset = offsetof(struct pvclock_vcpu_stolen_time, stolen_time);
 	u64 steal = 0;
 	int idx;
@@ -25,8 +25,8 @@ void kvm_update_stolen_time(struct kvm_vcpu *vcpu)
 	idx = srcu_read_lock(&kvm->srcu);
 	if (!kvm_get_guest(kvm, base + offset, steal)) {
 		steal = le64_to_cpu(steal);
-		vcpu->arch.steal.last_steal = READ_ONCE(current->sched_info.run_delay);
-		steal += vcpu->arch.steal.last_steal - last_steal;
+		vcpu->last_steal = READ_ONCE(current->sched_info.run_delay);
+		steal += vcpu->last_steal - last_steal;
 		kvm_put_guest(kvm, base + offset, cpu_to_le64(steal));
 	}
 	srcu_read_unlock(&kvm->srcu, idx);
@@ -61,7 +61,7 @@ gpa_t kvm_init_stolen_time(struct kvm_vcpu *vcpu)
 	 * Start counting stolen time from the time the guest requests
 	 * the feature enabled.
 	 */
-	vcpu->arch.steal.last_steal = current->sched_info.run_delay;
+	vcpu->last_steal = current->sched_info.run_delay;
 	kvm_write_guest_lock(kvm, base, &init_values, sizeof(init_values));
 
 	return base;
diff --git a/arch/loongarch/include/asm/kvm_host.h b/arch/loongarch/include/asm/kvm_host.h
index 23cfbecebbd7..14514a25b261 100644
--- a/arch/loongarch/include/asm/kvm_host.h
+++ b/arch/loongarch/include/asm/kvm_host.h
@@ -253,7 +253,6 @@ struct kvm_vcpu_arch {
 	/* paravirt steal time */
 	struct {
 		u64 guest_addr;
-		u64 last_steal;
 		struct gfn_to_hva_cache cache;
 		u8  preempted;
 	} st;
diff --git a/arch/loongarch/kvm/Kconfig b/arch/loongarch/kvm/Kconfig
index 15da2d88c0c1..4996ec9c07f9 100644
--- a/arch/loongarch/kvm/Kconfig
+++ b/arch/loongarch/kvm/Kconfig
@@ -26,6 +26,7 @@ config KVM
 	select HAVE_KVM_MSI
 	select HAVE_KVM_READONLY_MEM
 	select KVM_COMMON
+	select HAVE_KVM_PV_STEAL_TIME
 	select KVM_GENERIC_DIRTYLOG_READ_PROTECT
 	select KVM_GENERIC_HARDWARE_ENABLING
 	select KVM_MMIO
diff --git a/arch/loongarch/kvm/exit.c b/arch/loongarch/kvm/exit.c
index 4f58e6e2cf86..56ae6b18f8c1 100644
--- a/arch/loongarch/kvm/exit.c
+++ b/arch/loongarch/kvm/exit.c
@@ -773,7 +773,7 @@ static long kvm_save_notify(struct kvm_vcpu *vcpu)
 		if (!(data & KVM_STEAL_PHYS_VALID))
 			return 0;
 
-		vcpu->arch.st.last_steal = current->sched_info.run_delay;
+		vcpu->last_steal = current->sched_info.run_delay;
 		kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu);
 		return 0;
 	default:
diff --git a/arch/loongarch/kvm/vcpu.c b/arch/loongarch/kvm/vcpu.c
index ed9e092c97ba..728d29eba8e0 100644
--- a/arch/loongarch/kvm/vcpu.c
+++ b/arch/loongarch/kvm/vcpu.c
@@ -187,8 +187,8 @@ static void kvm_update_stolen_time(struct kvm_vcpu *vcpu)
 	smp_wmb();
 
 	unsafe_get_user(steal, &st->steal, out);
-	steal += current->sched_info.run_delay - vcpu->arch.st.last_steal;
-	vcpu->arch.st.last_steal = current->sched_info.run_delay;
+	steal += current->sched_info.run_delay - vcpu->last_steal;
+	vcpu->last_steal = current->sched_info.run_delay;
 	unsafe_put_user(steal, &st->steal, out);
 
 	smp_wmb();
@@ -1205,7 +1205,7 @@ static int kvm_loongarch_pvtime_set_attr(struct kvm_vcpu *vcpu,
 
 	if (!ret) {
 		vcpu->arch.st.guest_addr = gpa;
-		vcpu->arch.st.last_steal = current->sched_info.run_delay;
+		vcpu->last_steal = current->sched_info.run_delay;
 		kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu);
 	}
 
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index a30600579231..305875301c04 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -273,7 +273,6 @@ struct kvm_vcpu_arch {
 	/* SBI steal-time accounting */
 	struct {
 		gpa_t shmem;
-		u64 last_steal;
 	} sta;
 };
 
diff --git a/arch/riscv/kvm/Kconfig b/arch/riscv/kvm/Kconfig
index ec2cee0a39e0..1ff07a904a02 100644
--- a/arch/riscv/kvm/Kconfig
+++ b/arch/riscv/kvm/Kconfig
@@ -26,6 +26,7 @@ config KVM
 	select HAVE_KVM_READONLY_MEM
 	select HAVE_KVM_DIRTY_RING_ACQ_REL
 	select KVM_COMMON
+	select HAVE_KVM_PV_STEAL_TIME
 	select KVM_GENERIC_DIRTYLOG_READ_PROTECT
 	select KVM_GENERIC_HARDWARE_ENABLING
 	select KVM_MMIO
diff --git a/arch/riscv/kvm/vcpu_sbi_sta.c b/arch/riscv/kvm/vcpu_sbi_sta.c
index 60e50296a008..9a77d3bde7c7 100644
--- a/arch/riscv/kvm/vcpu_sbi_sta.c
+++ b/arch/riscv/kvm/vcpu_sbi_sta.c
@@ -19,13 +19,13 @@
 static void kvm_riscv_vcpu_sbi_sta_reset(struct kvm_vcpu *vcpu)
 {
 	vcpu->arch.sta.shmem = INVALID_GPA;
-	vcpu->arch.sta.last_steal = 0;
+	vcpu->last_steal = 0;
 }
 
 void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu)
 {
 	gpa_t shmem = vcpu->arch.sta.shmem;
-	u64 last_steal = vcpu->arch.sta.last_steal;
+	u64 last_steal = vcpu->last_steal;
 	__le32 __user *sequence_ptr;
 	__le64 __user *steal_ptr;
 	__le32 sequence_le;
@@ -67,8 +67,8 @@ void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu)
 
 	if (!WARN_ON(get_user(steal_le, steal_ptr))) {
 		steal = le64_to_cpu(steal_le);
-		vcpu->arch.sta.last_steal = READ_ONCE(current->sched_info.run_delay);
-		steal += vcpu->arch.sta.last_steal - last_steal;
+		vcpu->last_steal = READ_ONCE(current->sched_info.run_delay);
+		steal += vcpu->last_steal - last_steal;
 		WARN_ON(put_user(cpu_to_le64(steal), steal_ptr));
 	}
 
@@ -115,7 +115,7 @@ static int kvm_sbi_sta_steal_time_set_shmem(struct kvm_vcpu *vcpu)
 		return SBI_ERR_INVALID_ADDRESS;
 
 	vcpu->arch.sta.shmem = shmem;
-	vcpu->arch.sta.last_steal = current->sched_info.run_delay;
+	vcpu->last_steal = current->sched_info.run_delay;
 
 	return 0;
 }
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 683bb8bf43a9..ebdd8526ae12 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -872,7 +872,6 @@ struct kvm_vcpu_arch {
 	struct {
 		u8 preempted;
 		u64 msr_val;
-		u64 last_steal;
 		struct gfn_to_hva_cache cache;
 	} st;
 
diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index 538ed1e80332..b2aea8058841 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -40,6 +40,7 @@ config KVM_X86
 	select HAVE_KVM_MSI
 	select HAVE_KVM_CPU_RELAX_INTERCEPT
 	select HAVE_KVM_NO_POLL
+	select HAVE_KVM_PV_STEAL_TIME
 	select VIRT_XFER_TO_GUEST_WORK
 	select KVM_GENERIC_DIRTYLOG_READ_PROTECT
 	select KVM_VFIO
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe473..927c8b1ed83d 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2129,9 +2129,8 @@ static void record_steal_time(struct kvm_vcpu *vcpu)
 	smp_wmb();
 
 	unsafe_get_user(steal, &st->steal, out);
-	steal += current->sched_info.run_delay -
-		vcpu->arch.st.last_steal;
-	vcpu->arch.st.last_steal = current->sched_info.run_delay;
+	steal += current->sched_info.run_delay - vcpu->last_steal;
+	vcpu->last_steal = current->sched_info.run_delay;
 	unsafe_put_user(steal, &st->steal, out);
 
 	version += 1;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 03bfc92864b6..ff9d423b6a7c 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -398,6 +398,10 @@ struct kvm_vcpu {
 	 */
 	struct kvm_memory_slot *last_used_slot;
 	u64 last_used_slot_gen;
+
+#ifdef CONFIG_HAVE_KVM_PV_STEAL_TIME
+	u64 last_steal;
+#endif
 };
 
 /*
diff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig
index c3c0ee253fc7..f42a3a0fc5ba 100644
--- a/virt/kvm/Kconfig
+++ b/virt/kvm/Kconfig
@@ -82,6 +82,9 @@ config HAVE_KVM_IRQ_BYPASS
 config HAVE_KVM_VCPU_RUN_PID_CHANGE
        bool
 
+config HAVE_KVM_PV_STEAL_TIME
+       bool
+
 config HAVE_KVM_NO_POLL
        bool
 
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v2 2/4] KVM: Reset last_steal on vCPU pid change
  2026-09-04 17:55 [PATCH v2 0/4] KVM: Reset steal time accounting on vCPU pid change Dongli Zhang
  2026-09-04 17:55 ` [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu Dongli Zhang
@ 2026-09-04 17:55 ` Dongli Zhang
  2026-09-04 17:55 ` [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86 Dongli Zhang
  2026-09-04 17:55 ` [PATCH v2 4/4] KVM: selftests: Add arm64 coverage for steal time pid changes Dongli Zhang
  3 siblings, 0 replies; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 17:55 UTC (permalink / raw)
  To: kvm, kvmarm, loongarch, kvm-riscv, linux-kselftest
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, zhaotianrui, maobibo, chenhuacai, anup, atish.patra,
	seanjc, pbonzini, shuah, dwmw2, joe.jin

KVM does not support vCPU hotplug. When a vCPU is removed, its
corresponding data structures are not freed by KVM. Instead, QEMU destroys
only the userspace state and the vCPU thread, while the KVM vCPU fd remains
open and parked in QEMU.

As a result, vcpu->last_steal is not reset.

If the same vCPU is later re-created by QEMU, last_steal retains its old
value, while current->sched_info.run_delay starts from zero since a new
vCPU thread is created. This causes
current->sched_info.run_delay - vcpu->last_steal to produce a large, bogus
value.

Fix this by resetting vcpu->last_steal to current->sched_info.run_delay
when KVM vCPU PID is changed.

Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com>
---
v1->v2:
  - Move from kvm_arch_vcpu_run_pid_change() to its caller.

 virt/kvm/kvm_main.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 65eb26a0520d..784f0d945b45 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -4478,6 +4478,10 @@ static long kvm_vcpu_ioctl(struct file *filp,
 			if (r)
 				break;
 
+#ifdef CONFIG_HAVE_KVM_PV_STEAL_TIME
+			vcpu->last_steal = current->sched_info.run_delay;
+#endif
+
 			newpid = get_task_pid(current, PIDTYPE_PID);
 			write_lock(&vcpu->pid_lock);
 			vcpu->pid = newpid;
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86
  2026-09-04 17:55 [PATCH v2 0/4] KVM: Reset steal time accounting on vCPU pid change Dongli Zhang
  2026-09-04 17:55 ` [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu Dongli Zhang
  2026-09-04 17:55 ` [PATCH v2 2/4] KVM: Reset last_steal on vCPU pid change Dongli Zhang
@ 2026-09-04 17:55 ` Dongli Zhang
  2026-09-04 18:11   ` sashiko-bot
  2026-09-04 17:55 ` [PATCH v2 4/4] KVM: selftests: Add arm64 coverage for steal time pid changes Dongli Zhang
  3 siblings, 1 reply; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 17:55 UTC (permalink / raw)
  To: kvm, kvmarm, loongarch, kvm-riscv, linux-kselftest
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, zhaotianrui, maobibo, chenhuacai, anup, atish.patra,
	seanjc, pbonzini, shuah, dwmw2, joe.jin

Add a selftest for the case where the same vCPU fd is run from a new host
thread after steal time has already been enabled and updated.

Pin the vCPU thread and a busy-loop thread to CPU 0, force host-side
run_delay to accumulate, and run the vCPU again to observe guest steal
time. Then run the same vCPU fd from a newly created host thread and verify
that the next steal time value observed on the new thread remains monotonic
and sane relative to the value observed on the old thread.

This indirectly validates that vcpu->last_steal is reset when the vCPU run
PID changes.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com>
---
As I have access to only x86 and arm64 KVM hosts, I created and validated
the selftest on those two architectures only.

 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/steal_time_change_pid.c     | 162 ++++++++++++++++++
 2 files changed, 163 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/steal_time_change_pid.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..036c28849f84 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -164,6 +164,7 @@ TEST_GEN_PROGS_x86 += irq_test
 TEST_GEN_PROGS_x86 += mmu_stress_test
 TEST_GEN_PROGS_x86 += rseq_test
 TEST_GEN_PROGS_x86 += steal_time
+TEST_GEN_PROGS_x86 += steal_time_change_pid
 TEST_GEN_PROGS_x86 += system_counter_offset_test
 TEST_GEN_PROGS_x86 += pre_fault_memory_test
 
diff --git a/tools/testing/selftests/kvm/steal_time_change_pid.c b/tools/testing/selftests/kvm/steal_time_change_pid.c
new file mode 100644
index 000000000000..3c39594db398
--- /dev/null
+++ b/tools/testing/selftests/kvm/steal_time_change_pid.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verify that KVM resets steal-time accounting when a vCPU fd is run from
+ * a different host PID.
+ */
+
+#include <pthread.h>
+#include <asm/kvm_para.h>
+#include "kvm_util.h"
+#include "processor.h"
+
+#define ST_GPA_BASE		(1 << 30)
+#define ST_SANE_DELTA_NS	(1ULL << 63)
+
+static void *st_gva;
+static u64 guest_stolen_time;
+static u64 main_steal;
+static u64 thread_steal;
+
+#if defined(__x86_64__)
+
+#define STEAL_TIME_SIZE	((sizeof(struct kvm_steal_time) + 63) & ~63)
+
+static void guest_code(void)
+{
+	struct kvm_steal_time *st = st_gva;
+
+	WRITE_ONCE(guest_stolen_time, READ_ONCE(st->steal));
+	GUEST_SYNC(0);
+
+	WRITE_ONCE(guest_stolen_time, READ_ONCE(st->steal));
+	GUEST_SYNC(1);
+
+	WRITE_ONCE(guest_stolen_time, READ_ONCE(st->steal));
+	GUEST_DONE();
+}
+
+static bool steal_time_supported(struct kvm_vcpu *vcpu)
+{
+	return kvm_cpu_has(X86_FEATURE_KVM_STEAL_TIME);
+}
+
+static void steal_time_enable(struct kvm_vcpu *vcpu)
+{
+	vcpu_set_msr(vcpu, MSR_KVM_STEAL_TIME,
+		     (ulong)st_gva | KVM_MSR_ENABLED);
+}
+
+#else
+#error "steal_time_change_pid is not implemented on this architecture"
+#endif
+
+static void run_vcpu(struct kvm_vcpu *vcpu)
+{
+	struct ucall uc;
+
+	vcpu_run(vcpu);
+
+	switch (get_ucall(vcpu, &uc)) {
+	case UCALL_SYNC:
+	case UCALL_DONE:
+		break;
+	case UCALL_ABORT:
+		REPORT_GUEST_ASSERT(uc);
+	default:
+		TEST_ASSERT(false, "Unexpected exit: %s",
+			    exit_reason_str(vcpu->run->exit_reason));
+	}
+}
+
+static void *do_steal_time(void *arg)
+{
+	struct timespec ts, stop;
+
+	clock_gettime(CLOCK_MONOTONIC, &ts);
+	stop = timespec_add_ns(ts, MIN_RUN_DELAY_NS);
+
+	while (timespec_to_ns(timespec_sub(ts, stop)) < 0)
+		clock_gettime(CLOCK_MONOTONIC, &ts);
+
+	return NULL;
+}
+
+static void *vcpu_thread(void *arg)
+{
+	struct kvm_vcpu *vcpu = arg;
+
+	run_vcpu(vcpu);
+	sync_global_from_guest(vcpu->vm, guest_stolen_time);
+	thread_steal = guest_stolen_time;
+
+	return NULL;
+}
+
+int main(void)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	pthread_attr_t attr;
+	pthread_t thread;
+	cpu_set_t cpuset;
+	long run_delay;
+	long run_delay_delta;
+
+	ksft_print_header();
+	ksft_set_plan(1);
+
+	CPU_ZERO(&cpuset);
+	CPU_SET(0, &cpuset);
+	pthread_attr_init(&attr);
+	pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
+	pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+				    ST_GPA_BASE, 1, 1, 0);
+	virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, 1);
+
+	st_gva = (void *)ST_GPA_BASE;
+	sync_global_to_guest(vm, st_gva);
+	memset(addr_gva2hva(vm, ST_GPA_BASE), 0, STEAL_TIME_SIZE);
+
+	TEST_REQUIRE(steal_time_supported(vcpu));
+
+	steal_time_enable(vcpu);
+	run_vcpu(vcpu);
+
+	run_delay = get_run_delay();
+	pthread_create(&thread, &attr, do_steal_time, NULL);
+
+	while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS)
+		sched_yield();
+
+	pthread_join(thread, NULL);
+	run_delay_delta = get_run_delay() - run_delay;
+	TEST_ASSERT(run_delay_delta >= MIN_RUN_DELAY_NS,
+		    "Expected run_delay >= %ld, got %ld",
+		    MIN_RUN_DELAY_NS, run_delay_delta);
+
+	run_vcpu(vcpu);
+	sync_global_from_guest(vm, guest_stolen_time);
+	main_steal = guest_stolen_time;
+
+	TEST_ASSERT(main_steal >= MIN_RUN_DELAY_NS,
+		    "Expected steal time >= %ld, got %"PRIu64,
+		    MIN_RUN_DELAY_NS, main_steal);
+
+	pthread_create(&thread, NULL, vcpu_thread, vcpu);
+	pthread_join(thread, NULL);
+
+	TEST_ASSERT(thread_steal >= main_steal &&
+		    thread_steal - main_steal < ST_SANE_DELTA_NS,
+		    "Expected sane steal after vCPU pid change: "
+		    "old=%"PRIu64", new=%"PRIu64,
+		    main_steal, thread_steal);
+
+	ksft_test_result_pass("steal time remains sane across vCPU pid change\n");
+
+	pthread_attr_destroy(&attr);
+	kvm_vm_free(vm);
+	ksft_finished();
+}
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v2 4/4] KVM: selftests: Add arm64 coverage for steal time pid changes
  2026-09-04 17:55 [PATCH v2 0/4] KVM: Reset steal time accounting on vCPU pid change Dongli Zhang
                   ` (2 preceding siblings ...)
  2026-09-04 17:55 ` [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86 Dongli Zhang
@ 2026-09-04 17:55 ` Dongli Zhang
  3 siblings, 0 replies; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 17:55 UTC (permalink / raw)
  To: kvm, kvmarm, loongarch, kvm-riscv, linux-kselftest
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, zhaotianrui, maobibo, chenhuacai, anup, atish.patra,
	seanjc, pbonzini, shuah, dwmw2, joe.jin

The previous patch added the common steal_time_change_pid test with x86
support. Add the arm64 support so the same test also covers arm64.

Use KVM_ARM_VCPU_PVTIME_IPA to enable the steal time shared page from
userspace, and use the PV_TIME_ST SMCCC call in the guest to retrieve and
read that page.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com>
---
As I have access to only x86 and arm64 KVM hosts, I created and validated
the selftest on those two architectures only.

 tools/testing/selftests/kvm/Makefile.kvm      |  1 +
 .../selftests/kvm/steal_time_change_pid.c     | 54 +++++++++++++++++++
 2 files changed, 55 insertions(+)

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 036c28849f84..1de13c0017e4 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -205,6 +205,7 @@ TEST_GEN_PROGS_arm64 += guest_memfd_test
 TEST_GEN_PROGS_arm64 += mmu_stress_test
 TEST_GEN_PROGS_arm64 += rseq_test
 TEST_GEN_PROGS_arm64 += steal_time
+TEST_GEN_PROGS_arm64 += steal_time_change_pid
 
 TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
 TEST_GEN_PROGS_s390 += s390/memop
diff --git a/tools/testing/selftests/kvm/steal_time_change_pid.c b/tools/testing/selftests/kvm/steal_time_change_pid.c
index 3c39594db398..12d4927d1c01 100644
--- a/tools/testing/selftests/kvm/steal_time_change_pid.c
+++ b/tools/testing/selftests/kvm/steal_time_change_pid.c
@@ -46,6 +46,60 @@ static void steal_time_enable(struct kvm_vcpu *vcpu)
 		     (ulong)st_gva | KVM_MSR_ENABLED);
 }
 
+#elif defined(__aarch64__)
+
+#define STEAL_TIME_SIZE	((sizeof(struct st_time) + 63) & ~63)
+
+#define PV_TIME_ST	0xc5000021
+
+struct st_time {
+	u32 rev;
+	u32 attr;
+	u64 st_time;
+};
+
+static void guest_code(void)
+{
+	struct arm_smccc_res res;
+	struct st_time *st;
+
+	do_smccc(PV_TIME_ST, 0, 0, 0, 0, 0, 0, 0, &res);
+	GUEST_ASSERT_NE(res.a0, -1);
+	GUEST_ASSERT_EQ(res.a0, (ulong)st_gva);
+
+	st = (struct st_time *)res.a0;
+	WRITE_ONCE(guest_stolen_time, READ_ONCE(st->st_time));
+	GUEST_SYNC(0);
+
+	WRITE_ONCE(guest_stolen_time, READ_ONCE(st->st_time));
+	GUEST_SYNC(1);
+
+	WRITE_ONCE(guest_stolen_time, READ_ONCE(st->st_time));
+	GUEST_DONE();
+}
+
+static bool steal_time_supported(struct kvm_vcpu *vcpu)
+{
+	struct kvm_device_attr dev = {
+		.group = KVM_ARM_VCPU_PVTIME_CTRL,
+		.attr = KVM_ARM_VCPU_PVTIME_IPA,
+	};
+
+	return !__vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev);
+}
+
+static void steal_time_enable(struct kvm_vcpu *vcpu)
+{
+	u64 st_ipa = (ulong)st_gva;
+	struct kvm_device_attr dev = {
+		.group = KVM_ARM_VCPU_PVTIME_CTRL,
+		.attr = KVM_ARM_VCPU_PVTIME_IPA,
+		.addr = (u64)&st_ipa,
+	};
+
+	vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev);
+}
+
 #else
 #error "steal_time_change_pid is not implemented on this architecture"
 #endif
-- 
2.43.7


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu
  2026-09-04 17:55 ` [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu Dongli Zhang
@ 2026-09-04 18:07   ` sashiko-bot
  2026-09-04 20:22     ` Dongli Zhang
  0 siblings, 1 reply; 9+ messages in thread
From: sashiko-bot @ 2026-09-04 18:07 UTC (permalink / raw)
  To: Dongli Zhang; +Cc: Marc Zyngier, kvm, Oliver Upton, kvmarm

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

Pre-existing issues:
- [High] Host-userspace reachable WARN_ON can cause a host crash when updating guest steal time.
--

commit 8f2f1662b1b75cda859eddacb4ef288871960d3d
Author: Dongli Zhang <dongli.zhang@oracle.com>

KVM: Move last_steal to common struct kvm_vcpu

This commit moves the last_steal cache field for the per-vCPU host task's
run_delay from architecture-specific structures into the common struct
kvm_vcpu. It updates x86, arm64, riscv, and loongarch to use this unified
field and introduces the HAVE_KVM_PV_STEAL_TIME config option.

[ ... ]

> diff --git a/arch/riscv/kvm/vcpu_sbi_sta.c b/arch/riscv/kvm/vcpu_sbi_sta.c
> index 60e50296a0085..9a77d3bde7c72 100644
> --- a/arch/riscv/kvm/vcpu_sbi_sta.c
> +++ b/arch/riscv/kvm/vcpu_sbi_sta.c
[ ... ]
> @@ -67,8 +67,8 @@ void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu)
>  
>  	if (!WARN_ON(get_user(steal_le, steal_ptr))) {
>  		steal = le64_to_cpu(steal_le);
> -		vcpu->arch.sta.last_steal = READ_ONCE(current->sched_info.run_delay);
> -		steal += vcpu->arch.sta.last_steal - last_steal;
> +		vcpu->last_steal = READ_ONCE(current->sched_info.run_delay);
> +		steal += vcpu->last_steal - last_steal;
>  		WARN_ON(put_user(cpu_to_le64(steal), steal_ptr));
>  	}
>  

[Severity: High]
This is a pre-existing issue, but I noticed these WARN_ON() macros wrapping
get_user() and put_user() accesses to host-userspace memory in 
kvm_riscv_vcpu_record_steal_time() (as well as the sequence_ptr accesses
just above this block).

Could a malicious host-userspace process trigger these kernel warnings by
unmapping the shared memory area or protecting it with mprotect(PROT_NONE)
before this function runs?

If the virtual memory area is made inaccessible, get_user() or put_user()
will fail with -EFAULT, causing the WARN_ON() to fire. This allows
unprivileged host userspace to trigger a host kernel panic if panic_on_warn
is enabled.

Would it be better to handle these user-memory access checks as normal error
paths without the WARN_ON() wrappers?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260904175550.430266-1-dongli.zhang@oracle.com?part=1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86
  2026-09-04 17:55 ` [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86 Dongli Zhang
@ 2026-09-04 18:11   ` sashiko-bot
  2026-09-04 20:27     ` Dongli Zhang
  0 siblings, 1 reply; 9+ messages in thread
From: sashiko-bot @ 2026-09-04 18:11 UTC (permalink / raw)
  To: Dongli Zhang; +Cc: kvmarm, Marc Zyngier, kvm, Oliver Upton

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The delay accumulation loop can hang indefinitely because the background thread uses a fixed wall-clock timeout instead of synchronizing with the main thread.
- [Medium] Unchecked return values from pthread APIs lead to undefined behavior and potential test hangs.
--

commit 4093ded4f0187a7d185578eeea38cf167b09f1d2
Author: Dongli Zhang <dongli.zhang@oracle.com>

KVM: selftests: Test steal time across vCPU pid changes on x86

This commit adds a selftest to verify that KVM correctly resets steal-time
accounting when a vCPU is run from a new host thread. It creates a background
thread to accumulate run delay and checks if the steal time remains monotonic
across PID changes.

> --- /dev/null
> +++ b/tools/testing/selftests/kvm/steal_time_change_pid.c
> @@ -0,0 +1,162 @@
[ ... ]
> +static void *do_steal_time(void *arg)
> +{
> +	struct timespec ts, stop;
> +
> +	clock_gettime(CLOCK_MONOTONIC, &ts);
> +	stop = timespec_add_ns(ts, MIN_RUN_DELAY_NS);
> +
> +	while (timespec_to_ns(timespec_sub(ts, stop)) < 0)
> +		clock_gettime(CLOCK_MONOTONIC, &ts);

[Severity: Medium]
Does this background thread rely on a fixed wall-clock timeout instead of
synchronizing with the main thread's delay accumulation?

If scheduler interleaving allows the main thread to run during this window,
its accumulated run delay will be strictly less than MIN_RUN_DELAY_NS when
this thread exits. Once this thread exits, CPU 0 becomes uncontended and
the main thread's delay accumulation loop will hang indefinitely.

> +
> +	return NULL;
> +}
[ ... ]
> +int main(void)
> +{
> +	struct kvm_vcpu *vcpu;
> +	struct kvm_vm *vm;
> +	pthread_attr_t attr;
> +	pthread_t thread;
> +	cpu_set_t cpuset;
> +	long run_delay;
> +	long run_delay_delta;
> +
> +	ksft_print_header();
> +	ksft_set_plan(1);
> +
> +	CPU_ZERO(&cpuset);
> +	CPU_SET(0, &cpuset);
> +	pthread_attr_init(&attr);
> +	pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
> +	pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);

[Severity: Medium]
If this pthread_setaffinity_np call fails (for example, if CPU 0 is not in
the allowed task mask), could the threads run on different CPUs? This would
prevent the main thread from ever accumulating runqueue wait time, causing it
to hang forever in the sched_yield loop.

[ ... ]
> +	run_delay = get_run_delay();
> +	pthread_create(&thread, &attr, do_steal_time, NULL);

[Severity: Medium]
If pthread_create fails due to resource limits, will the thread variable
remain uninitialized? 

> +
> +	while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS)
> +		sched_yield();

[Severity: Medium]
As mentioned above, if do_steal_time exits before MIN_RUN_DELAY_NS is
accumulated by the main thread, does this spinloop hang permanently because
the background thread is no longer consuming CPU time?

> +
> +	pthread_join(thread, NULL);

[Severity: Medium]
If the thread variable is uninitialized due to a prior pthread_create
failure, does passing it unconditionally to pthread_join invoke undefined
behavior?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260904175550.430266-1-dongli.zhang@oracle.com?part=3

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu
  2026-09-04 18:07   ` sashiko-bot
@ 2026-09-04 20:22     ` Dongli Zhang
  0 siblings, 0 replies; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 20:22 UTC (permalink / raw)
  To: sashiko-reviews, kvm-riscv; +Cc: Marc Zyngier, kvm, Oliver Upton, kvmarm

+ kvm-riscv@lists.infradead.org

The issue reported by sashiko isn't relevant to the patchset.

I don't have risc-v KVM host to reproduce this issue. It looks can be reproduced
on purpose.

Thank you very much!

Dongli Zhang

On Fri, Sep 4, 2026 11:07:15AM -0700, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> 
> Pre-existing issues:
> - [High] Host-userspace reachable WARN_ON can cause a host crash when updating guest steal time.
> --
> 
> commit 8f2f1662b1b75cda859eddacb4ef288871960d3d
> Author: Dongli Zhang <dongli.zhang@oracle.com>
> 
> KVM: Move last_steal to common struct kvm_vcpu
> 
> This commit moves the last_steal cache field for the per-vCPU host task's
> run_delay from architecture-specific structures into the common struct
> kvm_vcpu. It updates x86, arm64, riscv, and loongarch to use this unified
> field and introduces the HAVE_KVM_PV_STEAL_TIME config option.
> 
> [ ... ]
> 
>> diff --git a/arch/riscv/kvm/vcpu_sbi_sta.c b/arch/riscv/kvm/vcpu_sbi_sta.c
>> index 60e50296a0085..9a77d3bde7c72 100644
>> --- a/arch/riscv/kvm/vcpu_sbi_sta.c
>> +++ b/arch/riscv/kvm/vcpu_sbi_sta.c
> [ ... ]
>> @@ -67,8 +67,8 @@ void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu)
>>  
>>  	if (!WARN_ON(get_user(steal_le, steal_ptr))) {
>>  		steal = le64_to_cpu(steal_le);
>> -		vcpu->arch.sta.last_steal = READ_ONCE(current->sched_info.run_delay);
>> -		steal += vcpu->arch.sta.last_steal - last_steal;
>> +		vcpu->last_steal = READ_ONCE(current->sched_info.run_delay);
>> +		steal += vcpu->last_steal - last_steal;
>>  		WARN_ON(put_user(cpu_to_le64(steal), steal_ptr));
>>  	}
>>  
> 
> [Severity: High]
> This is a pre-existing issue, but I noticed these WARN_ON() macros wrapping
> get_user() and put_user() accesses to host-userspace memory in 
> kvm_riscv_vcpu_record_steal_time() (as well as the sequence_ptr accesses
> just above this block).
> 
> Could a malicious host-userspace process trigger these kernel warnings by
> unmapping the shared memory area or protecting it with mprotect(PROT_NONE)
> before this function runs?
> 
> If the virtual memory area is made inaccessible, get_user() or put_user()
> will fail with -EFAULT, causing the WARN_ON() to fire. This allows
> unprivileged host userspace to trigger a host kernel panic if panic_on_warn
> is enabled.
> 
> Would it be better to handle these user-memory access checks as normal error
> paths without the WARN_ON() wrappers?
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86
  2026-09-04 18:11   ` sashiko-bot
@ 2026-09-04 20:27     ` Dongli Zhang
  0 siblings, 0 replies; 9+ messages in thread
From: Dongli Zhang @ 2026-09-04 20:27 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvmarm, Marc Zyngier, kvm, Oliver Upton

The implementation of this selftest program borrows from
tools/testing/selftests/kvm/steal_time.c.

The same concerns also apply to tools/testing/selftests/kvm/steal_time.c.

Based on my understanding, KVM selftests do not require strict error-handling code.

Thank you very much!

Dongli Zhang

On Fri, Sep 4, 2026 11:11:03AM -0700, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] The delay accumulation loop can hang indefinitely because the background thread uses a fixed wall-clock timeout instead of synchronizing with the main thread.
> - [Medium] Unchecked return values from pthread APIs lead to undefined behavior and potential test hangs.
> --
> 
> commit 4093ded4f0187a7d185578eeea38cf167b09f1d2
> Author: Dongli Zhang <dongli.zhang@oracle.com>
> 
> KVM: selftests: Test steal time across vCPU pid changes on x86
> 
> This commit adds a selftest to verify that KVM correctly resets steal-time
> accounting when a vCPU is run from a new host thread. It creates a background
> thread to accumulate run delay and checks if the steal time remains monotonic
> across PID changes.
> 
>> --- /dev/null
>> +++ b/tools/testing/selftests/kvm/steal_time_change_pid.c
>> @@ -0,0 +1,162 @@
> [ ... ]
>> +static void *do_steal_time(void *arg)
>> +{
>> +	struct timespec ts, stop;
>> +
>> +	clock_gettime(CLOCK_MONOTONIC, &ts);
>> +	stop = timespec_add_ns(ts, MIN_RUN_DELAY_NS);
>> +
>> +	while (timespec_to_ns(timespec_sub(ts, stop)) < 0)
>> +		clock_gettime(CLOCK_MONOTONIC, &ts);
> 
> [Severity: Medium]
> Does this background thread rely on a fixed wall-clock timeout instead of
> synchronizing with the main thread's delay accumulation?
> 
> If scheduler interleaving allows the main thread to run during this window,
> its accumulated run delay will be strictly less than MIN_RUN_DELAY_NS when
> this thread exits. Once this thread exits, CPU 0 becomes uncontended and
> the main thread's delay accumulation loop will hang indefinitely.
> 
>> +
>> +	return NULL;
>> +}
> [ ... ]
>> +int main(void)
>> +{
>> +	struct kvm_vcpu *vcpu;
>> +	struct kvm_vm *vm;
>> +	pthread_attr_t attr;
>> +	pthread_t thread;
>> +	cpu_set_t cpuset;
>> +	long run_delay;
>> +	long run_delay_delta;
>> +
>> +	ksft_print_header();
>> +	ksft_set_plan(1);
>> +
>> +	CPU_ZERO(&cpuset);
>> +	CPU_SET(0, &cpuset);
>> +	pthread_attr_init(&attr);
>> +	pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
>> +	pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
> 
> [Severity: Medium]
> If this pthread_setaffinity_np call fails (for example, if CPU 0 is not in
> the allowed task mask), could the threads run on different CPUs? This would
> prevent the main thread from ever accumulating runqueue wait time, causing it
> to hang forever in the sched_yield loop.
> 
> [ ... ]
>> +	run_delay = get_run_delay();
>> +	pthread_create(&thread, &attr, do_steal_time, NULL);
> 
> [Severity: Medium]
> If pthread_create fails due to resource limits, will the thread variable
> remain uninitialized? 
> 
>> +
>> +	while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS)
>> +		sched_yield();
> 
> [Severity: Medium]
> As mentioned above, if do_steal_time exits before MIN_RUN_DELAY_NS is
> accumulated by the main thread, does this spinloop hang permanently because
> the background thread is no longer consuming CPU time?
> 
>> +
>> +	pthread_join(thread, NULL);
> 
> [Severity: Medium]
> If the thread variable is uninitialized due to a prior pthread_create
> failure, does passing it unconditionally to pthread_join invoke undefined
> behavior?
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-09-04 20:27 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 17:55 [PATCH v2 0/4] KVM: Reset steal time accounting on vCPU pid change Dongli Zhang
2026-09-04 17:55 ` [PATCH v2 1/4] KVM: Move last_steal to common struct kvm_vcpu Dongli Zhang
2026-09-04 18:07   ` sashiko-bot
2026-09-04 20:22     ` Dongli Zhang
2026-09-04 17:55 ` [PATCH v2 2/4] KVM: Reset last_steal on vCPU pid change Dongli Zhang
2026-09-04 17:55 ` [PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86 Dongli Zhang
2026-09-04 18:11   ` sashiko-bot
2026-09-04 20:27     ` Dongli Zhang
2026-09-04 17:55 ` [PATCH v2 4/4] KVM: selftests: Add arm64 coverage for steal time pid changes Dongli Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox