Re: Re: [BUG REPORT] USE_AFTER_FREE in complete_emulated_mmio found by KASAN/Syzkaller fuzz test (v5.10.0)
From: Sean Christopherson
Date: Wed Feb 18 2026 - 15:58:52 EST
On Tue, Feb 10, 2026, Sean Christopherson wrote:
> On Tue, Feb 10, 2026, Zhangjiaji wrote:
> > > I think there's a not-completely-awful solution buried in this gigantic cesspool.
> > > The only time KVM uses on-stack variables is for qword or smaller accesses, i.e.
> > > 8 bytes in size or less. For larger fragments, e.g. AVX to/from MMIO, the target
> > > value will always be an operand in the emulator context. And so rather than
> > > disallow stack variables, for "small" fragments, we can rework the handling to
> > > copy the value to/from each fragment on-demand instead of stashing a pointer to
> > > the value.
> >
> > Since we can store the frag->val in struct kvm_mmio_fragment,
> > why not just point frag->data to it? This Way we can save a lot code about
> > (frag->data == NULL).
>
> It's not quite that simple, because we need to handle reads as well.
>
> > Though this patch will block any read-into-stack calls, we can add a special path
> > in function emulator_read_write handling feasible read-into-stack calls -- the
> > target is released just after emulator_read_write returns.
> >
> > ---
> > arch/x86/kvm/x86.c | 9 ++++++++-
> > include/linux/kvm_host.h | 3 ++-
> > 2 files changed, 10 insertions(+), 2 deletions(-)
> >
> > diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> > index 72d37c8930ad..12d53d441a39 100644
> > --- a/arch/x86/kvm/x86.c
> > +++ b/arch/x86/kvm/x86.c
> > @@ -8197,7 +8197,14 @@ static int emulator_read_write_onepage(unsigned long addr, void *val,
> > WARN_ON(vcpu->mmio_nr_fragments >= KVM_MAX_MMIO_FRAGMENTS);
> > frag = &vcpu->mmio_fragments[vcpu->mmio_nr_fragments++];
> > frag->gpa = gpa;
> > - frag->data = val;
> > + if (bytes > 8u || ! write) {
> > + if (WARN_ON_ONCE(object_is_on_stack(val)))
>
> This is user-triggerable, e.g. em_popa(), em_pop_sreg(), emulate_iret_real(),
> em_ret_near_imm(), em_ret_far(), and em_ret().
*sigh*
And I was wrong. I finally sat down to write some comments for all of this, and
realized that reads _never_ pass an on-stack @val to emulator_read_write_onepage(),
because read_emulated() always buffers reads through ctxt->mem_read.
So not only is my fancy, complex code unnecessary, it's actively broken. If a
read splits a page boundary, and the first page is NOT emulated MMIO, trying to
fulfill the read on-demand falls apart because the @val points at the start of
the operand (technically its cache "entry"). I'm sure that's a solvable problem,
but I don't see any point in manufacturing a problem in the first place.
I need to write a changelog, but as Yashu suggested, the fix can more simply be:
--
From: Sean Christopherson <seanjc@xxxxxxxxxx>
Date: Tue, 10 Feb 2026 09:45:37 -0800
Subject: [PATCH 01/14] KVM: x86: Use scratch field in MMIO fragment to hold
small write values
Fixes: f78146b0f923 ("KVM: Fix page-crossing MMIO")
Suggested-by: Yashu Zhang <zhangjiaji1@xxxxxxxxxx>
Reported-by: Yashu Zhang <zhangjiaji1@xxxxxxxxxx>
Closes: https://lore.kernel.org/all/369eaaa2b3c1425c85e8477066391bc7@xxxxxxxxxx
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
---
arch/x86/kvm/x86.c | 14 +++++++++++++-
include/linux/kvm_host.h | 3 ++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index db3f393192d9..ff3a6f86973f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8226,7 +8226,13 @@ static int emulator_read_write_onepage(unsigned long addr, void *val,
WARN_ON(vcpu->mmio_nr_fragments >= KVM_MAX_MMIO_FRAGMENTS);
frag = &vcpu->mmio_fragments[vcpu->mmio_nr_fragments++];
frag->gpa = gpa;
- frag->data = val;
+ if (write && bytes <= 8u) {
+ frag->val = 0;
+ frag->data = &frag->val;
+ memcpy(&frag->val, val, bytes);
+ } else {
+ frag->data = val;
+ }
frag->len = bytes;
return X86EMUL_CONTINUE;
}
@@ -8241,6 +8247,9 @@ static int emulator_read_write(struct x86_emulate_ctxt *ctxt,
gpa_t gpa;
int rc;
+ if (WARN_ON_ONCE((bytes > 8u || !ops->write) && object_is_on_stack(val)))
+ return X86EMUL_UNHANDLEABLE;
+
if (ops->read_write_prepare &&
ops->read_write_prepare(vcpu, val, bytes))
return X86EMUL_CONTINUE;
@@ -11847,6 +11856,9 @@ static int complete_emulated_mmio(struct kvm_vcpu *vcpu)
frag++;
vcpu->mmio_cur_fragment++;
} else {
+ if (WARN_ON_ONCE(frag->data == &frag->val))
+ return -EIO;
+
/* Go forward to the next mmio piece. */
frag->data += len;
frag->gpa += len;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 2c7d76262898..0bb2a34fb93d 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -320,7 +320,8 @@ static inline bool kvm_vcpu_can_poll(ktime_t cur, ktime_t stop)
struct kvm_mmio_fragment {
gpa_t gpa;
void *data;
- unsigned len;
+ u64 val;
+ unsigned int len;
};
struct kvm_vcpu {
base-commit: 183bb0ce8c77b0fd1fb25874112bc8751a461e49
--