From nobody Fri Apr 3 02:56:55 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B6EA717A305; Tue, 17 Feb 2026 01:01:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771290083; cv=none; b=KWRHBgwuon5nBqO5xztItc57fcY3vETz5pUI19Ukkrv28tSQct+75iqYXJj7I4f5R06ij6c8yXybztjht3f6mzgJESF/ReVdWMSBQksUWtwEoHxw8s3QtGjGfmGPZAPw2GD+dCtQslinCNrIoQFb3XnXc9mI4lqS7futNcH0oNc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771290083; c=relaxed/simple; bh=c2vbSdetJzlZYmNsQeV45rnwSVku9N1DKuDBKHrmoyU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=bWKAYpEr/ftD7bjHbZ6CeAo9mjY1Jlh9ZqkQoUAMLJub0MK4NbjaLVJpbpzv34tf+ZUBO98MyO4csUK7hARPhkVs154ed2gNHIdCT0QVUQLTqmCV2lxwXYRvuCqezBk43Acbf5W+0/GaHG9EoULzU2S9QnX9i234porSMgA1yUI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=UrgKKEm4; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="UrgKKEm4" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 8C4C7C19425; Tue, 17 Feb 2026 01:01:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771290083; bh=c2vbSdetJzlZYmNsQeV45rnwSVku9N1DKuDBKHrmoyU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UrgKKEm4ExLZehDCY/9XGTpxbLdYqLgMh0PzJ7l94t1WDEs8bEzv198g9kFnwMnsj ccBjHudYRfVFHxSNPnNn3SuA914m2Fvk6fv1eMLrfpbzlHGt4yJECWHgZEt5M2jDYq +AUwEuE2W7lkMXk49Mb/aKvJOmLWatt+5D2X+KrsI8TCCrtXskfQ+X/sJyvBnQa698 tZYzyTDb09yxdGuYpK1sw6pZn9rIY81VHoinOl8iu+AEFl2iv60nZk285gU7ILEAZ8 DrpPHuZtFyskWl6SaS0FMWkC+lNV1f1p55mRD0Qu9aZg2qaBRBz3UYm7bwcncWQr8S rt+PfHwEjIy2A== From: Sasha Levin To: patches@lists.linux.dev, stable@vger.kernel.org Cc: Stefano Stabellini , Dominique Martinet , Sasha Levin , ericvh@kernel.org, lucho@ionkov.net, v9fs@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH AUTOSEL 6.19-6.12] 9p/xen: protect xen_9pfs_front_free against concurrent calls Date: Mon, 16 Feb 2026 20:01:14 -0500 Message-ID: <20260217010118.3503621-3-sashal@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260217010118.3503621-1-sashal@kernel.org> References: <20260217010118.3503621-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-stable: review X-Patchwork-Hint: Ignore X-stable-base: Linux 6.19.2 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Stefano Stabellini [ 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 a= ddress 0x6b6b6b6b6b6b6b6b: 0000 [#1] SMP DEBUG_PAGEALLOC NOPTI [ 27.052357] CPU: 0 UID: 0 PID: 32 Comm: xenwatch Not tainted 6.18.0-0208= 7-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: 00000000000= 00000 [ 27.052385] RDX: 6b6b6b6b6b6b6b6b RSI: 0000000000000000 RDI: 00000000000= 00000 [ 27.052389] RBP: ffff88800a887040 R08: 0000000000000000 R09: 00000000000= 00000 [ 27.052393] R10: 0000000000000000 R11: 0000000000000000 R12: ffff888009e= 46b68 [ 27.052397] R13: 0000000000000200 R14: 0000000000000000 R15: ffff88800a8= 87040 [ 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: 00000000000= 50660 [ 27.052418] Call Trace: [ 27.052420] [ 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] [ 27.052465] Modules linked in: [ 27.052471] ---[ end trace 0000000000000000 ]--- Signed-off-by: Stefano Stabellini Message-ID: <20260129230348.2390470-1-stefano.stabellini@amd.com> Signed-off-by: Dominique Martinet Signed-off-by: Sasha Levin --- 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 =E2=80=94 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 =E2=86=92 OK 2. `xen_9pfs_front_changed()` receives `XenbusStateInitWait` =E2=86=92 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` =E2=86=92 `priv` is freed, but drv= data still points to freed memory 4. Backend state continues changing =E2=86=92 `XenbusStateClosing` arrives = =E2=86=92 `xenbus_frontend_closed(dev)` =E2=86=92 triggers device removal =E2=86= =92 `xen_9pfs_front_remove()` called 5. `xen_9pfs_front_remove()` does `dev_get_drvdata(&dev->dev)` =E2=86=92 ge= ts stale pointer to freed memory =E2=86=92 calls `xen_9pfs_front_free(priv)` again =E2=86=92 **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 =E2=80=94 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 =E2=80=94 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 =E2=80=94 author is the subsystem creator, concrete crash trace provided, fix has clear logic 2. **Fixes a real bug**: Yes =E2=80=94 double-free causing kernel GPF/crash 3. **Important issue**: Yes =E2=80=94 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 =E2=80=94 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" =E2=80=94 present in stable (confirmed in `git log HEAD`) - `e43c608f40c06` "9p/xen: fix release of IRQ" =E2=80=94 present in stable (confirmed) - `ea4f1009408ef` "9p/xen: Fix use after free bug" =E2=80=94 present in sta= ble (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 =E2= =80=94 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 =E2=80=94 he is th= e 9p maintainer - **Checked prior related fixes**: `ea4f1009408ef` fixed a different race (work vs free), `7ef3ae82a6ebb` fixed init sequence =E2=80=94 both a= re prerequisites present in stable - **Unverified**: Whether older stable trees (6.6.y, 6.1.y, 5.15.y) have all prerequisites =E2=80=94 the ring loop variable `XEN_9PFS_NUM_RINGS` a= nd 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; =20 - write_lock(&xen_9pfs_lock); - list_del(&priv->list); - write_unlock(&xen_9pfs_lock); - - for (i =3D 0; i < XEN_9PFS_NUM_RINGS; i++) { - struct xen_9pfs_dataring *ring =3D &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 =3D 0; - j < (1 << priv->rings[i].intf->ring_order); - j++) { - grant_ref_t ref; - - ref =3D 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 =3D 0; i < XEN_9PFS_NUM_RINGS; i++) { + struct xen_9pfs_dataring *ring =3D &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 =3D 0; + j < (1 << priv->rings[i].intf->ring_order); + j++) { + grant_ref_t ref; + + ref =3D 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); } =20 static void xen_9pfs_front_remove(struct xenbus_device *dev) { - struct xen_9pfs_front_priv *priv =3D dev_get_drvdata(&dev->dev); + struct xen_9pfs_front_priv *priv; =20 + write_lock(&xen_9pfs_lock); + priv =3D dev_get_drvdata(&dev->dev); + if (priv =3D=3D 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); } =20 @@ -382,7 +389,7 @@ static int xen_9pfs_front_init(struct xenbus_device *de= v) { int ret, i; struct xenbus_transaction xbt; - struct xen_9pfs_front_priv *priv =3D dev_get_drvdata(&dev->dev); + struct xen_9pfs_front_priv *priv; char *versions, *v; unsigned int max_rings, max_ring_order, len =3D 0; =20 @@ -410,6 +417,10 @@ static int xen_9pfs_front_init(struct xenbus_device *d= ev) if (p9_xen_trans.maxsize > XEN_FLEX_RING_SIZE(max_ring_order)) p9_xen_trans.maxsize =3D XEN_FLEX_RING_SIZE(max_ring_order) / 2; =20 + priv =3D kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev =3D dev; priv->rings =3D 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 *d= ev) goto error; } =20 + 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; =20 @@ -482,19 +498,6 @@ static int xen_9pfs_front_init(struct xenbus_device *d= ev) static int xen_9pfs_front_probe(struct xenbus_device *dev, const struct xenbus_device_id *id) { - struct xen_9pfs_front_priv *priv =3D NULL; - - priv =3D kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->dev =3D 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; } =20 --=20 2.51.0