From: "Pali Rohár" <pali@kernel.org>
To: Steve French <sfrench@samba.org>,
Paulo Alcantara <pc@manguebit.org>,
Ronnie Sahlberg <ronniesahlberg@gmail.com>
Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH RESEND 10/11] cifs: Add support for parsing WSL symlinks in version 1 format
Date: Mon, 6 Jul 2026 20:48:18 +0200 [thread overview]
Message-ID: <20260706184819.22124-11-pali@kernel.org> (raw)
In-Reply-To: <20260706184819.22124-1-pali@kernel.org>
MS-FSCC 2.1.2.7 for IO_REPARSE_TAG_LX_SYMLINK reparse points currently
documents only layout version 2 format.
IO_REPARSE_TAG_LX_SYMLINK reparse point buffer of layout version 1 format
is documented in the newly released Microsoft WSL source code at github:
https://github.com/microsoft/WSL/blob/2.5.8/test/windows/DrvFsTests.cpp#L775-L815
Difference between version 1 and version 2 is that version 1 stores the
symlink target location into data section of the file, but version 2 stores
it directly into the reparse point buffer.
This change implements support for parsing WSL symlinks in this layout
version 1 format by Linux SMB client and so allow to recognize these type
of symlinks like Windows WSL.
Signed-off-by: Pali Rohár <pali@kernel.org>
---
fs/smb/client/cifsproto.h | 1 +
fs/smb/client/inode.c | 1 +
fs/smb/client/reparse.c | 118 ++++++++++++++++++++++++++++++++------
fs/smb/common/fscc.h | 5 +-
4 files changed, 105 insertions(+), 20 deletions(-)
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
index c4ababcb51a3..1ed97957038f 100644
--- a/fs/smb/client/cifsproto.h
+++ b/fs/smb/client/cifsproto.h
@@ -390,6 +390,7 @@ int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix);
char *extract_hostname(const char *unc);
char *extract_sharename(const char *unc);
int parse_reparse_point(struct reparse_data_buffer *buf, u32 plen,
+ unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, const char *full_path,
struct cifs_open_info_data *data);
int __cifs_sfu_make_node(unsigned int xid, struct inode *inode,
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 6f82aa9bd033..002a2ec85db5 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -1171,6 +1171,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
reparse_buf = server->ops->get_reparse_point_buffer(iov, &reparse_len);
rc = parse_reparse_point(reparse_buf, reparse_len,
+ xid, tcon,
cifs_sb, full_path, data);
/*
* If the reparse point was not handled but it is the
diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index 50eeea127a42..0402a30d0d53 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -1045,51 +1045,124 @@ static int parse_reparse_native_symlink(struct reparse_symlink_data_buffer *sym,
}
static int parse_reparse_wsl_symlink(struct reparse_wsl_symlink_data_buffer *buf,
+ unsigned int xid,
+ struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb,
+ const char *full_path,
struct cifs_open_info_data *data)
{
int len = le16_to_cpu(buf->ReparseDataLength);
int data_offset = offsetof(typeof(*buf), Target) - offsetof(typeof(*buf), Version);
- int symname_utf8_len;
+ bool free_symname_utf8 = false;
+ struct cifs_open_parms oparms;
+ struct cifs_io_parms io_parms;
+ unsigned int symname_utf8_len;
+ char *symname_utf8 = NULL;
__le16 *symname_utf16;
int symname_utf16_len;
+ struct cifs_fid fid;
+ __u32 oplock;
+ int buf_type;
+ int rc = 0;
- if (len <= data_offset) {
+ if (len < data_offset) {
cifs_dbg(VFS, "srv returned malformed wsl symlink buffer\n");
- return smb_EIO2(smb_eio_trace_reparse_wsl_symbuf,
+ rc = smb_EIO2(smb_eio_trace_reparse_wsl_symbuf,
len, data_offset);
+ goto out;
}
- /* MS-FSCC 2.1.2.7 defines layout of the Target field only for Version 2. */
u32 version = le32_to_cpu(buf->Version);
+ switch (version) {
+ case 1:
+ /*
+ * Layout version 1 stores the symlink target in the data section of
+ * the file encoded in UTF-8 without trailing null-term byte.
+ */
- if (version != 2) {
+ oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_READ_DATA,
+ FILE_OPEN, CREATE_NOT_DIR | OPEN_REPARSE_POINT,
+ ACL_NO_MODE);
+ oparms.fid = &fid;
+ oplock = tcon->ses->server->oplocks ? REQ_OPLOCK : 0;
+ rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL);
+ if (rc)
+ goto out;
+
+ free_symname_utf8 = true;
+ symname_utf8_len = le64_to_cpu(data->fi.EndOfFile);
+ symname_utf8 = kmalloc(symname_utf8_len, GFP_KERNEL);
+ if (!symname_utf8) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ buf_type = CIFS_NO_BUFFER;
+ io_parms = (struct cifs_io_parms) {
+ .netfid = fid.netfid,
+ .pid = current->tgid,
+ .tcon = tcon,
+ .offset = 0,
+ .length = symname_utf8_len,
+ };
+ rc = tcon->ses->server->ops->sync_read(xid, &fid, &io_parms,
+ &symname_utf8_len,
+ &symname_utf8,
+ &buf_type);
+ if (!rc && symname_utf8_len != le64_to_cpu(data->fi.EndOfFile))
+ rc = -EIO;
+
+ tcon->ses->server->ops->close(xid, tcon, &fid);
+
+ if (rc) {
+ cifs_dbg(VFS, "cannot read wsl symlink target location: %d\n", rc);
+ goto out;
+ }
+
+ break;
+ case 2:
+ /*
+ * Layout version 2 stores the symlink target in the reparse buffer
+ * field Target encoded in UTF-8 without trailing null-term byte.
+ */
+ symname_utf8_len = len - data_offset;
+ symname_utf8 = buf->Target;
+ break;
+ default:
cifs_dbg(VFS, "srv returned unsupported wsl symlink version %u\n", version);
return smb_EIO1(smb_eio_trace_reparse_wsl_ver, version);
}
- /* Target for Version 2 is in UTF-8 but without trailing null-term byte */
- symname_utf8_len = len - data_offset;
+ if (symname_utf8_len == 0) {
+ cifs_dbg(VFS, "srv returned empty wsl symlink target location\n");
+ rc = -EIO;
+ goto out;
+ }
+
/*
* Check that buffer does not contain null byte
* because Linux cannot process symlink with null byte.
*/
- size_t ulen = strnlen(buf->Target, symname_utf8_len);
+ size_t ulen = strnlen(symname_utf8, symname_utf8_len);
if (ulen != symname_utf8_len) {
cifs_dbg(VFS, "srv returned null byte in wsl symlink target location\n");
- return smb_EIO2(smb_eio_trace_reparse_wsl_ver,
+ rc = smb_EIO2(smb_eio_trace_reparse_wsl_ver,
ulen, symname_utf8_len);
+ goto out;
}
symname_utf16 = kzalloc(symname_utf8_len * 2, GFP_KERNEL);
- if (!symname_utf16)
- return -ENOMEM;
- symname_utf16_len = utf8s_to_utf16s(buf->Target, symname_utf8_len,
+ if (!symname_utf16) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ symname_utf16_len = utf8s_to_utf16s(symname_utf8, symname_utf8_len,
UTF16_LITTLE_ENDIAN,
(wchar_t *) symname_utf16, symname_utf8_len * 2);
if (symname_utf16_len < 0) {
kfree(symname_utf16);
- return symname_utf16_len;
+ rc = symname_utf16_len;
+ goto out;
}
symname_utf16_len *= 2; /* utf8s_to_utf16s() returns number of u16 items, not byte length */
@@ -1097,14 +1170,23 @@ static int parse_reparse_wsl_symlink(struct reparse_wsl_symlink_data_buffer *buf
symname_utf16_len, true,
cifs_sb->local_nls);
kfree(symname_utf16);
- if (!data->symlink_target)
- return -ENOMEM;
+ if (!data->symlink_target) {
+ rc = -ENOMEM;
+ goto out;
+ }
- return 0;
+out:
+ if (free_symname_utf8)
+ kfree(symname_utf8);
+
+ return rc;
}
int parse_reparse_point(struct reparse_data_buffer *buf,
- u32 plen, struct cifs_sb_info *cifs_sb,
+ u32 plen,
+ unsigned int xid,
+ struct cifs_tcon *tcon,
+ struct cifs_sb_info *cifs_sb,
const char *full_path,
struct cifs_open_info_data *data)
{
@@ -1122,7 +1204,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
case IO_REPARSE_TAG_LX_SYMLINK:
return parse_reparse_wsl_symlink(
(struct reparse_wsl_symlink_data_buffer *)buf,
- cifs_sb, data);
+ xid, tcon, cifs_sb, full_path, data);
case IO_REPARSE_TAG_AF_UNIX:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_LX_CHR:
diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h
index 859849a42fec..f5ec6b203c42 100644
--- a/fs/smb/common/fscc.h
+++ b/fs/smb/common/fscc.h
@@ -69,12 +69,13 @@ struct reparse_nfs_data_buffer {
__u8 DataBuffer[];
} __packed;
-/* For IO_REPARSE_TAG_LX_SYMLINK - see MS-FSCC 2.1.2.7 */
+/* For IO_REPARSE_TAG_LX_SYMLINK - see MS-FSCC 2.1.2.7 and
+ * https://github.com/microsoft/WSL/blob/2.5.8/test/windows/DrvFsTests.cpp#L775-L815 */
struct reparse_wsl_symlink_data_buffer {
__le32 ReparseTag;
__le16 ReparseDataLength;
__u16 Reserved;
- __le32 Version; /* Always 2 */
+ __le32 Version; /* 1 - stores symlink path in file data section; 2 - stores symlink path in Target[] field */
__u8 Target[]; /* Variable Length UTF-8 string without nul-term */
} __packed;
--
2.20.1
next prev parent reply other threads:[~2026-07-06 18:48 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 18:48 [PATCH RESEND 00/11] cifs: Various changes Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 01/11] cifs: Fix and improve cifs_is_path_accessible() function Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 02/11] cifs: Remove code for querying FILE_INFO_STANDARD via CIFSSMBQPathInfo() Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 03/11] cifs: Remove CIFSSMBSetPathInfoFB() fallback function Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 04/11] cifs: Remove cifs_backup_query_path_info() and replace it by cifs_query_path_info() Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 05/11] cifs: Fix validation of EAs for WSL reparse points Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 06/11] cifs: Validate presence of EA $LXMOD " Pali Rohár
2026-07-06 22:30 ` Steve French
2026-07-06 22:39 ` Pali Rohár
2026-07-06 22:55 ` Steve French
2026-07-06 23:02 ` Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 07/11] cifs: Check if server supports EAs before trying to set it for WSL Pali Rohár
2026-07-06 22:33 ` Steve French
2026-07-06 22:42 ` Pali Rohár
[not found] ` <CAH2r5mvDGM7i8EsdPkW681a_AnXOAsHkg38eJqegO7g2gV8qAw@mail.gmail.com>
2026-07-06 22:58 ` Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 08/11] cifs: Extend ->set_EA() callback to allow operate on reparse point Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 09/11] cifs: Create native Window socket file compatible also with WSL subsystem Pali Rohár
2026-07-06 18:48 ` Pali Rohár [this message]
2026-07-06 20:50 ` [PATCH RESEND 10/11] cifs: Add support for parsing WSL symlinks in version 1 format Pali Rohár
2026-07-06 21:02 ` Pali Rohár
2026-07-06 18:48 ` [PATCH RESEND 11/11] cifs: Add support for creating " Pali Rohár
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260706184819.22124-11-pali@kernel.org \
--to=pali@kernel.org \
--cc=linux-cifs@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=pc@manguebit.org \
--cc=ronniesahlberg@gmail.com \
--cc=sfrench@samba.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.