summaryrefslogtreecommitdiff
path: root/fs/smb/client/reparse.c
diff options
context:
space:
mode:
authorPali Rohár <pali@kernel.org>2024-09-23 23:40:38 +0300
committerSteve French <stfrench@microsoft.com>2024-11-25 23:50:32 +0300
commit723f4ef90452aa629f3d923e92e0449d69362b1d (patch)
treec2a9c81efc28e9894eceb83956433db353727a24 /fs/smb/client/reparse.c
parent4bdec0d1f658f7c98749bd2c5a486e6cfa8565d2 (diff)
downloadlinux-723f4ef90452aa629f3d923e92e0449d69362b1d.tar.xz
cifs: Fix parsing native symlinks relative to the export
SMB symlink which has SYMLINK_FLAG_RELATIVE set is relative (as opposite of the absolute) and it can be relative either to the current directory (where is the symlink stored) or relative to the top level export path. To what it is relative depends on the first character of the symlink target path. If the first character is path separator then symlink is relative to the export, otherwise to the current directory. Linux (and generally POSIX systems) supports only symlink paths relative to the current directory where is symlink stored. Currently if Linux SMB client reads relative SMB symlink with first character as path separator (slash), it let as is. Which means that Linux interpret it as absolute symlink pointing from the root (/). But this location is different than the top level directory of SMB export (unless SMB export was mounted to the root) and thefore SMB symlinks relative to the export are interpreted wrongly by Linux SMB client. Fix this problem. As Linux does not have equivalent of the path relative to the top of the mount point, convert such symlink target path relative to the current directory. Do this by prepending "../" pattern N times before the SMB target path, where N is the number of path separators found in SMB symlink path. So for example, if SMB share is mounted to Linux path /mnt/share/, symlink is stored in file /mnt/share/test/folder1/symlink (so SMB symlink path is test\folder1\symlink) and SMB symlink target points to \test\folder2\file, then convert symlink target path to Linux path ../../test/folder2/file. Deduplicate code for parsing SMB symlinks in native form from functions smb2_parse_symlink_response() and parse_reparse_native_symlink() into new function smb2_parse_native_symlink() and pass into this new function a new full_path parameter from callers, which specify SMB full path where is symlink stored. This change fixes resolving of the native Windows symlinks relative to the top level directory of the SMB share. Signed-off-by: Pali Rohár <pali@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
Diffstat (limited to 'fs/smb/client/reparse.c')
-rw-r--r--fs/smb/client/reparse.c90
1 files changed, 78 insertions, 12 deletions
diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index 90da1e2b6217..f74d0a86f44a 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -535,9 +535,76 @@ static int parse_reparse_posix(struct reparse_posix_data *buf,
return 0;
}
+int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
+ bool unicode, bool relative,
+ const char *full_path,
+ struct cifs_sb_info *cifs_sb)
+{
+ char sep = CIFS_DIR_SEP(cifs_sb);
+ char *linux_target = NULL;
+ char *smb_target = NULL;
+ int levels;
+ int rc;
+ int i;
+
+ smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls);
+ if (!smb_target) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (smb_target[0] == sep && relative) {
+ /*
+ * This is a relative SMB symlink from the top of the share,
+ * which is the top level directory of the Linux mount point.
+ * Linux does not support such relative symlinks, so convert
+ * it to the relative symlink from the current directory.
+ * full_path is the SMB path to the symlink (from which is
+ * extracted current directory) and smb_target is the SMB path
+ * where symlink points, therefore full_path must always be on
+ * the SMB share.
+ */
+ int smb_target_len = strlen(smb_target)+1;
+ levels = 0;
+ for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */
+ if (full_path[i] == sep)
+ levels++;
+ }
+ linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL);
+ if (!linux_target) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ for (i = 0; i < levels; i++) {
+ linux_target[i*3 + 0] = '.';
+ linux_target[i*3 + 1] = '.';
+ linux_target[i*3 + 2] = sep;
+ }
+ memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */
+ } else {
+ linux_target = smb_target;
+ smb_target = NULL;
+ }
+
+ if (sep == '\\')
+ convert_delimiter(linux_target, '/');
+
+ rc = 0;
+ *target = linux_target;
+
+ cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target);
+
+out:
+ if (rc != 0)
+ kfree(linux_target);
+ kfree(smb_target);
+ return rc;
+}
+
static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
u32 plen, bool unicode,
struct cifs_sb_info *cifs_sb,
+ const char *full_path,
struct cifs_open_info_data *data)
{
unsigned int len;
@@ -552,20 +619,18 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
return -EIO;
}
- data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs,
- len, unicode,
- cifs_sb->local_nls);
- if (!data->symlink_target)
- return -ENOMEM;
-
- convert_delimiter(data->symlink_target, '/');
- cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target);
-
- return 0;
+ return smb2_parse_native_symlink(&data->symlink_target,
+ sym->PathBuffer + offs,
+ len,
+ unicode,
+ le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
+ full_path,
+ cifs_sb);
}
int parse_reparse_point(struct reparse_data_buffer *buf,
u32 plen, struct cifs_sb_info *cifs_sb,
+ const char *full_path,
bool unicode, struct cifs_open_info_data *data)
{
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
@@ -580,7 +645,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
case IO_REPARSE_TAG_SYMLINK:
return parse_reparse_symlink(
(struct reparse_symlink_data_buffer *)buf,
- plen, unicode, cifs_sb, data);
+ plen, unicode, cifs_sb, full_path, data);
case IO_REPARSE_TAG_LX_SYMLINK:
case IO_REPARSE_TAG_AF_UNIX:
case IO_REPARSE_TAG_LX_FIFO:
@@ -596,6 +661,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
}
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
+ const char *full_path,
struct kvec *rsp_iov,
struct cifs_open_info_data *data)
{
@@ -605,7 +671,7 @@ int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
buf = (struct reparse_data_buffer *)((u8 *)io +
le32_to_cpu(io->OutputOffset));
- return parse_reparse_point(buf, plen, cifs_sb, true, data);
+ return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data);
}
static void wsl_to_fattr(struct cifs_open_info_data *data,