From 5852cdf0006ff0f1d9a56f06197673d4e4270220 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:50:59 +0530 Subject: [PATCH 01/39] net/9p: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Christian Schoenebeck Message-ID: <20260904112100.34541-1-hemanth.selam@gmail.com> Signed-off-by: Dominique Martinet --- include/net/9p/9p.h | 2 +- include/net/9p/client.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h index fd7a034b8278..22ab7486795d 100644 --- a/include/net/9p/9p.h +++ b/include/net/9p/9p.h @@ -120,7 +120,7 @@ void _p9_debug(enum p9_debug_flags level, const char *func, * @P9_RCREATE: response with file access information * @P9_TREAD: request to transfer data from a file or directory * @P9_RREAD: response with data requested - * @P9_TWRITE: reuqest to transfer data to a file + * @P9_TWRITE: request to transfer data to a file * @P9_RWRITE: response with out much data was transferred to file * @P9_TCLUNK: forget about a handle to an entity within the file system * @P9_RCLUNK: response when server has forgotten about the handle diff --git a/include/net/9p/client.h b/include/net/9p/client.h index 55c6cb54bd25..f3aead8939ef 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -97,7 +97,7 @@ struct p9_req_t { * @proto_version: 9P protocol version to use * @trans_mod: module API instantiated with this client * @status: connection state - * @trans: tranport instance state and API + * @trans: transport instance state and API * @fids: All active FID handles * @reqs: All active requests. * @name: node name used as client id From 37dce65a0b4356d7ec289a5d09cece5db241c302 Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Fri, 29 May 2026 17:06:24 +0800 Subject: [PATCH 02/39] 9p: use fc->net_ns for network namespace in RDMA and socket transports The 9p RDMA transport currently passes &init_net to rdma_create_id(). As a result, RDMA address resolution and connection setup are performed in the initial network namespace, even when the mount is initiated from a non-initial network namespace. This differs from the socket-based 9p transports, which create sockets in current->nsproxy->net_ns. Use fc->net_ns for both the RDMA and socket transports instead, so that transport setup follows the VFS mount namespace context rather than depending on the calling process's current namespace. This avoids surprising behaviour where a 9p RDMA mount from a container or other non-initial network namespace may use the host namespace for RDMA CM operations. Fixes: fa20105e09e9 ("IB/cma: Add support for network namespaces") Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Suggested-by: Dominique Martinet Assisted-by: GLM:GLM-5.1 Signed-off-by: Yizhou Zhao Message-ID: <20260529090625.78761-1-zhaoyz24@mails.tsinghua.edu.cn> Signed-off-by: Dominique Martinet --- net/9p/trans_fd.c | 11 +++++------ net/9p/trans_rdma.c | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index eb685b52aeb2..dce963332a04 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -887,8 +887,8 @@ p9_fd_create_tcp(struct p9_client *client, struct fs_context *fc) return -EINVAL; sprintf(port_str, "%u", opts.port); - err = inet_pton_with_scope(current->nsproxy->net_ns, AF_UNSPEC, addr, - port_str, &stor); + err = inet_pton_with_scope(fc->net_ns, AF_UNSPEC, addr, port_str, + &stor); if (err < 0) return err; @@ -896,8 +896,8 @@ p9_fd_create_tcp(struct p9_client *client, struct fs_context *fc) client->trans_opts.tcp.port = opts.port; client->trans_opts.tcp.privport = opts.privport; - err = __sock_create(current->nsproxy->net_ns, stor.ss_family, - SOCK_STREAM, IPPROTO_TCP, &csocket, 1); + err = __sock_create(fc->net_ns, stor.ss_family, SOCK_STREAM, + IPPROTO_TCP, &csocket, 1); if (err) { pr_err("%s (%d): problem creating socket\n", __func__, task_pid_nr(current)); @@ -946,8 +946,7 @@ p9_fd_create_unix(struct p9_client *client, struct fs_context *fc) __func__, task_pid_nr(current), addr); return -ENAMETOOLONG; } - err = __sock_create(current->nsproxy->net_ns, PF_UNIX, - SOCK_STREAM, 0, &csocket, 1); + err = __sock_create(fc->net_ns, PF_UNIX, SOCK_STREAM, 0, &csocket, 1); if (err < 0) { pr_err("%s (%d): problem creating socket\n", __func__, task_pid_nr(current)); diff --git a/net/9p/trans_rdma.c b/net/9p/trans_rdma.c index b4274f10fa44..f4450e779a79 100644 --- a/net/9p/trans_rdma.c +++ b/net/9p/trans_rdma.c @@ -556,7 +556,7 @@ rdma_create_trans(struct p9_client *client, struct fs_context *fc) return -ENOMEM; /* Create the RDMA CM ID */ - rdma->cm_id = rdma_create_id(&init_net, p9_cm_event_handler, client, + rdma->cm_id = rdma_create_id(fc->net_ns, p9_cm_event_handler, client, RDMA_PS_TCP, IB_QPT_RC); if (IS_ERR(rdma->cm_id)) goto error; From 7fb9bd3659051ff74a29387de0023bb8300e4aa7 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 3 Jul 2026 12:22:54 +0200 Subject: [PATCH 03/39] 9p: fix privport option setting wrong RDMA field While reviewing a patch adding vsock transport to 9p, I noticed that since commit 1f3e4142c0eb ("9p: convert to the new mount API"), the Opt_privport case incorrectly sets rdma_opts->port instead of rdma_opts->privport, so mounting with the privport option overwrites the RDMA port number instead of enabling privileged port usage. Fixes: 1f3e4142c0eb ("9p: convert to the new mount API") Cc: stable@vger.kernel.org Cc: sandeen@redhat.com Signed-off-by: Stefano Garzarella Acked-by: Eric Sandeen Message-ID: <20260703102254.114446-1-sgarzare@redhat.com> Signed-off-by: Dominique Martinet --- fs/9p/v9fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c index 274c5157135d..f426cee37414 100644 --- a/fs/9p/v9fs.c +++ b/fs/9p/v9fs.c @@ -406,7 +406,7 @@ int v9fs_parse_param(struct fs_context *fc, struct fs_parameter *param) break; case Opt_privport: fd_opts->privport = true; - rdma_opts->port = true; + rdma_opts->privport = true; break; } From 0955472fff1e9f9e410a0300136924ebc8bd7a16 Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Fri, 29 May 2026 16:18:16 +0800 Subject: [PATCH 04/39] net/9p/usbg: fix descriptor cleanup use-after-free usb9pfs_free_func() frees the f_usb9pfs object before calling usb_free_all_descriptors(). The usb_function passed to the latter is embedded in the freed object, so removing the function from a gadget dereferences freed memory. The failing teardown sequence is: configfs unlink usb9pfs_free_func() kfree(usb9pfs) usb_free_all_descriptors(f) dereference the embedded usb_function KASAN reported: BUG: KASAN: slab-use-after-free in usb_free_all_descriptors+0x138/0x190 Read of size 8 at addr ffff888106a73088 by task poc/95 Call Trace: usb_free_all_descriptors+0x138/0x190 config_usb_cfg_unlink+0x1f0/0x2f0 configfs_unlink+0x321/0x6f0 Free the descriptors before freeing their containing object. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: GLM:GLM-5.1 Signed-off-by: Yizhou Zhao Co-developed-by: Chengfeng Ye Signed-off-by: Chengfeng Ye Acked-by: Michael Grzeschik Message-ID: <20260529081817.77898-1-zhaoyz24@mails.tsinghua.edu.cn> Message-ID: <20260731150414.3135662-1-nicoyip.dev@gmail.com> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 419cda13a7b5..23056b803ddf 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -725,8 +725,6 @@ static void usb9pfs_free_func(struct usb_function *f) struct f_usb9pfs *usb9pfs = func_to_usb9pfs(f); struct f_usb9pfs_opts *opts; - kfree(usb9pfs); - opts = container_of(f->fi, struct f_usb9pfs_opts, func_inst); mutex_lock(&opts->lock); @@ -734,6 +732,8 @@ static void usb9pfs_free_func(struct usb_function *f) mutex_unlock(&opts->lock); usb_free_all_descriptors(f); + + kfree(usb9pfs); } static int usb9pfs_set_alt(struct usb_function *f, From 9ee83ef8d24f11feb55fd0d222c0ec19f09dbd3f Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Sun, 7 Jun 2026 20:19:04 +0800 Subject: [PATCH 05/39] net/9p/usbg: fix prefix matching in device lookup p9_usbg_create() matches the mount source against registered usb9pfs instance tags with strncmp() and the length of the caller supplied source. That accepts any prefix of the registered tag. For example, a gadget instance tagged "secret_channel" can be selected with "s", "sec", or any other non-empty prefix. Once selected, the instance is marked in use and the client is attached to that transport. This differs from the configfs tag lookup path, which uses strcmp() and therefore requires an exact tag match. Require an exact tag match in p9_usbg_create() as well. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: GLM:GLM-5.1 Signed-off-by: Yizhou Zhao Co-developed-by: Jackie Liu Signed-off-by: Jackie Liu Message-ID: <20260607121907.12588-1-zhaoyz24@mails.tsinghua.edu.cn> Message-ID: <20260719040346.88295-1-liu.yun@linux.dev> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 23056b803ddf..021631d63318 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -391,7 +391,7 @@ static int p9_usbg_create(struct p9_client *client, struct fs_context *fc) guard(mutex)(&usb9pfs_lock); list_for_each_entry(dev, &usbg_instance_list, usb9pfs_instance) { - if (!strncmp(devname, dev->tag, strlen(devname))) { + if (!strcmp(devname, dev->tag)) { if (!dev->inuse) { dev->inuse = true; found = 1; From 5c69297356bf6d04d8222f52717c6c4aa73e5d2c Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Fri, 29 May 2026 16:10:25 +0800 Subject: [PATCH 06/39] net/9p/usbg: Fix use-after-free on usb9pfs_clear_tx When p9_usbg_close() is called, it invokes usb9pfs_clear_tx() but does not clear usb9pfs->client nor usb9pfs->in_req->context afterwards. The 9p client core (p9_client_destroy) calls close() and then kfree(clnt), so after p9_usbg_close returns the client struct is freed. If the USB gadget is later disabled (e.g., host disconnect), usb9pfs_disable calls usb9pfs_clear_tx again, which passes the dangling usb9pfs->client pointer to p9_client_cb. Inside p9_client_cb, p9_req_put(c, req) dereferences the freed client to access its reqs IDR, causing a use-after-free. Additionally, usb9pfs_clear_tx does not clear usb9pfs->in_req->context after calling p9_client_cb, so a second invocation finds a stale request pointer and attempts double-completion, further compounding the UAF risk. Fix by: 1. Clearing usb9pfs->in_req->context to NULL in usb9pfs_clear_tx() before checking usb9pfs->client, so that even if the client is gone the stale request pointer is consumed and cannot cause double-completion. 2. Adding a NULL check on usb9pfs->client in usb9pfs_clear_tx() to skip p9_client_cb() when the client has already been destroyed. 3. Setting usb9pfs->client = NULL in p9_usbg_close() after calling usb9pfs_clear_tx(), to prevent any subsequent call from dereferencing the freed client pointer. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: GLM:GLM-5.1 Signed-off-by: Yizhou Zhao Message-ID: <20260529081026.77732-1-zhaoyz24@mails.tsinghua.edu.cn> Acked-by: Michael Grzeschik Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 021631d63318..5f15e4249e84 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -435,6 +435,11 @@ static void usb9pfs_clear_tx(struct f_usb9pfs *usb9pfs) if (!req) return; + usb9pfs->in_req->context = NULL; + + if (!usb9pfs->client) + return; + if (!req->t_err) req->t_err = -ECONNRESET; @@ -457,6 +462,7 @@ static void p9_usbg_close(struct p9_client *client) client->status = Disconnected; usb9pfs_clear_tx(usb9pfs); + usb9pfs->client = NULL; opts = container_of(usb9pfs->function.fi, struct f_usb9pfs_opts, func_inst); From b58943dd690eb9b76c652e735789937a03f3d30c Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 2 Aug 2026 23:21:48 +0800 Subject: [PATCH 07/39] net/9p/usbg: clear stale request context after abort usb9pfs_clear_tx() serializes access to the active TX request with usb9pfs->lock, but leaves in_req->context pointing at the request after p9_client_cb() drops the transport reference. A later teardown path can therefore retrieve the same request after it has been freed: CPU 0 (p9_usbg_close) CPU 1 (usb9pfs_disable) lock(usb9pfs->lock) req = in_req->context p9_client_cb(client, req) p9_req_put(client, req) unlock(usb9pfs->lock) request reaches zero refs kmem_cache_free(req) lock(usb9pfs->lock) req = in_req->context read req->t_err The lock orders the two callbacks, but cannot protect a pointer that remains published after its reference has been released. KASAN reported: BUG: KASAN: slab-use-after-free in usb9pfs_clear_tx+0x166/0x1b0 Read of size 4 at addr ffff88810e7420f4 by task poc/94 Call Trace: usb9pfs_clear_tx+0x166/0x1b0 usb9pfs_disable+0x1d/0x30 reset_config+0x9d/0x200 __composite_disconnect+0xa7/0x140 Allocated by task 93: kmem_cache_alloc_noprof+0x141/0x370 p9_tag_alloc+0x8f/0x5b0 p9_client_prepare_req+0xff/0x350 p9_client_rpc+0x1a5/0xac0 Freed by task 0: slab_free_after_rcu_debug+0xa6/0x1e0 rcu_core+0x50a/0x1850 Last potentially related work creation: kmem_cache_free+0x1db/0x3d0 p9_req_put+0x164/0x1f0 usb9pfs_clear_tx+0x120/0x1b0 p9_usbg_close+0x7e/0x150 Clear in_req->context after the callback while still holding the lock. This matches the consume-and-clear pattern in usb9pfs_tx_complete(), so subsequent teardown calls return without touching the released request. The existing callback and error ordering remain unchanged. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Qi Zhang Message-ID: <20260802152148.810390-1-marsy12010123@gmail.com> Acked-by: Michael Grzeschik Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 5f15e4249e84..9fd7801d56ab 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -444,6 +444,7 @@ static void usb9pfs_clear_tx(struct f_usb9pfs *usb9pfs) req->t_err = -ECONNRESET; p9_client_cb(usb9pfs->client, req, REQ_STATUS_ERROR); + usb9pfs->in_req->context = NULL; } static void p9_usbg_close(struct p9_client *client) From 5bfef2b6dafe83fe2d8d210d54093a8f48cfc97e Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sat, 27 Jun 2026 17:10:12 -0400 Subject: [PATCH 08/39] 9p/trans_virtio: reject mount tags longer than NAME_MAX at probe p9_virtio_probe() reads a 16-bit mount tag length (tag_len) from the device config and kzalloc()s tag_len + 1 bytes for chan->tag with no upper bound. A malicious or compromised host can present a tag of up to 65535 bytes; that tag is later copied into the single-page sysfs buffer by p9_mount_tag_show() (memcpy(buf, chan->tag, tag_len + 1)), a host-controlled out-of-bounds write of up to ~64 KiB past the PAGE_SIZE attribute buffer. Reject an overlong tag at probe time. A 9p mount tag is a name-like identifier, so bound it by NAME_MAX (255): that limit is independent of the sysfs/seq_file page size and is far above any legitimate mount tag, which keeps p9_mount_tag_show() safe at the root without hard-coding a seq_file implementation detail. Fixes: 179a5bc4b8cb ("net/9p: use memcpy() instead of snprintf() in p9_mount_tag_show()") Cc: stable@vger.kernel.org Suggested-by: Christian Schoenebeck Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Christian Schoenebeck Message-ID: <20260627211012.4042372-1-michael.bommarito@gmail.com> Signed-off-by: Dominique Martinet --- net/9p/trans_virtio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/9p/trans_virtio.c b/net/9p/trans_virtio.c index b0d0094ec8e2..9fa63e3ebf67 100644 --- a/net/9p/trans_virtio.c +++ b/net/9p/trans_virtio.c @@ -633,6 +633,11 @@ static int p9_virtio_probe(struct virtio_device *vdev) err = -EINVAL; goto out_free_vq; } + if (tag_len > NAME_MAX) { + dev_err(&vdev->dev, "mount tag too long (%u bytes)\n", tag_len); + err = -EINVAL; + goto out_free_vq; + } tag = kzalloc(tag_len + 1, GFP_KERNEL); if (!tag) { err = -ENOMEM; From 4d183e6f153245aca2534e372152583b868dd925 Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Thu, 6 Aug 2026 14:42:54 +0000 Subject: [PATCH 09/39] 9p/xen: fix refcount leak in p9_xen_response() on wrong tag p9_xen_response() looks up the request for an incoming reply with p9_tag_lookup(), which takes a reference on the returned p9_req_t. When the tag does not resolve to a request in REQ_STATUS_SENT, the function warns and continues the loop without dropping that reference, permanently leaking the p9_req_t and its msize buffers. The reply header, including the tag, is supplied by the backend, so a malicious or buggy 9P backend can leak kernel memory on every crafted response. Drop the reference before continuing. Fixes: 728356dedeff ("9p: Add refcount to p9_req_t") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Reviewed-by: Stefano Stabellini Message-ID: <20260806144255.4167019-1-gyf161023@gmail.com> Signed-off-by: Dominique Martinet --- net/9p/trans_xen.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index f9fb2db7a066..8eea0da8797f 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -203,6 +203,8 @@ static void p9_xen_response(struct work_struct *work) req = p9_tag_lookup(priv->client, h.tag); if (!req || req->status != REQ_STATUS_SENT) { dev_warn(&priv->dev->dev, "Wrong req tag=%x\n", h.tag); + if (req) + p9_req_put(priv->client, req); cons += h.size; virt_mb(); ring->intf->in_cons = cons; From 2acd800de8827b7cdce8cb5c2c63b8e8608ec8e3 Mon Sep 17 00:00:00 2001 From: Zhao Zhang Date: Sat, 20 Jun 2026 23:53:03 +0800 Subject: [PATCH 10/39] 9p: fix caches_show() out-of-bounds write The sysfs show handler for /sys/fs/9p/caches appends cache tags with snprintf(buf + count, limit, ...) and then advances count and limit by the return value. This is incorrect for truncation because snprintf() returns the full would-have-been length, not the number of bytes stored. Once the accumulated output exceeds PAGE_SIZE, count can advance past the sysfs buffer and limit can become negative. A later iteration then passes an out-of-bounds destination pointer and an oversized size_t into snprintf(), leading to an out-of-bounds write. Use sysfs_emit_at() for the append instead. It follows the sysfs buffer contract and returns the number of bytes actually stored, so the offset remains bounded even when the output is truncated. Fixes: 86db0c32f16c ("9p: fix /sys/fs/9p/caches overwriting itself") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Zhao Zhang Co-authored-by: Ren Wei Signed-off-by: Ren Wei Message-ID: Signed-off-by: Dominique Martinet --- fs/9p/v9fs.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c index f426cee37414..b30ca5145fae 100644 --- a/fs/9p/v9fs.c +++ b/fs/9p/v9fs.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -617,21 +618,14 @@ static ssize_t caches_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { - ssize_t n = 0, count = 0, limit = PAGE_SIZE; + ssize_t count = 0; struct v9fs_session_info *v9ses; spin_lock(&v9fs_sessionlist_lock); list_for_each_entry(v9ses, &v9fs_sessionlist, slist) { - if (v9ses->cachetag) { - n = snprintf(buf + count, limit, "%s\n", v9ses->cachetag); - if (n < 0) { - count = n; - break; - } - - count += n; - limit -= n; - } + if (v9ses->cachetag) + count += sysfs_emit_at(buf, count, "%s\n", + v9ses->cachetag); } spin_unlock(&v9fs_sessionlist_lock); From 64f350a594f97afd590a278f4c4d9c1e99092f14 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sat, 25 Jul 2026 07:14:48 +0530 Subject: [PATCH 11/39] 9p: don't WARN_ONCE on racy symlink fid lookup failure v9fs_init_request() WARN_ONCE()s when v9fs_fid_lookup() fails for a symlink read via the page cache. This is a benign TOCTOU race: a concurrent unlink()/rename() can remove the target between the walk finding the dentry and p9_client_walk() completing, so the lookup legitimately fails with -ENOENT (observed: dentry=/file0, err=-2). With panic_on_warn=1, this WARN escalates to a full kernel panic -- an unprivileged local DoS reachable by racing mount() against a concurrent unlink/rename. Return the error directly instead of WARN_ONCE(). Reported-by: syzbot+344c09c64fcd8d3d2782@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=344c09c64fcd8d3d2782 Signed-off-by: Deepanshu Kartikey Message-ID: <20260725014448.18711-1-kartikey406@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_addr.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 13cf87a5f90c..ec23f05e293f 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -166,13 +166,16 @@ static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file) goto no_fid; p9_fid_get(fid); } else if (S_ISLNK(rreq->inode->i_mode)) { + /* racing unlink/rename can make this fail with -ENOENT; + * that's expected, not a kernel bug, so don't WARN + */ dentry = d_find_any_alias(rreq->inode); if (!dentry) goto no_fid; fid = v9fs_fid_lookup(dentry); dput(dentry); if (IS_ERR(fid)) - goto no_fid; + return PTR_ERR(fid); } else { fid = v9fs_fid_find_inode(rreq->inode, writing, INVALID_UID, true); if (!fid) From 74795e00cfa797023a177db10c95d1d2776f27e6 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 14 Aug 2026 07:08:26 +0300 Subject: [PATCH 12/39] 9p: simplify v9fs_cache_session_get_cookie() Use the convenient 'strreplace()' to simplify 'v9fs_cache_session_get_cookie()'. Signed-off-by: Dmitry Antipov Message-ID: <20260814040826.1213127-1-dmantipov@yandex.ru> Signed-off-by: Dominique Martinet --- fs/9p/cache.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/9p/cache.c b/fs/9p/cache.c index 12c0ae29f185..194a912158ac 100644 --- a/fs/9p/cache.c +++ b/fs/9p/cache.c @@ -20,17 +20,14 @@ int v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses, const char *dev_name) { struct fscache_volume *vcookie; - char *name, *p; + char *name; name = kasprintf(GFP_KERNEL, "9p,%s,%s", dev_name, v9ses->cachetag ?: v9ses->aname); if (!name) return -ENOMEM; - for (p = name; *p; p++) - if (*p == '/') - *p = ';'; - + strreplace(name, '/', ';'); vcookie = fscache_acquire_volume(name, NULL, NULL, 0); p9_debug(P9_DEBUG_FSC, "session %p get volume %p (%s)\n", v9ses, vcookie, name); From 33625427a38288bc54c5821203e198a661e6b8c2 Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Thu, 9 Jul 2026 18:17:41 +0200 Subject: [PATCH 13/39] net/9p: add vsock transport Add vsock as a transport option for 9P client connections. This allows mounting 9P filesystems over VM sockets without requiring TCP/IP networking or additional userspace tools, which can be useful with hypervisors other than Qemu/KVM, for example Hyper-V/WSL which is used by podman machine on Windows[1][2]. The implementation extends trans_fd.c with vsock support, reusing the existing socket infrastructure. A new p9_fd_create_vsock() function handles vsock connection setup by parsing the CID from the mount source, creating an AF_VSOCK socket, and connecting to the specified endpoint. All other transport operations (close, request, cancel) use the shared fd transport implementation. The privport mount option is not currently implemented for vsock and returns -EOPNOTSUPP if specified. Add CONFIG_NET_9P_VSOCK option that conditionally compiles vsock support into 9pnet_fd.ko. This follows the pattern where socket-based transports (TCP, Unix, vsock) share trans_fd.c, while specialized hardware transports (virtio, xen, rdma) have dedicated files. Usage: mount -t 9p -o trans=vsock[,port=] /mnt/point Link: https://github.com/podman-container-tools/podman/blob/main/cmd/podman/machine/server9p.go [1] Link: https://github.com/podman-container-tools/podman/blob/main/cmd/podman/machine/client9p.go [2] Signed-off-by: Matus Skvarla Reviewed-by: Stefan Hajnoczi Acked-by: Stefano Garzarella Message-ID: <20260709161741.15138-1-skvarlamatus@gmail.com> Signed-off-by: Dominique Martinet --- Documentation/filesystems/9p.rst | 10 ++++ include/net/9p/client.h | 10 +++- net/9p/Kconfig | 10 ++++ net/9p/trans_fd.c | 98 +++++++++++++++++++++++++++++++- 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/Documentation/filesystems/9p.rst b/Documentation/filesystems/9p.rst index 3f65db648db0..44b9e1b5eab7 100644 --- a/Documentation/filesystems/9p.rst +++ b/Documentation/filesystems/9p.rst @@ -50,6 +50,14 @@ mount points. Each 9P export is seen by the client as a virtio device with an associated "mount_tag" property. Available mount tags can be seen by reading /sys/bus/virtio/drivers/9pnet_virtio/virtio/mount_tag files. +For server accessible over vsock (VM sockets):: + + mount -t 9p -o trans=vsock[,port=] /mnt/9 + +where CID is the vsock Context ID of the remote endpoint (2 for host from +a guest VM, 3+ for guest VMs from the host). The port to connect to can be +specified with the port option (default: 564). + USBG Usage ========== @@ -117,6 +125,8 @@ Options tcp specifying a normal TCP/IP connection fd used passed file descriptors for connection (see rfdno and wfdno) + vsock connect over VM sockets (vsock) using + CID as the mount source virtio connect to the next virtio channel available (from QEMU with trans_virtio module) rdma connect to a specified RDMA channel diff --git a/include/net/9p/client.h b/include/net/9p/client.h index f3aead8939ef..262c2a5db078 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -122,8 +122,12 @@ struct p9_client { struct { u16 port; bool privport; - } tcp; +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) + struct { + u32 port; + } vsock; +#endif } trans_opts; struct idr fids; @@ -151,13 +155,13 @@ struct p9_client_opts { * struct p9_fd_opts - per-transport options for fd transport * @rfd: file descriptor for reading (trans=fd) * @wfd: file descriptor for writing (trans=fd) - * @port: port to connect to (trans=tcp) + * @port: port to connect to (trans=tcp, trans=vsock) * @privport: port is privileged */ struct p9_fd_opts { int rfd; int wfd; - u16 port; + u32 port; bool privport; }; diff --git a/net/9p/Kconfig b/net/9p/Kconfig index 22f8c167845d..b0a6342e3475 100644 --- a/net/9p/Kconfig +++ b/net/9p/Kconfig @@ -32,6 +32,16 @@ config NET_9P_VIRTIO This builds support for a transports between guest partitions and a host partition. +config NET_9P_VSOCK + depends on NET_9P_FD + depends on VSOCKETS + bool "9P VSOCK Transport" + help + This builds support for a transport for 9pfs over + virtual sockets (vsock). This is useful for communication + between virtual machines and their host without requiring + TCP/IP networking configuration or additional userspace software. + config NET_9P_XEN depends on XEN select XEN_XENBUS_FRONTEND diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index dce963332a04..159d57e41e56 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -28,6 +28,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) +#include +#endif #include /* killme */ @@ -36,6 +39,9 @@ static struct p9_trans_module p9_tcp_trans; static struct p9_trans_module p9_fd_trans; +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) +static struct p9_trans_module p9_vsock_trans; +#endif enum { Rworksched = 1, /* read work scheduled or running */ @@ -714,6 +720,12 @@ static int p9_fd_show_options(struct seq_file *m, struct p9_client *clnt) if (clnt->trans_opts.fd.wfd != ~0) seq_printf(m, ",wfd=%u", clnt->trans_opts.fd.wfd); } +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) + else if (clnt->trans_mod == &p9_vsock_trans) { + if (clnt->trans_opts.vsock.port != P9_FD_PORT) + seq_printf(m, ",port=%u", clnt->trans_opts.vsock.port); + } +#endif return 0; } @@ -875,7 +887,7 @@ p9_fd_create_tcp(struct p9_client *client, struct fs_context *fc) const char *addr = fc->source; struct v9fs_context *ctx = fc->fs_private; int err; - char port_str[6]; + char port_str[11]; struct socket *csocket; struct sockaddr_storage stor = { 0 }; struct p9_fd_opts opts; @@ -965,6 +977,67 @@ p9_fd_create_unix(struct p9_client *client, struct fs_context *fc) return p9_socket_open(client, csocket); } +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) +static int +p9_fd_create_vsock(struct p9_client *client, struct fs_context *fc) +{ + const char *addr = fc->source; + struct v9fs_context *ctx = fc->fs_private; + int err; + struct socket *csocket; + struct sockaddr_vm addr_vm = { 0 }; + struct p9_fd_opts opts; + unsigned int cid; + + /* opts are already parsed in context */ + opts = ctx->fd_opts; + + if (opts.privport) { + // TODO: implement vsock privileged port binding + pr_err("%s (%d): privport currently not implemented for vsock\n", + __func__, task_pid_nr(current)); + return -EOPNOTSUPP; + } + + if (!addr) + return -EINVAL; + + err = kstrtouint(addr, 10, &cid); + if (err < 0) { + pr_err("%s (%d): invalid CID: %s\n", + __func__, task_pid_nr(current), addr); + return err; + } + + csocket = NULL; + + client->trans_opts.vsock.port = opts.port; + err = __sock_create(fc->net_ns, AF_VSOCK, + SOCK_STREAM, 0, &csocket, 1); + if (err) { + pr_err("%s (%d): problem creating socket\n", + __func__, task_pid_nr(current)); + return err; + } + + addr_vm.svm_family = AF_VSOCK; + addr_vm.svm_port = opts.port; + addr_vm.svm_cid = cid; + + err = READ_ONCE(csocket->ops)->connect(csocket, + (struct sockaddr_unsized *)&addr_vm, + sizeof(addr_vm), 0); + if (err < 0) { + pr_err("%s (%d): problem connecting socket to %s:%u\n", + __func__, task_pid_nr(current), addr, opts.port); + sock_release(csocket); + return err; + } + + return p9_socket_open(client, csocket); +} +#endif /* CONFIG_NET_9P_VSOCK */ + static int p9_fd_create(struct p9_client *client, struct fs_context *fc) { @@ -1035,6 +1108,23 @@ static struct p9_trans_module p9_fd_trans = { }; MODULE_ALIAS_9P("fd"); +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) +static struct p9_trans_module p9_vsock_trans = { + .name = "vsock", + .maxsize = MAX_SOCK_BUF, + .def = false, + .supports_vmalloc = true, + .create = p9_fd_create_vsock, + .close = p9_fd_close, + .request = p9_fd_request, + .cancel = p9_fd_cancel, + .cancelled = p9_fd_cancelled, + .show_options = p9_fd_show_options, + .owner = THIS_MODULE, +}; +MODULE_ALIAS_9P("vsock"); +#endif + /** * p9_poll_workfn - poll worker thread * @work: work queue @@ -1072,6 +1162,9 @@ static int __init p9_trans_fd_init(void) v9fs_register_trans(&p9_tcp_trans); v9fs_register_trans(&p9_unix_trans); v9fs_register_trans(&p9_fd_trans); +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) + v9fs_register_trans(&p9_vsock_trans); +#endif return 0; } @@ -1082,6 +1175,9 @@ static void __exit p9_trans_fd_exit(void) v9fs_unregister_trans(&p9_tcp_trans); v9fs_unregister_trans(&p9_unix_trans); v9fs_unregister_trans(&p9_fd_trans); +#if IS_ENABLED(CONFIG_NET_9P_VSOCK) + v9fs_unregister_trans(&p9_vsock_trans); +#endif } module_init(p9_trans_fd_init); From b3d07d64832c0be1f84b941a0c538f1150b6118b Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 13 Sep 2026 12:27:40 -0700 Subject: [PATCH 14/39] 9p/xen: Use array for data rings Store the fixed set of Xen 9p data rings in the frontend private allocation instead of allocating a separate rings array. This keeps the data ring storage tied to the frontend lifetime and simplifies the allocation and cleanup paths. Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Message-ID: <20260913192740.29668-1-rosenp@gmail.com> Signed-off-by: Dominique Martinet --- net/9p/trans_xen.c | 67 ++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 8eea0da8797f..5a0b5b83e8ab 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -55,7 +55,7 @@ struct xen_9pfs_front_priv { char *tag; struct p9_client *client; - struct xen_9pfs_dataring *rings; + struct xen_9pfs_dataring rings[XEN_9PFS_NUM_RINGS]; }; static LIST_HEAD(xen_9pfs_devs); @@ -279,41 +279,37 @@ static void xen_9pfs_front_free(struct xen_9pfs_front_priv *priv) { int i, j; - if (priv->rings) { - for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { - struct xen_9pfs_dataring *ring = &priv->rings[i]; + for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { + struct xen_9pfs_dataring *ring = &priv->rings[i]; - cancel_work_sync(&ring->work); + cancel_work_sync(&ring->work); - if (!ring->intf) - break; - if (ring->irq >= 0) { - unbind_from_irqhandler(ring->irq, ring); - ring->irq = -1; - } - if (ring->data.in) { - for (j = 0; j < (1 << ring->intf->ring_order); - j++) { - grant_ref_t ref; - - ref = ring->intf->ref[j]; - gnttab_end_foreign_access(ref, NULL); - ring->intf->ref[j] = INVALID_GRANT_REF; - } - free_pages_exact(ring->data.in, - 1UL << (ring->intf->ring_order + - XEN_PAGE_SHIFT)); - ring->data.in = NULL; - ring->data.out = NULL; - } - if (ring->ref != INVALID_GRANT_REF) { - gnttab_end_foreign_access(ring->ref, NULL); - ring->ref = INVALID_GRANT_REF; - } - free_page((unsigned long)ring->intf); - ring->intf = NULL; + if (!ring->intf) + break; + if (ring->irq >= 0) { + unbind_from_irqhandler(ring->irq, ring); + ring->irq = -1; } - kfree(priv->rings); + if (ring->data.in) { + for (j = 0; j < (1 << ring->intf->ring_order); j++) { + grant_ref_t ref; + + ref = ring->intf->ref[j]; + gnttab_end_foreign_access(ref, NULL); + ring->intf->ref[j] = INVALID_GRANT_REF; + } + free_pages_exact(ring->data.in, + 1UL << (ring->intf->ring_order + + XEN_PAGE_SHIFT)); + ring->data.in = NULL; + ring->data.out = NULL; + } + if (ring->ref != INVALID_GRANT_REF) { + gnttab_end_foreign_access(ring->ref, NULL); + ring->ref = INVALID_GRANT_REF; + } + free_page((unsigned long)ring->intf); + ring->intf = NULL; } kfree(priv->tag); kfree(priv); @@ -452,11 +448,6 @@ static int xen_9pfs_front_init(struct xenbus_device *dev) if (!priv) return -ENOMEM; priv->dev = dev; - priv->rings = kzalloc_objs(*priv->rings, XEN_9PFS_NUM_RINGS); - if (!priv->rings) { - kfree(priv); - return -ENOMEM; - } for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { priv->rings[i].priv = priv; From 7b6f2e3c29fb4d76f1e4973280792eabbd526602 Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Tue, 9 Jun 2026 23:52:01 +0800 Subject: [PATCH 15/39] 9p/xen: drain response work after unbinding IRQ commit ea4f1009408e ("9p/xen : Fix use after free bug in xen_9pfs_front_remove due to race condition") added cancel_work_sync() to keep xen_9pfs_front_free() from freeing a ring while p9_xen_response() is still running. The work is currently drained before the event channel IRQ is unbound. That leaves a race window where xen_9pfs_front_event_handler() can run after cancel_work_sync() returns and queue p9_xen_response() again. The following unbind_from_irqhandler() synchronizes with the IRQ handler, but it does not cancel work that the handler already queued. The teardown can then free the ring and private data while the response work remains pending, and the worker later dereferences ring->priv and priv->client. Unbind the event channel IRQ before draining the work. Once the IRQ is unbound, no new response work can be queued by the backend event channel, and cancel_work_sync() waits for any work that was queued before or during the unbind. It is then safe to release the ring resources. Fixes: ea4f1009408e ("9p/xen : Fix use after free bug in xen_9pfs_front_remove due to race condition") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: GLM:GLM-5.1 Signed-off-by: Yizhou Zhao Message-ID: <20260609155202.1706-1-zhaoyz24@mails.tsinghua.edu.cn> Signed-off-by: Dominique Martinet --- net/9p/trans_xen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 5a0b5b83e8ab..9ee1ac9143d4 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -282,14 +282,14 @@ static void xen_9pfs_front_free(struct xen_9pfs_front_priv *priv) for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { struct xen_9pfs_dataring *ring = &priv->rings[i]; - cancel_work_sync(&ring->work); - if (!ring->intf) break; if (ring->irq >= 0) { unbind_from_irqhandler(ring->irq, ring); ring->irq = -1; } + cancel_work_sync(&ring->work); + if (ring->data.in) { for (j = 0; j < (1 << ring->intf->ring_order); j++) { grant_ref_t ref; From a869fe526caa175924863ea570ac4bb40ccbb1ac Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:11:59 -0700 Subject: [PATCH 16/39] 9p: Add const and unsigned to wnames allocation type In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned type is "const unsigned char **", but the converted allocation type would be "char **", which differs in both the const qualifier and the signedness of the character type. Take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: fs/9p/fid.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Message-ID: <20260917211158.i.129-kees@kernel.org> Signed-off-by: Dominique Martinet --- fs/9p/fid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/9p/fid.c b/fs/9p/fid.c index 76242d450aa7..65dc7972b396 100644 --- a/fs/9p/fid.c +++ b/fs/9p/fid.c @@ -154,7 +154,7 @@ static int build_path_from_dentry(struct v9fs_session_info *v9ses, for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent) n++; - wnames = kmalloc_array(n, sizeof(char *), GFP_KERNEL); + wnames = kmalloc_array(n, sizeof(*wnames), GFP_KERNEL); if (!wnames) goto err_out; From 8bb0279930cc90975f03bad4d83282613e5f57fa Mon Sep 17 00:00:00 2001 From: Haobin Wu <853555@gmail.com> Date: Fri, 18 Sep 2026 10:48:49 +0800 Subject: [PATCH 17/39] 9p: skip intermediate directory entry name copy in p9dirent_read() v9fs_dir_readdir_dotl() decodes each entry of a Rreaddir reply with p9dirent_read(), which parses it through p9pdu_readf("Qqbs"). The 's' conversion in p9pdu_vreadf() already allocates a NUL-terminated copy of the name from the wire buffer; p9dirent_read() then strscpy()s that copy into the fixed 256-byte p9_dirent::d_name and frees the original. The wire format carries the name length in 16 bits, so a name longer than 255 bytes is valid on the wire and may well be valid on the server's filesystem, but it makes strscpy() return -E2BIG. v9fs_dir_readdir_dotl() turns that into -EIO and aborts getdents64(), so every entry after the long one disappears from the listing. Drop the second copy: keep the string allocated by p9pdu_vreadf() in p9_dirent and let v9fs_dir_readdir_dotl(), its only user, free it once dir_emit() has consumed it. p9_dirent is a short-lived stack object, so the string's lifetime does not change. Note that the VFS only rejects names of PATH_MAX bytes or more in verify_dirent_name(), so after this change a name between NAME_MAX and PATH_MAX is returned by getdents64() even though any later lookup on it fails with -ENAMETOOLONG. The next patch skips such entries. Fixes: 7751bdb3a095 ("9p: readdir implementation for 9p2000.L") Closes: https://github.com/microsoft/WSL/issues/41192 Signed-off-by: Haobin Wu <853555@gmail.com> Reviewed-by: Christian Schoenebeck Message-ID: <20260918024851.51229-2-853555@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_dir.c | 6 +++++- include/net/9p/client.h | 2 +- net/9p/protocol.c | 12 ++---------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c index 323f85352f6a..733ec42258bc 100644 --- a/fs/9p/vfs_dir.c +++ b/fs/9p/vfs_dir.c @@ -196,8 +196,12 @@ static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx) if (!dir_emit(ctx, curdirent.d_name, strlen(curdirent.d_name), QID2INO(&curdirent.qid), - curdirent.d_type)) + curdirent.d_type)) { + kfree(curdirent.d_name); return 0; + } + + kfree(curdirent.d_name); ctx->pos = curdirent.d_off; rdir->head += err; diff --git a/include/net/9p/client.h b/include/net/9p/client.h index 262c2a5db078..e4e5cb102fb1 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -274,7 +274,7 @@ struct p9_dirent { struct p9_qid qid; u64 d_off; unsigned char d_type; - char d_name[256]; + char *d_name; }; struct iov_iter; diff --git a/net/9p/protocol.c b/net/9p/protocol.c index 67b0586d807f..d4335884e0c0 100644 --- a/net/9p/protocol.c +++ b/net/9p/protocol.c @@ -770,7 +770,7 @@ int p9dirent_read(struct p9_client *clnt, char *buf, int len, { struct p9_fcall fake_pdu; int ret; - char *nameptr; + char *nameptr = NULL; fake_pdu.size = len; fake_pdu.capacity = len; @@ -785,15 +785,7 @@ int p9dirent_read(struct p9_client *clnt, char *buf, int len, return ret; } - ret = strscpy(dirent->d_name, nameptr, sizeof(dirent->d_name)); - if (ret < 0) { - p9_debug(P9_DEBUG_ERROR, - "On the wire dirent name too long: %s\n", - nameptr); - kfree(nameptr); - return ret; - } - kfree(nameptr); + dirent->d_name = nameptr; return fake_pdu.offset; } From edd807dc2031c62f8358b81a48956ac762bc8a1d Mon Sep 17 00:00:00 2001 From: Haobin Wu <853555@gmail.com> Date: Fri, 18 Sep 2026 10:48:50 +0800 Subject: [PATCH 18/39] 9p: skip directory entries with names longer than NAME_MAX The 9p wire format carries directory entry names of up to 65535 bytes and nothing on the client checks them against NAME_MAX. Since the previous patch, v9fs_dir_readdir_dotl() passes such names straight to dir_emit(), and the VFS only rejects names of PATH_MAX bytes or more in verify_dirent_name(), as -EIO, which again fails the whole getdents64() call. A name between NAME_MAX and PATH_MAX is therefore returned to userspace even though every later operation on it fails with -ENAMETOOLONG, and POSIX requires readdir() to only return components of at most NAME_MAX bytes. Nothing can use such an entry, so skip it instead of returning it or failing the listing, reusing the strlen() result that was already computed for dir_emit(). Log the skipped entry at P9_DEBUG_ERROR, the same level as the strscpy() failure message this replaces. Suggested-by: Dominique Martinet Link: https://lore.kernel.org/all/vz5bum547fqyxf5z4m3x7tuqkuq52jlopm65t7hvynqeulh7i3@2t4wnhfxs7qv/ Signed-off-by: Haobin Wu <853555@gmail.com> Reviewed-by: Christian Schoenebeck Message-ID: <20260918024851.51229-3-853555@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_dir.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c index 733ec42258bc..6723550f62fd 100644 --- a/fs/9p/vfs_dir.c +++ b/fs/9p/vfs_dir.c @@ -184,6 +184,7 @@ static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx) } while (rdir->head < rdir->tail) { + size_t namelen; err = p9dirent_read(fid->clnt, rdir->buf + rdir->head, rdir->tail - rdir->head, @@ -193,10 +194,14 @@ static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx) return -EIO; } - if (!dir_emit(ctx, curdirent.d_name, - strlen(curdirent.d_name), - QID2INO(&curdirent.qid), - curdirent.d_type)) { + namelen = strlen(curdirent.d_name); + if (namelen > NAME_MAX) { + p9_debug(P9_DEBUG_ERROR, + "skip dentry: name length %zu > NAME_MAX\n", + namelen); + } else if (!dir_emit(ctx, curdirent.d_name, namelen, + QID2INO(&curdirent.qid), + curdirent.d_type)) { kfree(curdirent.d_name); return 0; } From 55d6732b8015705a657d96fc5bd37fbed349624a Mon Sep 17 00:00:00 2001 From: Haobin Wu <853555@gmail.com> Date: Fri, 18 Sep 2026 10:48:51 +0800 Subject: [PATCH 19/39] 9p: skip over-long directory entry names for legacy 9p2000 too The legacy 9p2000 and 9p2000.u readdir path, v9fs_dir_readdir(), parses each entry with p9stat_read() and, unlike the 9p2000.L path, never had a 256-byte name limit: a name of any length up to the wire maximum was handed to dir_emit(). getdents64() therefore returned names longer than NAME_MAX that no later syscall can use, and failed with -EIO for names of PATH_MAX bytes or more. Apply the same NAME_MAX check as the previous patch does for v9fs_dir_readdir_dotl(), so both protocol variants behave the same. Note that this is a behaviour change for 9p2000 and 9p2000.u, Name length 256..4095: - before: listed, but unusable (ENAMETOOLONG) - now: entry skipped, fetch continues Name length >=4096: - before: aborts the entire fetch (similar to 9p2000.L side) - now: entry skipped, fetch continues Suggested-by: Christian Schoenebeck Link: https://lore.kernel.org/all/3910569.MHq7AAxBmi@weasel/ Signed-off-by: Haobin Wu <853555@gmail.com> Message-ID: <20260918024851.51229-4-853555@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_dir.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c index 6723550f62fd..b561f4ce020e 100644 --- a/fs/9p/vfs_dir.c +++ b/fs/9p/vfs_dir.c @@ -85,7 +85,6 @@ static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen) static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx) { - bool over; struct p9_wstat st; int err = 0; struct p9_fid *fid; @@ -125,6 +124,9 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx) rdir->offset = ctx->pos; } while (rdir->head < rdir->tail) { + bool over = false; + size_t namelen; + err = p9stat_read(fid->clnt, rdir->buf + rdir->head, rdir->tail - rdir->head, &st); if (err <= 0) { @@ -132,8 +134,15 @@ static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx) return -EIO; } - over = !dir_emit(ctx, st.name, strlen(st.name), - QID2INO(&st.qid), dt_type(&st)); + namelen = strlen(st.name); + if (namelen > NAME_MAX) { + p9_debug(P9_DEBUG_ERROR, + "skip dentry: name length %zu > NAME_MAX\n", + namelen); + } else { + over = !dir_emit(ctx, st.name, namelen, + QID2INO(&st.qid), dt_type(&st)); + } p9stat_free(&st); if (over) return 0; From 6f401e350939442b2ac905f7e5ac952e3802a5d8 Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Sun, 7 Jun 2026 21:01:16 +0800 Subject: [PATCH 20/39] net/9p/usbg: Fix use-after-free in disable_usb9pfs() disable_usb9pfs() frees the IN and OUT usb_request objects before it disables the corresponding endpoints. If either request is still queued, the later usb_ep_disable() call cancels the endpoint queue and the UDC driver can still access the already freed request. With dummy_hcd and KASAN, this is reproducible by queueing the OUT request and then disconnecting the configfs gadget: BUG: KASAN: slab-use-after-free in dummy_disable+0x2b4/0x300 Read of size 8 at addr ffff888009702400 by task sh/1 usb_ep_disable+0x8e/0x1f0 usb9pfs_func_unbind+0x193/0x350 gadget_dev_desc_UDC_store+0x135/0x280 dummy_free_request() also warns because the request is freed while its queue entry is still linked. Disable both endpoints before freeing the request objects. This lets usb_ep_disable() cancel any queued transfers and invoke the completion callback while the request storage is still valid. The request objects are then freed only after they have been removed from the endpoint queues. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: GLM:GLM-5.1 Signed-off-by: Yizhou Zhao Acked-by: Michael Grzeschik Message-ID: <20260607130118.16579-1-zhaoyz24@mails.tsinghua.edu.cn> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 9fd7801d56ab..f4ff0349166b 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -278,6 +278,9 @@ static void disable_usb9pfs(struct f_usb9pfs *usb9pfs) struct usb_composite_dev *cdev = usb9pfs->function.config->cdev; + disable_ep(cdev, usb9pfs->in_ep); + disable_ep(cdev, usb9pfs->out_ep); + if (usb9pfs->in_req) { usb_ep_free_request(usb9pfs->in_ep, usb9pfs->in_req); usb9pfs->in_req = NULL; @@ -287,9 +290,6 @@ static void disable_usb9pfs(struct f_usb9pfs *usb9pfs) usb_ep_free_request(usb9pfs->out_ep, usb9pfs->out_req); usb9pfs->out_req = NULL; } - - disable_ep(cdev, usb9pfs->in_ep); - disable_ep(cdev, usb9pfs->out_ep); dev_dbg(&cdev->gadget->dev, "%s disabled\n", usb9pfs->function.name); } From f81cdb9164806eb2df1e1c6df402e03bff7dde31 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 22 Sep 2026 00:25:54 +0200 Subject: [PATCH 21/39] net/9p/usbg: also disable endpoints on p9_usbg_close The close function has to fully reverse the state change of 9p_create (mount) and the potential call of set_alt(1). This includes to ensure that the usage of the endpoints is not active any more. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-1-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index f4ff0349166b..55bdcef1f9c5 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -473,6 +473,8 @@ static void p9_usbg_close(struct p9_client *client) mutex_lock(&usb9pfs_lock); dev->inuse = false; mutex_unlock(&usb9pfs_lock); + + disable_usb9pfs(usb9pfs); } static int p9_usbg_request(struct p9_client *client, struct p9_req_t *p9_req) From 795f8eeca01d1c9ca3d9a450385b7745ace4e53a Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 22 Sep 2026 00:25:55 +0200 Subject: [PATCH 22/39] net/9p/usbg: set client to Disconnected on usb9pfs_disable This patch is setting the client status to Disconnected, when the client is still in use. Otherwise a disconnected usb cable would run any use of the mount to faults. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-2-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 55bdcef1f9c5..002e0a5842ab 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -757,7 +757,12 @@ static int usb9pfs_set_alt(struct usb_function *f, static void usb9pfs_disable(struct usb_function *f) { struct f_usb9pfs *usb9pfs = func_to_usb9pfs(f); + unsigned long flags; + spin_lock_irqsave(&usb9pfs->lock, flags); + if (usb9pfs->client) + usb9pfs->client->status = Disconnected; + spin_unlock_irqrestore(&usb9pfs->lock, flags); usb9pfs_clear_tx(usb9pfs); } From 08c6b9f484a406d9d87c5b2580725d2e0597f3b9 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 22 Sep 2026 00:25:56 +0200 Subject: [PATCH 23/39] net/9p/usbg: always reset completion when disconnecting When some tx or rx transfers were pending while closing the connection, the completion handler could catch one pending completion call. To ensure a normal start when mounting again, we have to reset the completion and flush any pending completions. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-3-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 002e0a5842ab..3304986cf136 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -475,6 +475,7 @@ static void p9_usbg_close(struct p9_client *client) mutex_unlock(&usb9pfs_lock); disable_usb9pfs(usb9pfs); + reinit_completion(&usb9pfs->send); } static int p9_usbg_request(struct p9_client *client, struct p9_req_t *p9_req) @@ -764,6 +765,7 @@ static void usb9pfs_disable(struct usb_function *f) usb9pfs->client->status = Disconnected; spin_unlock_irqrestore(&usb9pfs->lock, flags); usb9pfs_clear_tx(usb9pfs); + reinit_completion(&usb9pfs->send); } static struct usb_function *usb9pfs_alloc(struct usb_function_instance *fi) From cd1988cf723c2eae87112b198bf90b0aebfeaf31 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 22 Sep 2026 00:25:57 +0200 Subject: [PATCH 24/39] net/9p/usbg: call disable_usb9pfs() from usb9pfs_disable() usb9pfs_disable() is the usb_function ->disable callback, invoked by the composite gadget core whenever the host resets or unconfigures the gadget. The gadget function API requires this callback to disable its endpoints; every other in-tree function driver does so from its ->disable hook. usb9pfs_disable() never called disable_usb9pfs(), so the IN/OUT endpoints and their usb_request objects were left active and allocated across a host-driven disable. When the host later reconfigures the device, enable_usb9pfs() calls alloc_requests() again and unconditionally overwrites usb9pfs->in_req/out_req, permanently leaking the previous allocations, while the endpoints themselves are left enabled underneath the function's own idea of being disabled. Call disable_usb9pfs() before reinit_completion(&usb9pfs->send), mirroring the same sequence already used in p9_usbg_close(). Placing it before the reinit_completion() also ensures usb_ep_disable() has synchronously flushed any requests still queued in hardware before the completion is reset, closing the same race that motivated resetting the completion here in the first place. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Assisted-by: Claude:claude-opus-4.8 Cc: stable@vger.kernel.org Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-4-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index 3304986cf136..a214d0b6d875 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -765,6 +765,10 @@ static void usb9pfs_disable(struct usb_function *f) usb9pfs->client->status = Disconnected; spin_unlock_irqrestore(&usb9pfs->lock, flags); usb9pfs_clear_tx(usb9pfs); + + if (usb9pfs->in_ep->enabled) + disable_usb9pfs(usb9pfs); + reinit_completion(&usb9pfs->send); } From 5f9807270c7bda8f86b312ed1a8acd7716600555 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 22 Sep 2026 00:25:58 +0200 Subject: [PATCH 25/39] net/9p/usbg: fix out_req buffer leak in disable_usb9pfs usb9pfs->out_req is allocated with alloc_ep_req(), which also allocates the request's data buffer (req->buf). disable_usb9pfs() frees it with plain usb_ep_free_request(), which only frees the usb_request structure itself and leaks req->buf on every close. Use free_ep_req() instead, matching the allocator, as documented by its own comment ("Requests allocated via alloc_ep_req() *must* be freed by free_ep_req()"). Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-5-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index a214d0b6d875..d5e0949f367f 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -287,7 +287,7 @@ static void disable_usb9pfs(struct f_usb9pfs *usb9pfs) } if (usb9pfs->out_req) { - usb_ep_free_request(usb9pfs->out_ep, usb9pfs->out_req); + free_ep_req(usb9pfs->out_ep, usb9pfs->out_req); usb9pfs->out_req = NULL; } dev_dbg(&cdev->gadget->dev, "%s disabled\n", From bbdb38b2e543ffa72dca5450f2535cf998ef1cdd Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 22 Sep 2026 00:25:59 +0200 Subject: [PATCH 26/39] net/9p/usbg: remove bogus context initialization in alloc_requests alloc_requests() initializes usb9pfs->in_req->context to point at the struct f_usb9pfs instance itself. usb9pfs_queue_tx() later overwrites this with the real struct p9_req_t pointer before every transmit, and usb9pfs_tx_complete() clears it back to NULL after each completion, so in_req->context only ever holds a valid p9_req_t once a request has actually been queued. usb9pfs_clear_tx(), however, can run at any time (mount close, gadget disable) independent of whether a request was ever queued. If it runs before the first usb9pfs_transmit(), it reads the leftover sentinel value, type-confuses the struct f_usb9pfs pointer as a struct p9_req_t, and both writes through it (req->t_err = -ECONNRESET) and hands it to p9_client_cb(), which manipulates req->wq and req->refcount at bogus offsets inside f_usb9pfs. This reliably corrupts memory or panics whenever the transport is torn down before any 9p request has been transmitted, e.g. mounting and immediately unmounting, or a cable disconnect racing the very first request. usb9pfs->out_req->context is set the same way but is never read by this transport (usb9pfs_rx_complete() identifies the instance via ep->driver_data instead), so it serves no purpose either. Both endpoints' ->driver_data are already set to usb9pfs in enable_endpoint(), which is what the completion handlers actually use to recover the f_usb9pfs instance. Just drop the leftover ->context assignments; usb_ep_alloc_request()/alloc_ep_req() already return zeroed requests, so in_req->context correctly starts out NULL. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-6-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index d5e0949f367f..fe2e69571732 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -314,10 +314,6 @@ static int alloc_requests(struct usb_composite_dev *cdev, usb9pfs->in_req->complete = usb9pfs_tx_complete; usb9pfs->out_req->complete = usb9pfs_rx_complete; - /* length will be set in complete routine */ - usb9pfs->in_req->context = usb9pfs; - usb9pfs->out_req->context = usb9pfs; - return 0; fail_in: From a478958f4c1181ff3362f8857b489c2d069f9c7d Mon Sep 17 00:00:00 2001 From: Hyungjung Joo Date: Tue, 22 Sep 2026 00:26:00 +0200 Subject: [PATCH 27/39] net/9p/usbg: clear stale client pointer on close p9_usbg_close() tears down the client transport, but usb9pfs keeps using usb9pfs->client from asynchronous TX and RX completion handlers. A late completion can therefore dereference a client that has already been freed during mount teardown. Clear usb9pfs->client under usb9pfs->lock when closing the transport, detach any pending TX request from in_req->context, and make the TX/RX completion handlers bail out once the transport has been detached. This keeps late completions from touching a freed or rebound p9_client. Fixes: a3be076dc174 ("net/9p/usbg: Add new usb gadget function transport") Cc: stable@vger.kernel.org Signed-off-by: Hyungjung Joo Signed-off-by: Michael Grzeschik Message-ID: <20260922-usb9pfsfixes-v1-7-9d8dcc52904c@kernel.org> Signed-off-by: Dominique Martinet --- net/9p/trans_usbg.c | 63 +++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/net/9p/trans_usbg.c b/net/9p/trans_usbg.c index fe2e69571732..45b0ea594211 100644 --- a/net/9p/trans_usbg.c +++ b/net/9p/trans_usbg.c @@ -149,7 +149,8 @@ static void usb9pfs_tx_complete(struct usb_ep *ep, struct usb_request *req) { struct f_usb9pfs *usb9pfs = ep->driver_data; struct usb_composite_dev *cdev = usb9pfs->function.config->cdev; - struct p9_req_t *p9_tx_req = req->context; + struct p9_client *client; + struct p9_req_t *p9_tx_req; unsigned long flags; /* reset zero packages */ @@ -165,18 +166,25 @@ static void usb9pfs_tx_complete(struct usb_ep *ep, struct usb_request *req) ep->name, req->status, req->actual, req->length); spin_lock_irqsave(&usb9pfs->lock, flags); - WRITE_ONCE(p9_tx_req->status, REQ_STATUS_SENT); - - p9_req_put(usb9pfs->client, p9_tx_req); - + client = usb9pfs->client; + p9_tx_req = req->context; req->context = NULL; + if (!client || !p9_tx_req) + goto unlock_complete; + + WRITE_ONCE(p9_tx_req->status, REQ_STATUS_SENT); + + p9_req_put(client, p9_tx_req); + +unlock_complete: spin_unlock_irqrestore(&usb9pfs->lock, flags); complete(&usb9pfs->send); } -static struct p9_req_t *usb9pfs_rx_header(struct f_usb9pfs *usb9pfs, void *buf) +static struct p9_req_t *usb9pfs_rx_header(struct f_usb9pfs *usb9pfs, + struct p9_client *client, void *buf) { struct p9_req_t *p9_rx_req; struct p9_fcall rc; @@ -202,7 +210,7 @@ static struct p9_req_t *usb9pfs_rx_header(struct f_usb9pfs *usb9pfs, void *buf) "mux %p pkt: size: %d bytes tag: %d\n", usb9pfs, rc.size, rc.tag); - p9_rx_req = p9_tag_lookup(usb9pfs->client, rc.tag); + p9_rx_req = p9_tag_lookup(client, rc.tag); if (!p9_rx_req || p9_rx_req->status != REQ_STATUS_SENT) { p9_debug(P9_DEBUG_ERROR, "Unexpected packet tag %d\n", rc.tag); return NULL; @@ -212,7 +220,7 @@ static struct p9_req_t *usb9pfs_rx_header(struct f_usb9pfs *usb9pfs, void *buf) p9_debug(P9_DEBUG_ERROR, "requested packet size too big: %d for tag %d with capacity %zd\n", rc.size, rc.tag, p9_rx_req->rc.capacity); - p9_req_put(usb9pfs->client, p9_rx_req); + p9_req_put(client, p9_rx_req); return NULL; } @@ -220,7 +228,7 @@ static struct p9_req_t *usb9pfs_rx_header(struct f_usb9pfs *usb9pfs, void *buf) p9_debug(P9_DEBUG_ERROR, "No recv fcall for tag %d (req %p), disconnecting!\n", rc.tag, p9_rx_req); - p9_req_put(usb9pfs->client, p9_rx_req); + p9_req_put(client, p9_rx_req); return NULL; } @@ -231,8 +239,10 @@ static void usb9pfs_rx_complete(struct usb_ep *ep, struct usb_request *req) { struct f_usb9pfs *usb9pfs = ep->driver_data; struct usb_composite_dev *cdev = usb9pfs->function.config->cdev; + struct p9_client *client; struct p9_req_t *p9_rx_req; unsigned int req_size = req->actual; + unsigned long flags; int status = REQ_STATUS_RCVD; if (req->status) { @@ -241,9 +251,16 @@ static void usb9pfs_rx_complete(struct usb_ep *ep, struct usb_request *req) return; } - p9_rx_req = usb9pfs_rx_header(usb9pfs, req->buf); - if (!p9_rx_req) + spin_lock_irqsave(&usb9pfs->lock, flags); + client = usb9pfs->client; + if (!client) { + spin_unlock_irqrestore(&usb9pfs->lock, flags); return; + } + + p9_rx_req = usb9pfs_rx_header(usb9pfs, client, req->buf); + if (!p9_rx_req) + goto out_unlock; if (req_size > p9_rx_req->rc.capacity) { dev_err(&cdev->gadget->dev, @@ -257,8 +274,11 @@ static void usb9pfs_rx_complete(struct usb_ep *ep, struct usb_request *req) p9_rx_req->rc.size = req_size; - p9_client_cb(usb9pfs->client, p9_rx_req, status); - p9_req_put(usb9pfs->client, p9_rx_req); + p9_client_cb(client, p9_rx_req, status); + p9_req_put(client, p9_rx_req); + +out_unlock: + spin_unlock_irqrestore(&usb9pfs->lock, flags); complete(&usb9pfs->received); } @@ -412,7 +432,9 @@ static int p9_usbg_create(struct p9_client *client, struct fs_context *fc) client->status = Disconnected; else client->status = Connected; + spin_lock_irq(&usb9pfs->lock); usb9pfs->client = client; + spin_unlock_irq(&usb9pfs->lock); client->trans_mod->maxsize = usb9pfs->buflen; @@ -423,12 +445,19 @@ static int p9_usbg_create(struct p9_client *client, struct fs_context *fc) static void usb9pfs_clear_tx(struct f_usb9pfs *usb9pfs) { + struct p9_client *client; struct p9_req_t *req; + unsigned long flags; - guard(spinlock_irqsave)(&usb9pfs->lock); + spin_lock_irqsave(&usb9pfs->lock, flags); + client = usb9pfs->client; + usb9pfs->client = NULL; + req = usb9pfs->in_req ? usb9pfs->in_req->context : NULL; + if (usb9pfs->in_req) + usb9pfs->in_req->context = NULL; + spin_unlock_irqrestore(&usb9pfs->lock, flags); - req = usb9pfs->in_req->context; - if (!req) + if (!req || !client) return; usb9pfs->in_req->context = NULL; @@ -439,7 +468,7 @@ static void usb9pfs_clear_tx(struct f_usb9pfs *usb9pfs) if (!req->t_err) req->t_err = -ECONNRESET; - p9_client_cb(usb9pfs->client, req, REQ_STATUS_ERROR); + p9_client_cb(client, req, REQ_STATUS_ERROR); usb9pfs->in_req->context = NULL; } From ec8cd74fcf73329ffe3b40e7a3c1afd50e6dc930 Mon Sep 17 00:00:00 2001 From: Ankit Thakur Date: Sat, 24 Oct 2026 14:51:51 +0530 Subject: [PATCH 28/39] 9p: fix header guard comment in acl.h The closing comment of the include guard says FS_9P_XATTR_H, but the guard macro is FS_9P_ACL_H. Fix the comment to match. Signed-off-by: Ankit Thakur Message-ID: <20261024092154.1844-1-codeankit.thakur@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/acl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/9p/acl.h b/fs/9p/acl.h index 333cfcc281da..1d8b58858f6b 100644 --- a/fs/9p/acl.h +++ b/fs/9p/acl.h @@ -51,4 +51,4 @@ static inline int v9fs_acl_mode(struct inode *dir, umode_t *modep, } #endif -#endif /* FS_9P_XATTR_H */ +#endif /* FS_9P_ACL_H */ From 6b18056882abf93ae3a78f67901695998efd8cee Mon Sep 17 00:00:00 2001 From: Ankit Thakur Date: Sat, 24 Oct 2026 14:51:52 +0530 Subject: [PATCH 29/39] 9p: fix typos and wrong parameter docs in vfs_inode.c Fix spelling mistakes in comments (functon -> function, Hadlink -> Hardlink). Also fix the kernel-doc of v9fs_vfs_mkdir(), which described @dir and @dentry as being unlinked instead of being the parent directory and the directory being created. Signed-off-by: Ankit Thakur Message-ID: <20261024092154.1844-2-codeankit.thakur@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_inode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 3829554ca369..efd8aef5a793 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -481,7 +481,7 @@ static int v9fs_at_to_dotl_flags(int flags) } /** - * v9fs_dec_count - helper functon to drop i_nlink. + * v9fs_dec_count - helper function to drop i_nlink. * * If a directory had nlink <= 2 (including . and ..), then we should not drop * the link count, which indicates the underlying exported fs doesn't maintain @@ -673,8 +673,8 @@ v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, /** * v9fs_vfs_mkdir - VFS mkdir hook to create a directory * @idmap: idmap of the mount - * @dir: inode that is being unlinked - * @dentry: dentry that is being unlinked + * @dir: inode of the parent directory + * @dentry: dentry representing the directory being created * @mode: mode for new directory * */ @@ -1130,7 +1130,7 @@ v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode, if (v9fs_proto_dotu(v9ses)) { unsigned int i_nlink; /* - * Hadlink support got added later to the .u extension. + * Hardlink support got added later to the .u extension. * So there can be a server out there that doesn't * support this even with .u extension. That would * just leave us with stat->extension being an empty From 9eb9b583ea3513473c7a366dc1f17075069bb41f Mon Sep 17 00:00:00 2001 From: Ankit Thakur Date: Sat, 24 Oct 2026 14:51:53 +0530 Subject: [PATCH 30/39] 9p: remove duplicated assignment in v9fs_fill_super() The declaration of v9ses assigns it twice (v9ses = v9ses = sb->s_fs_info). Drop the redundant assignment. No functional change. Signed-off-by: Ankit Thakur Message-ID: <20261024092154.1844-3-codeankit.thakur@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c index 94d6b02c221b..f923cf56542c 100644 --- a/fs/9p/vfs_super.c +++ b/fs/9p/vfs_super.c @@ -34,7 +34,7 @@ static const struct super_operations v9fs_super_ops, v9fs_super_ops_dotl; static int v9fs_fill_super(struct super_block *sb) { int ret; - struct v9fs_session_info *v9ses = v9ses = sb->s_fs_info; + struct v9fs_session_info *v9ses = sb->s_fs_info; sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_blocksize_bits = fls(v9ses->maxdata - 1); From 8a152d6a389a8375e272e4eb0f3447dce742bc20 Mon Sep 17 00:00:00 2001 From: Ankit Thakur Date: Sat, 24 Oct 2026 14:51:54 +0530 Subject: [PATCH 31/39] 9p: fix typo in file header comments Fix the spelling mistake contians -> contains in the header comments of vfs_addr.c, vfs_dentry.c and vfs_file.c. Signed-off-by: Ankit Thakur Message-ID: <20261024092154.1844-4-codeankit.thakur@gmail.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_addr.c | 2 +- fs/9p/vfs_dentry.c | 2 +- fs/9p/vfs_file.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index ec23f05e293f..33da8140572e 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * This file contians vfs address (mmap) ops for 9P2000. + * This file contains vfs address (mmap) ops for 9P2000. * * Copyright (C) 2005 by Eric Van Hensbergen * Copyright (C) 2002 by Ron Minnich diff --git a/fs/9p/vfs_dentry.c b/fs/9p/vfs_dentry.c index fa6b7143db98..5b05fa7bbc06 100644 --- a/fs/9p/vfs_dentry.c +++ b/fs/9p/vfs_dentry.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * This file contians vfs dentry ops for the 9P2000 protocol. + * This file contains vfs dentry ops for the 9P2000 protocol. * * Copyright (C) 2004 by Eric Van Hensbergen * Copyright (C) 2002 by Ron Minnich diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c index cddfb4b7ce4e..a983ef866713 100644 --- a/fs/9p/vfs_file.c +++ b/fs/9p/vfs_file.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * This file contians vfs file ops for 9P2000. + * This file contains vfs file ops for 9P2000. * * Copyright (C) 2004 by Eric Van Hensbergen * Copyright (C) 2002 by Ron Minnich From 9b4cf60c6b739461c5b4ec9c52587da6b2608786 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Mon, 5 Oct 2026 21:56:38 +0900 Subject: [PATCH 32/39] 9p/xen: validate the response size from the backend p9_xen_response() takes the length of an incoming response from the ring -- the first field of the 9p header, which the Xen transport reuses as its framing header -- and uses it to advance the consumer index, but never checks that it is at least the header size or no larger than the number of bytes the backend has actually produced. A malicious or buggy backend can post a response whose size is smaller than the header (for example 0). The consumer index then never advances, so the response work re-reads the same ring contents instead of making progress. In testing this raced with the teardown that the malformed reply triggers and dereferenced a freed p9_client: 9pfs 9pfs-0: Wrong req tag=ffff BUG: kernel NULL pointer dereference, address: 0000000000000060 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted 7.2.0-rc5 #6 PREEMPT(lazy) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014 Workqueue: events p9_xen_response RIP: 0010:idr_find+0x4/0x10 RSP: 0018:ffffbf5d80063de8 EFLAGS: 00010202 RAX: 0000000000000001 RBX: ffffa229c21195a0 RCX: ffffa229c2400000 RDX: ffffa229c11c2200 RSI: 000000000000ffff RDI: 0000000000000050 RBP: 000000000000ffff R08: 3fffffffffffdfff R09: ffffffffffffffff R10: 3fffffffffffdfff R11: ffffffff87a60e80 R12: 0000000000000050 R13: 0000000000000000 R14: 000000000000ffff R15: 0000000000100000 FS: 0000000000000000(0000) GS:ffffa22a7661c000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000060 CR3: 000000003ac34002 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: p9_tag_lookup+0x2b/0x90 p9_xen_response+0x17c/0x2e0 process_one_work+0x16a/0x3a0 worker_thread+0x172/0x2e0 kthread+0xdd/0x110 ret_from_fork+0x18b/0x240 ret_from_fork_asm+0x1a/0x30 Modules linked in: CR2: 0000000000000060 ---[ end trace 0000000000000000 ]--- Reject a response whose size is outside [sizeof(header), queued] before using it, matching the validation the backend already applies to incoming requests. Fixes: f66c72bea129 ("xen/9pfs: receive responses") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Reviewed-by: Juergen Gross Message-ID: <20261005125640.274054-1-yhlee@isslab.korea.ac.kr> Signed-off-by: Dominique Martinet --- net/9p/trans_xen.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 9ee1ac9143d4..690d941236b6 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -200,6 +200,14 @@ static void p9_xen_response(struct work_struct *work) masked_prod, &masked_cons, XEN_9PFS_RING_SIZE(ring)); + if (h.size < sizeof(h) || + h.size > xen_9pfs_queued(prod, cons, + XEN_9PFS_RING_SIZE(ring))) { + dev_warn_ratelimited(&priv->dev->dev, + "bad response size %u from backend\n", h.size); + break; + } + req = p9_tag_lookup(priv->client, h.tag); if (!req || req->status != REQ_STATUS_SENT) { dev_warn(&priv->dev->dev, "Wrong req tag=%x\n", h.tag); From a3a6212c7d67baebebd0e4c2cf2ae1b75eb9231e Mon Sep 17 00:00:00 2001 From: Barret Rhoden Date: Thu, 2 Jul 2026 09:09:32 +0000 Subject: [PATCH 33/39] 9p: treat read return values of 0 as EOF If your 9p server shares a synthetic file system such as sysfs, the file size from stat often disagrees with the actual file size. Treat non-error reads of 0 as EOF, except in the case where the request was for 0 bytes. Tested: git@github.com:hugelgupf/p9.git, served "/" on 10.0.2.2 bash-5.3# mount -t 9p -o trans=tcp,port=12345,cache=none 10.0.2.2 /tmp/foo bash-5.3# cat /tmp/foo/sys/devices/system/cpu/online 0-47 Fixes: eb497943fa21 ("9p: Convert to using the netfs helper lib to do reads and caching") Signed-off-by: Barret Rhoden Message-ID: <20260702090941.1298188-1-brho@google.com> Signed-off-by: Dominique Martinet --- fs/9p/vfs_addr.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 33da8140572e..65eea22919b9 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -99,6 +99,7 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq) char *target; unsigned long long pos = subreq->start + subreq->transferred; int total = 0, err, len, n; + size_t to_read = iov_iter_count(&subreq->io_iter); if (S_ISLNK(rreq->inode->i_mode)) { /* p9_client_readlink() must not be called for legacy protocols @@ -131,6 +132,8 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq) __set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags); if (pos + total >= i_size_read(rreq->inode)) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); + if (!err && !total && to_read) + __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); if (!err && total) { subreq->transferred += total; __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); From d037e9c1b40c28eb57ea10947141cad3781b91a7 Mon Sep 17 00:00:00 2001 From: Omokefe Emmanuel Onanaroghene Date: Fri, 2 Oct 2026 04:58:37 +0100 Subject: [PATCH 34/39] 9p: fix kernel-doc warning for enum p9_session_flags kernel-doc cannot parse enum p9_session_flags, since its documentation comment is followed by #define lines instead of the enum declaration: fs/9p/v9fs.h:31 : error: Cannot parse enum! Move the three #define lines below the enum whose values they are derived from, and give V9FS_ACCESS_ANY and V9FS_ACCESS_MASK their own kernel-doc comments with the descriptions they had in the enum comment. Also document the four enum members that had no description, using the corresponding mount option wording from Documentation/filesystems/9p.rst. Fixes: 76381a42e4a5 ("fs/9p: Add access = client option to opt in acl evaluation.") Assisted-by: LLM Signed-off-by: Omokefe Emmanuel Onanaroghene Message-ID: <20261002035837.39703-1-emmaonana18@gmail.com> [Dominique: slight reword, also fixed typo in Doc/ for access=client] Signed-off-by: Dominique Martinet --- Documentation/filesystems/9p.rst | 2 +- fs/9p/v9fs.h | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Documentation/filesystems/9p.rst b/Documentation/filesystems/9p.rst index 44b9e1b5eab7..a791684627b6 100644 --- a/Documentation/filesystems/9p.rst +++ b/Documentation/filesystems/9p.rst @@ -238,7 +238,7 @@ Options any v9fs does single attach and performs all operations as one user - clien + client ACL based access check on the 9p client side for access validation diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index a462bcbfc7da..ea3cfdf2580f 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -21,20 +21,16 @@ * @V9FS_ACCESS_SINGLE: only the mounting user can access the hierarchy * @V9FS_ACCESS_USER: a new attach will be issued for every user (default) * @V9FS_ACCESS_CLIENT: Just like user, but access check is performed on client. - * @V9FS_ACCESS_ANY: use a single attach for all users - * @V9FS_ACCESS_MASK: bit mask of different ACCESS options * @V9FS_POSIX_ACL: POSIX ACLs are enforced + * @V9FS_NO_XATTR: do not offer xattr functions on this mount + * @V9FS_IGNORE_QV: ignore qid.version==0 as a marker to disable data cache + * @V9FS_DIRECT_IO: disable data read/write cache + * @V9FS_SYNC: disable data write cache * @V9FS_NDENTRY_TIMEOUT_SET: Has negative dentry timeout retention time been * overridden by negtimeout mount option * * Session flags reflect options selected by users at mount time */ -#define V9FS_ACCESS_ANY (V9FS_ACCESS_SINGLE | \ - V9FS_ACCESS_USER | \ - V9FS_ACCESS_CLIENT) -#define V9FS_ACCESS_MASK V9FS_ACCESS_ANY -#define V9FS_ACL_MASK V9FS_POSIX_ACL - enum p9_session_flags { V9FS_PROTO_2000U = 0x01, V9FS_PROTO_2000L = 0x02, @@ -49,6 +45,20 @@ enum p9_session_flags { V9FS_NDENTRY_TIMEOUT_SET = 0x400, }; +/** + * V9FS_ACCESS_ANY - use a single attach for all users + */ +#define V9FS_ACCESS_ANY (V9FS_ACCESS_SINGLE | \ + V9FS_ACCESS_USER | \ + V9FS_ACCESS_CLIENT) + +/** + * V9FS_ACCESS_MASK - bit mask of different ACCESS options + */ +#define V9FS_ACCESS_MASK V9FS_ACCESS_ANY + +#define V9FS_ACL_MASK V9FS_POSIX_ACL + /** * enum p9_cache_shortcuts - human readable cache preferences * @CACHE_SC_NONE: disable all caches From 4e915aa8566173b44146bc46177c85afbe6bdf96 Mon Sep 17 00:00:00 2001 From: Yuanfu Xie Date: Mon, 28 Sep 2026 15:25:48 +0800 Subject: [PATCH 35/39] 9p: don't decrement a server-owned zero nlink A 9P2000.L server can report nlink=0 for a regular file that still exists. On a cache=loose or cache=fscache mount, lookup stores that value with set_nlink(). A later successful unlink calls v9fs_dec_count(), which still calls drop_nlink() for a non-directory. drop_nlink() warns, and the unsigned link count wraps to 4294967295. fstat() on a descriptor opened before the unlink shows 0 before and 4294967295 after. The warning on the unpatched kernel, trimmed: WARNING: fs/inode.c:408 at drop_nlink+0xac/0xd0, CPU#1: trigger/188 CPU: 1 UID: 0 PID: 188 Comm: trigger Tainted: G N 7.3.0-rc4-00537-ga3ff15db6820 #1 PREEMPT(lazy) RIP: 0010:drop_nlink+0xac/0xd0 Call Trace: v9fs_remove+0x529/0x750 vfs_unlink+0x2f0/0xbd0 filename_unlinkat+0x340/0x6c0 __x64_sys_unlink+0x46/0x70 do_syscall_64+0xe8/0x4f0 entry_SYSCALL_64_after_hwframe+0x77/0x7f ---[ end trace 0000000000000000 ]--- Directories whose cached nlink is at most 2 already skip this decrement, because the exported filesystem may not maintain nlink (commit ac89b2ef9b559). cache=none skips it as well (commit 574aa0b479947). cache=mmap does not take this path: it sets neither CACHE_META nor CACHE_LOOSE. A regular file on cache=loose or cache=fscache still does. Its count came from GETATTR and may already be zero, so skip the decrement when i_nlink is already 0. Verified on 7.3-rc4-00537-ga3ff15db6820 with only this change. A server that advertises nlink=0 and accepts the unlink no longer warns, and the open descriptor stays at nlink 0. An honest server on the same mount still goes from 1 to 0. A rejected unlink leaves the count unchanged. cache=none is unchanged. With panic_on_warn the unpatched kernel panics on the warning; the patched kernel does not. Fixes: ac89b2ef9b559 ("9p: don't maintain dir i_nlink if the exported fs doesn't either") Cc: stable@vger.kernel.org Signed-off-by: Yuanfu Xie Message-ID: <8B490036-EBB0-4A36-AE39-F294F7C79649@stu.pku.edu.cn> Signed-off-by: Dominique Martinet --- fs/9p/vfs_inode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index efd8aef5a793..76c343202296 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -495,6 +495,12 @@ static int v9fs_at_to_dotl_flags(int flags) * nothing and races with concurrent metadata fetches that may already * have observed the post-unlink value (nlink == 0). * + * The same applies to a regular file whose cached nlink has already been + * driven to zero by the server: in the cached modes the attributes come + * from GETATTR and are applied verbatim, so decrementing a link count the + * server does not maintain only trips the drop_nlink() WARN_ON and + * underflows i_nlink. + * * @inode: inode whose nlink is being dropped */ static void v9fs_dec_count(struct inode *inode) @@ -503,6 +509,8 @@ static void v9fs_dec_count(struct inode *inode) if (!(v9ses->cache & (CACHE_META | CACHE_LOOSE))) return; + if (inode->i_nlink == 0) + return; if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2) drop_nlink(inode); } From ff1baa01a6e1f24741f7428119e6f3a61b995604 Mon Sep 17 00:00:00 2001 From: Yuanfu Xie Date: Tue, 22 Sep 2026 21:15:36 +0800 Subject: [PATCH 36/39] 9p: fall back to the normal open path for non-regular files v9fs_vfs_atomic_open() and v9fs_vfs_atomic_open_dotl() call finish_open(file, dentry, generic_file_open) regardless of the file type the server reported for the created dentry. The supplied callback replaces f_op->open in do_dentry_open(), so blkdev_open() never runs for a server-reported device node: an open() with O_CREAT succeeds with f_op = def_blk_fops while f_mapping still points at the 9p inode, because the bdev mapping swap only happens inside blkdev_open(). The block file methods then derive their block_device from that inode with I_BDEV(); the loads land at the tail of a 656-byte v9fs_inode_cache object (offsets +624 and +632), which KASAN reports as slab-out-of-bounds, and the garbage they read is then dereferenced, so the first read(), write(), ioctl() or close() ends in a NULL pointer write and a panic. In the ioctl(BLKGETSIZE64) variant the value read this way is returned to userspace. The server controls the file type reported for the create, so a malicious server can crash the client this way. KASAN report of the read() variant, on a build of linux-stable 2709dd5ae32f: BUG: KASAN: slab-out-of-bounds in blkdev_read_iter+0x3cd/0x440 Read of size 8 at addr ffff88800828ceb0 by task repro/1 Call Trace: dump_stack_lvl+0x7b/0xa0 print_report+0xd0/0x630 kasan_report+0xe5/0x120 blkdev_read_iter+0x3cd/0x440 vfs_read+0x718/0xa50 ksys_read+0x10f/0x200 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff88800828cc40 which belongs to the cache v9fs_inode_cache of size 656 The buggy address is located 624 bytes inside of allocated 656-byte region [ffff88800828cc40, ffff88800828ced0) A plain open() without O_CREAT is not affected: it runs blkdev_open() and either maps the device correctly or fails with -ENXIO for an rdev with no registered device. When the created inode is not a regular file, clunk the fid the create opened. The unopened fid is already on the dentry. Set FMODE_CREATED and return finish_no_open(file, NULL) so the VFS keeps its dentry reference and calls the inode's ->open. Regular files still use finish_open() with generic_file_open. Fixes: be12af3ef5e6 ("getting rid of 'opened' argument of ->atomic_open() - part 1") Signed-off-by: Yuanfu Xie Message-ID: <908FC420-EC1F-46FC-8526-698F646961A8@stu.pku.edu.cn> Signed-off-by: Dominique Martinet --- fs/9p/vfs_inode.c | 10 ++++++++++ fs/9p/vfs_inode_dotl.c | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 76c343202296..25037cd3b21c 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -810,6 +810,16 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry, v9fs_invalidate_inode_attr(dir); inode = d_inode(dentry); v9inode = V9FS_I(inode); + if (!S_ISREG(inode->i_mode)) { + /* + * Not a regular file. v9fs_create() already stored the + * unopened fid on the dentry. Clunk the opened fid from + * the create and let the VFS call ->open. + */ + p9_fid_put(fid); + file->f_mode |= FMODE_CREATED; + return finish_no_open(file, NULL); + } err = finish_open(file, dentry, generic_file_open); if (unlikely(err)) { p9_fid_put(fid); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 116b29e95f21..1431549b1f4d 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -312,6 +312,18 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, v9fs_fid_add(dentry, &fid); d_instantiate(dentry, inode); + if (!S_ISREG(inode->i_mode)) { + /* + * Not a regular file. The unopened fid is already on the + * dentry. Clunk the opened create fid and let the VFS call + * ->open. + */ + p9_fid_put(ofid); + ofid = NULL; + file->f_mode |= FMODE_CREATED; + err = finish_no_open(file, NULL); + goto out; + } /* Since we are opening a file, assign the open fid to the file */ err = finish_open(file, dentry, generic_file_open); if (err) From 56894cadf21c34abbba7f74b8f5d78dabf69dd39 Mon Sep 17 00:00:00 2001 From: Yuanfu Xie Date: Tue, 22 Sep 2026 21:16:22 +0800 Subject: [PATCH 37/39] 9p: do not store the create fid on special files v9fs_vfs_atomic_open_dotl() always stores the create fid in file->private_data after finish_open(). FIFO and device inodes already have type-specific file_operations that own that field. Overwriting it with the fid makes later read/write/close treat the p9_fid as a pipe_inode_info, which corrupts the fid and can use it after it has been clunked. The non-dotl atomic_open path does not assign the fid this way. Only attach the fid for regular files and directories. Special files do not need the create fid; leave it for the existing p9_fid_put() on the out path so it is clunked before return. KASAN report, linux-stable 4982d3552a3b: BUG: KASAN: slab-use-after-free in anon_pipe_prefill_and_lock+0x376/0x3f0 Read of size 4 at addr ffff88800ab30538 by task repro/1 Call Trace: anon_pipe_prefill_and_lock+0x376/0x3f0 anon_pipe_write+0x127/0x1600 fifo_pipe_write+0x24/0x300 vfs_write+0x659/0xd00 ksys_write+0x10f/0x200 do_syscall_64+0xdd/0x4a0 Fixes: 30d904947459 ("kill struct opendata") Signed-off-by: Yuanfu Xie Message-ID: <92377346-29E9-46D3-96F5-AABA8A64CD57@stu.pku.edu.cn> Signed-off-by: Dominique Martinet --- fs/9p/vfs_inode_dotl.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 1431549b1f4d..d9156b2509ac 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -328,16 +328,24 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, err = finish_open(file, dentry, generic_file_open); if (err) goto out; - file->private_data = ofid; + /* + * Special files have type-specific fops that own + * file->private_data. The create fid is unused + * there: leave it for p9_fid_put() on the out + * path so it is clunked before we return. + */ + if (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)) { + file->private_data = ofid; #ifdef CONFIG_9P_FSCACHE - if (v9ses->cache & CACHE_FSCACHE) { - struct v9fs_inode *v9inode = V9FS_I(inode); - fscache_use_cookie(v9fs_inode_cookie(v9inode), - file->f_mode & FMODE_WRITE); - } + if (v9ses->cache & CACHE_FSCACHE) { + struct v9fs_inode *v9inode = V9FS_I(inode); + fscache_use_cookie(v9fs_inode_cookie(v9inode), + file->f_mode & FMODE_WRITE); + } #endif - v9fs_fid_add_modes(ofid, v9ses->flags, v9ses->cache, flags); - v9fs_open_fid_add(inode, &ofid); + v9fs_fid_add_modes(ofid, v9ses->flags, v9ses->cache, flags); + v9fs_open_fid_add(inode, &ofid); + } file->f_mode |= FMODE_CREATED; out: p9_fid_put(dfid); From 241d4690122658a3cdb434de4d996ef269d6244d Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Thu, 29 Oct 2026 05:58:00 +0000 Subject: [PATCH 38/39] 9p: reject RERROR/RLERROR with zero errcode In p9_check_errors() (net/9p/client.c), a malicious or buggy 9p server returning P9_RERROR or P9_RLERROR with ecode == 0 causes p9_check_errors() to return 0 (success) while leaving the unparsed response payload as if the expected reply message type succeeded. Map ecode == 0 on RERROR / RLERROR to -EPROTO. Link: https://lore.kernel.org/all/20260919221734.3708000-1-benquike@gmail.com/ Signed-off-by: Hui Peng Message-ID: <20261029055801.3-1-asmadeus@codewreck.org> Signed-off-by: Dominique Martinet --- net/9p/client.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/9p/client.c b/net/9p/client.c index ef64546c6d52..e63e8c2a853d 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -448,6 +448,9 @@ static int p9_check_errors(struct p9_client *c, struct p9_req_t *req) p9_debug(P9_DEBUG_9P, "<<< RLERROR (%d)\n", -ecode); } + if (!err) + err = -EPROTO; + return err; out_err: From 35ecd40c0d7e4f772d00dfec9f7077315ba5ca82 Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Thu, 29 Oct 2026 06:00:12 +0000 Subject: [PATCH 39/39] 9p: optimize p9_fcall struct p9_fcall has a 1 byte hole followed by more data and then a single byte element: struct p9_fcall { u32 size; /* 0 4 */ u8 id; /* 4 1 */ /* XXX 1 byte hole, try to pack */ u16 tag; /* 6 2 */ size_t offset; /* 8 8 */ size_t capacity; /* 16 8 */ struct kmem_cache * cache; /* 24 8 */ u8 * sdata; /* 32 8 */ bool zc; /* 40 1 */ /* size: 48, cachelines: 1, members: 8 */ /* sum members: 40, holes: 1, sum holes: 1 */ /* padding: 7 */ /* last cacheline: 48 bytes */ }; move `zc` up to optimize it: struct p9_fcall { u32 size; /* 0 4 */ u8 id; /* 4 1 */ bool zc; /* 5 1 */ u16 tag; /* 6 2 */ size_t offset; /* 8 8 */ size_t capacity; /* 16 8 */ struct kmem_cache * cache; /* 24 8 */ u8 * sdata; /* 32 8 */ /* size: 40, cachelines: 1, members: 8 */ /* last cacheline: 40 bytes */ }; This in turn also makes p9_req_t 16 bytes smaller (152 -> 136) as it contains two of these. Signed-off-by: Dominique Martinet Message-ID: <20261029060014.3-1-asmadeus@codewreck.org> --- include/net/9p/9p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h index 22ab7486795d..067e8a3b38d0 100644 --- a/include/net/9p/9p.h +++ b/include/net/9p/9p.h @@ -581,6 +581,7 @@ struct p9_rstatfs { struct p9_fcall { u32 size; u8 id; + bool zc; u16 tag; size_t offset; @@ -588,7 +589,6 @@ struct p9_fcall { struct kmem_cache *cache; u8 *sdata; - bool zc; }; int p9_errstr2errno(char *errstr, int len);