[PATCH AUTOSEL 6.19-6.12] 9p/xen: protect xen_9pfs_front_free against concurrent calls
From: Sasha Levin
Date: Mon Feb 16 2026 - 20:01:44 EST
From: Stefano Stabellini <stefano.stabellini@xxxxxxx>
[ Upstream commit ce8ded2e61f47747e31eeefb44dc24a2160a7e32 ]
The xenwatch thread can race with other back-end change notifications
and call xen_9pfs_front_free() twice, hitting the observed general
protection fault due to a double-free. Guard the teardown path so only
one caller can release the front-end state at a time, preventing the
crash.
This is a fix for the following double-free:
[ 27.052347] Oops: general protection fault, probably for non-canonical address 0x6b6b6b6b6b6b6b6b: 0000 [#1] SMP DEBUG_PAGEALLOC NOPTI
[ 27.052357] CPU: 0 UID: 0 PID: 32 Comm: xenwatch Not tainted 6.18.0-02087-g51ab33fc0a8b-dirty #60 PREEMPT(none)
[ 27.052363] RIP: e030:xen_9pfs_front_free+0x1d/0x150
[ 27.052368] Code: 90 90 90 90 90 90 90 90 90 90 90 90 90 41 55 41 54 55 48 89 fd 48 c7 c7 48 d0 92 85 53 e8 cb cb 05 00 48 8b 45 08 48 8b 55 00 <48> 3b 28 0f 85 f9 28 35 fe 48 3b 6a 08 0f 85 ef 28 35 fe 48 89 42
[ 27.052377] RSP: e02b:ffffc9004016fdd0 EFLAGS: 00010246
[ 27.052381] RAX: 6b6b6b6b6b6b6b6b RBX: ffff88800d66e400 RCX: 0000000000000000
[ 27.052385] RDX: 6b6b6b6b6b6b6b6b RSI: 0000000000000000 RDI: 0000000000000000
[ 27.052389] RBP: ffff88800a887040 R08: 0000000000000000 R09: 0000000000000000
[ 27.052393] R10: 0000000000000000 R11: 0000000000000000 R12: ffff888009e46b68
[ 27.052397] R13: 0000000000000200 R14: 0000000000000000 R15: ffff88800a887040
[ 27.052404] FS: 0000000000000000(0000) GS:ffff88808ca57000(0000) knlGS:0000000000000000
[ 27.052408] CS: e030 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 27.052412] CR2: 00007f9714004360 CR3: 0000000004834000 CR4: 0000000000050660
[ 27.052418] Call Trace:
[ 27.052420] <TASK>
[ 27.052422] xen_9pfs_front_changed+0x5d5/0x720
[ 27.052426] ? xenbus_otherend_changed+0x72/0x140
[ 27.052430] ? __pfx_xenwatch_thread+0x10/0x10
[ 27.052434] xenwatch_thread+0x94/0x1c0
[ 27.052438] ? __pfx_autoremove_wake_function+0x10/0x10
[ 27.052442] kthread+0xf8/0x240
[ 27.052445] ? __pfx_kthread+0x10/0x10
[ 27.052449] ? __pfx_kthread+0x10/0x10
[ 27.052452] ret_from_fork+0x16b/0x1a0
[ 27.052456] ? __pfx_kthread+0x10/0x10
[ 27.052459] ret_from_fork_asm+0x1a/0x30
[ 27.052463] </TASK>
[ 27.052465] Modules linked in:
[ 27.052471] ---[ end trace 0000000000000000 ]---
Signed-off-by: Stefano Stabellini <stefano.stabellini@xxxxxxx>
Message-ID: <20260129230348.2390470-1-stefano.stabellini@xxxxxxx>
Signed-off-by: Dominique Martinet <asmadeus@xxxxxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---
LLM Generated explanations, may be completely bogus:
The 9p Xen transport driver was introduced in v4.12. So this bug affects
all stable trees from v4.12 onwards.
Now let me verify the specific race scenario described in the commit
message — double-free via concurrent calls to `xen_9pfs_front_free`:
Looking at the code flow in the current (unpatched) tree:
**Scenario for double-free:**
1. `xen_9pfs_front_probe()` allocates `priv`, adds to list, sets drvdata
→ OK
2. `xen_9pfs_front_changed()` receives `XenbusStateInitWait` → calls
`xen_9pfs_front_init(dev)`
3. If `_init` fails, it calls `xen_9pfs_front_free(priv)` which does
`list_del`, frees rings, frees `priv` → `priv` is freed, but drvdata
still points to freed memory
4. Backend state continues changing → `XenbusStateClosing` arrives →
`xenbus_frontend_closed(dev)` → triggers device removal →
`xen_9pfs_front_remove()` called
5. `xen_9pfs_front_remove()` does `dev_get_drvdata(&dev->dev)` → gets
stale pointer to freed memory → calls `xen_9pfs_front_free(priv)`
again → **GPF/double-free**
This is the exact scenario described in the commit message and matches
the stack trace showing the crash in `xen_9pfs_front_free()` called from
`xen_9pfs_front_changed()`.
## Classification
- **Bug type**: Double-free / Use-after-free
- **Severity**: Kernel crash (general protection fault, Oops)
- **Trigger**: Xenbus backend state changes during 9pfs frontend
initialization failure
- **Impact**: System crash in Xen guests using 9pfs
- **Scope**: net/9p/trans_xen.c only — single file, well-contained
## Risk Assessment
**Risk of the fix**: MEDIUM-LOW
- The fix restructures `_probe`, `_init`, `_remove`, and `_free`
functions
- It's not a one-liner — about 170 lines are changed across the file
- However, all changes are logically consistent and well-motivated:
- Moving allocation from probe to init is safe (probe just returns 0
now)
- Adding NULL check in remove prevents double-free
- Using lock around drvdata access prevents TOCTOU race
- Adding `if (priv->rings)` guard in free prevents NULL deref
- The author is Stefano Stabellini, the original 9p/xen driver author
- Reviewed and signed off by Dominique Martinet (9p maintainer)
**Risk of NOT backporting**: HIGH
- Kernel crash on Xen guests using 9p filesystem
- Reproducible by backend state changes (not just theoretical)
- Concrete Oops with stack trace demonstrated
## Stable Criteria Check
1. **Obviously correct and tested**: Yes — author is the subsystem
creator, concrete crash trace provided, fix has clear logic
2. **Fixes a real bug**: Yes — double-free causing kernel GPF/crash
3. **Important issue**: Yes — kernel crash/oops
4. **Small and contained**: The diff is moderately sized (~170 lines)
but confined to a single file and a single logical change
5. **No new features**: Correct — no new APIs or features added
6. **Clean application**: The stable tree (6.19.y) has the prerequisite
commits `e43c608f40c06` and `7ef3ae82a6ebb`
## Dependencies
The commit depends on:
- `7ef3ae82a6ebb` "9p/xen: fix init sequence" — present in stable
(confirmed in `git log HEAD`)
- `e43c608f40c06` "9p/xen: fix release of IRQ" — present in stable
(confirmed)
- `ea4f1009408ef` "9p/xen: Fix use after free bug" — present in stable
(confirmed)
All prerequisites are present in the 6.19.y stable tree.
## Verification
- **Read the current (unpatched) trans_xen.c**: Confirmed the pre-patch
code at lines 276-320, 381-480, 482-499 showing the buggy lifecycle
- **Confirmed the race**: `xen_9pfs_front_init()` error path at line 478
calls `xen_9pfs_front_free(priv)` which frees `priv`, but drvdata is
not cleared. Subsequent `_remove()` at line 316 reads stale drvdata
and calls `_free` again.
- **Confirmed 0x6b6b6b6b6b6b6b6b**: SLAB_FREE_DEBUG poison pattern,
confirming access to freed slab memory
- **git log master -- net/9p/trans_xen.c**: Confirmed all prerequisite
commits exist in the tree
- **git describe 868eb122739a5**: Confirmed driver exists since v4.12 —
bug affects all stable trees
- **Author verification**: Stefano Stabellini is the original driver
author (confirmed by copyright in file header and commit
`71ebd71921e45`)
- **Maintainer sign-off**: Dominique Martinet signed off — he is the 9p
maintainer
- **Checked prior related fixes**: `ea4f1009408ef` fixed a different
race (work vs free), `7ef3ae82a6ebb` fixed init sequence — both are
prerequisites present in stable
- **Unverified**: Whether older stable trees (6.6.y, 6.1.y, 5.15.y) have
all prerequisites — the ring loop variable `XEN_9PFS_NUM_RINGS` and
some code may differ. The commit may need backport adjustments for
older trees.
## Conclusion
This is a clear-cut bug fix for a double-free/use-after-free crash in
the Xen 9p filesystem transport. The bug has a concrete reproducer with
a kernel Oops stack trace. The fix is self-contained in a single file,
authored by the original driver creator, and signed off by the subsystem
maintainer. While the change is moderately sized, every part of it
directly addresses the race condition. The risk of the fix is low
compared to the impact of the crash it prevents. All prerequisites exist
in the current stable tree.
**YES**
net/9p/trans_xen.c | 85 ++++++++++++++++++++++++----------------------
1 file changed, 44 insertions(+), 41 deletions(-)
diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c
index 12f752a923324..9bbfc20744f69 100644
--- a/net/9p/trans_xen.c
+++ b/net/9p/trans_xen.c
@@ -277,45 +277,52 @@ static void xen_9pfs_front_free(struct xen_9pfs_front_priv *priv)
{
int i, j;
- write_lock(&xen_9pfs_lock);
- list_del(&priv->list);
- write_unlock(&xen_9pfs_lock);
-
- for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) {
- struct xen_9pfs_dataring *ring = &priv->rings[i];
-
- cancel_work_sync(&ring->work);
-
- if (!priv->rings[i].intf)
- break;
- if (priv->rings[i].irq > 0)
- unbind_from_irqhandler(priv->rings[i].irq, ring);
- if (priv->rings[i].data.in) {
- for (j = 0;
- j < (1 << priv->rings[i].intf->ring_order);
- j++) {
- grant_ref_t ref;
-
- ref = priv->rings[i].intf->ref[j];
- gnttab_end_foreign_access(ref, NULL);
- }
- free_pages_exact(priv->rings[i].data.in,
+ if (priv->rings) {
+ for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) {
+ struct xen_9pfs_dataring *ring = &priv->rings[i];
+
+ cancel_work_sync(&ring->work);
+
+ if (!priv->rings[i].intf)
+ break;
+ if (priv->rings[i].irq > 0)
+ unbind_from_irqhandler(priv->rings[i].irq, ring);
+ if (priv->rings[i].data.in) {
+ for (j = 0;
+ j < (1 << priv->rings[i].intf->ring_order);
+ j++) {
+ grant_ref_t ref;
+
+ ref = priv->rings[i].intf->ref[j];
+ gnttab_end_foreign_access(ref, NULL);
+ }
+ free_pages_exact(priv->rings[i].data.in,
1UL << (priv->rings[i].intf->ring_order +
XEN_PAGE_SHIFT));
+ }
+ gnttab_end_foreign_access(priv->rings[i].ref, NULL);
+ free_page((unsigned long)priv->rings[i].intf);
}
- gnttab_end_foreign_access(priv->rings[i].ref, NULL);
- free_page((unsigned long)priv->rings[i].intf);
+ kfree(priv->rings);
}
- kfree(priv->rings);
kfree(priv->tag);
kfree(priv);
}
static void xen_9pfs_front_remove(struct xenbus_device *dev)
{
- struct xen_9pfs_front_priv *priv = dev_get_drvdata(&dev->dev);
+ struct xen_9pfs_front_priv *priv;
+ write_lock(&xen_9pfs_lock);
+ priv = dev_get_drvdata(&dev->dev);
+ if (priv == NULL) {
+ write_unlock(&xen_9pfs_lock);
+ return;
+ }
dev_set_drvdata(&dev->dev, NULL);
+ list_del(&priv->list);
+ write_unlock(&xen_9pfs_lock);
+
xen_9pfs_front_free(priv);
}
@@ -382,7 +389,7 @@ static int xen_9pfs_front_init(struct xenbus_device *dev)
{
int ret, i;
struct xenbus_transaction xbt;
- struct xen_9pfs_front_priv *priv = dev_get_drvdata(&dev->dev);
+ struct xen_9pfs_front_priv *priv;
char *versions, *v;
unsigned int max_rings, max_ring_order, len = 0;
@@ -410,6 +417,10 @@ static int xen_9pfs_front_init(struct xenbus_device *dev)
if (p9_xen_trans.maxsize > XEN_FLEX_RING_SIZE(max_ring_order))
p9_xen_trans.maxsize = XEN_FLEX_RING_SIZE(max_ring_order) / 2;
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dev = dev;
priv->rings = kcalloc(XEN_9PFS_NUM_RINGS, sizeof(*priv->rings),
GFP_KERNEL);
if (!priv->rings) {
@@ -468,6 +479,11 @@ static int xen_9pfs_front_init(struct xenbus_device *dev)
goto error;
}
+ write_lock(&xen_9pfs_lock);
+ dev_set_drvdata(&dev->dev, priv);
+ list_add_tail(&priv->list, &xen_9pfs_devs);
+ write_unlock(&xen_9pfs_lock);
+
xenbus_switch_state(dev, XenbusStateInitialised);
return 0;
@@ -482,19 +498,6 @@ static int xen_9pfs_front_init(struct xenbus_device *dev)
static int xen_9pfs_front_probe(struct xenbus_device *dev,
const struct xenbus_device_id *id)
{
- struct xen_9pfs_front_priv *priv = NULL;
-
- priv = kzalloc(sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->dev = dev;
- dev_set_drvdata(&dev->dev, priv);
-
- write_lock(&xen_9pfs_lock);
- list_add_tail(&priv->list, &xen_9pfs_devs);
- write_unlock(&xen_9pfs_lock);
-
return 0;
}
--
2.51.0