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: 7751bdb3a0 ("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 <linux_oss@crudebyte.com>
Message-ID: <20260918024851.51229-2-853555@gmail.com>
Signed-off-by: Dominique Martinet <asmadeus@codewreck.org>
This commit is contained in:
Haobin Wu 2026-09-18 10:48:49 +08:00 committed by Dominique Martinet
parent a869fe526c
commit 8bb0279930
3 changed files with 8 additions and 12 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;
}