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: 17+ 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 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox