Skip to content

Commit 723f4ef

Browse files
palismfrench
authored andcommitted
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 <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 4bdec0d commit 723f4ef

File tree

9 files changed

+108
-28
lines changed

9 files changed

+108
-28
lines changed

fs/smb/client/cifsglob.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ struct smb_version_operations {
594594
/* Check for STATUS_NETWORK_NAME_DELETED */
595595
bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv);
596596
int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb,
597+
const char *full_path,
597598
struct kvec *rsp_iov,
598599
struct cifs_open_info_data *data);
599600
int (*create_reparse_symlink)(const unsigned int xid,

fs/smb/client/cifsproto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ char *extract_hostname(const char *unc);
661661
char *extract_sharename(const char *unc);
662662
int parse_reparse_point(struct reparse_data_buffer *buf,
663663
u32 plen, struct cifs_sb_info *cifs_sb,
664+
const char *full_path,
664665
bool unicode, struct cifs_open_info_data *data);
665666
int __cifs_sfu_make_node(unsigned int xid, struct inode *inode,
666667
struct dentry *dentry, struct cifs_tcon *tcon,

fs/smb/client/inode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
11371137
rc = 0;
11381138
} else if (iov && server->ops->parse_reparse_point) {
11391139
rc = server->ops->parse_reparse_point(cifs_sb,
1140+
full_path,
11401141
iov, data);
11411142
}
11421143
break;

fs/smb/client/reparse.c

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -535,9 +535,76 @@ static int parse_reparse_posix(struct reparse_posix_data *buf,
535535
return 0;
536536
}
537537

538+
int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
539+
bool unicode, bool relative,
540+
const char *full_path,
541+
struct cifs_sb_info *cifs_sb)
542+
{
543+
char sep = CIFS_DIR_SEP(cifs_sb);
544+
char *linux_target = NULL;
545+
char *smb_target = NULL;
546+
int levels;
547+
int rc;
548+
int i;
549+
550+
smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls);
551+
if (!smb_target) {
552+
rc = -ENOMEM;
553+
goto out;
554+
}
555+
556+
if (smb_target[0] == sep && relative) {
557+
/*
558+
* This is a relative SMB symlink from the top of the share,
559+
* which is the top level directory of the Linux mount point.
560+
* Linux does not support such relative symlinks, so convert
561+
* it to the relative symlink from the current directory.
562+
* full_path is the SMB path to the symlink (from which is
563+
* extracted current directory) and smb_target is the SMB path
564+
* where symlink points, therefore full_path must always be on
565+
* the SMB share.
566+
*/
567+
int smb_target_len = strlen(smb_target)+1;
568+
levels = 0;
569+
for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */
570+
if (full_path[i] == sep)
571+
levels++;
572+
}
573+
linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL);
574+
if (!linux_target) {
575+
rc = -ENOMEM;
576+
goto out;
577+
}
578+
for (i = 0; i < levels; i++) {
579+
linux_target[i*3 + 0] = '.';
580+
linux_target[i*3 + 1] = '.';
581+
linux_target[i*3 + 2] = sep;
582+
}
583+
memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */
584+
} else {
585+
linux_target = smb_target;
586+
smb_target = NULL;
587+
}
588+
589+
if (sep == '\\')
590+
convert_delimiter(linux_target, '/');
591+
592+
rc = 0;
593+
*target = linux_target;
594+
595+
cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target);
596+
597+
out:
598+
if (rc != 0)
599+
kfree(linux_target);
600+
kfree(smb_target);
601+
return rc;
602+
}
603+
538604
static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
539605
u32 plen, bool unicode,
540606
struct cifs_sb_info *cifs_sb,
607+
const char *full_path,
541608
struct cifs_open_info_data *data)
542609
{
543610
unsigned int len;
@@ -552,20 +619,18 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
552619
return -EIO;
553620
}
554621

555-
data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs,
556-
len, unicode,
557-
cifs_sb->local_nls);
558-
if (!data->symlink_target)
559-
return -ENOMEM;
560-
561-
convert_delimiter(data->symlink_target, '/');
562-
cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target);
563-
564-
return 0;
622+
return smb2_parse_native_symlink(&data->symlink_target,
623+
sym->PathBuffer + offs,
624+
len,
625+
unicode,
626+
le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
627+
full_path,
628+
cifs_sb);
565629
}
566630

567631
int parse_reparse_point(struct reparse_data_buffer *buf,
568632
u32 plen, struct cifs_sb_info *cifs_sb,
633+
const char *full_path,
569634
bool unicode, struct cifs_open_info_data *data)
570635
{
571636
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
@@ -580,7 +645,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
580645
case IO_REPARSE_TAG_SYMLINK:
581646
return parse_reparse_symlink(
582647
(struct reparse_symlink_data_buffer *)buf,
583-
plen, unicode, cifs_sb, data);
648+
plen, unicode, cifs_sb, full_path, data);
584649
case IO_REPARSE_TAG_LX_SYMLINK:
585650
case IO_REPARSE_TAG_AF_UNIX:
586651
case IO_REPARSE_TAG_LX_FIFO:
@@ -596,6 +661,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
596661
}
597662

598663
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
664+
const char *full_path,
599665
struct kvec *rsp_iov,
600666
struct cifs_open_info_data *data)
601667
{
@@ -605,7 +671,7 @@ int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
605671

606672
buf = (struct reparse_data_buffer *)((u8 *)io +
607673
le32_to_cpu(io->OutputOffset));
608-
return parse_reparse_point(buf, plen, cifs_sb, true, data);
674+
return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data);
609675
}
610676

611677
static void wsl_to_fattr(struct cifs_open_info_data *data,

fs/smb/client/reparse.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
117117
int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
118118
struct dentry *dentry, struct cifs_tcon *tcon,
119119
const char *full_path, umode_t mode, dev_t dev);
120-
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov,
120+
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
121+
const char *full_path,
122+
struct kvec *rsp_iov,
121123
struct cifs_open_info_data *data);
122124

123125
#endif /* _CIFS_REPARSE_H */

fs/smb/client/smb1ops.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ static int cifs_query_symlink(const unsigned int xid,
994994
}
995995

996996
static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb,
997+
const char *full_path,
997998
struct kvec *rsp_iov,
998999
struct cifs_open_info_data *data)
9991000
{
@@ -1004,7 +1005,7 @@ static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb,
10041005

10051006
buf = (struct reparse_data_buffer *)((__u8 *)&io->hdr.Protocol +
10061007
le32_to_cpu(io->DataOffset));
1007-
return parse_reparse_point(buf, plen, cifs_sb, unicode, data);
1008+
return parse_reparse_point(buf, plen, cifs_sb, full_path, unicode, data);
10081009
}
10091010

10101011
static bool

fs/smb/client/smb2file.c

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
6363
return sym;
6464
}
6565

66-
int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path)
66+
int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov,
67+
const char *full_path, char **path)
6768
{
6869
struct smb2_symlink_err_rsp *sym;
6970
unsigned int sub_offs, sub_len;
7071
unsigned int print_offs, print_len;
71-
char *s;
7272

7373
if (!cifs_sb || !iov || !iov->iov_base || !iov->iov_len || !path)
7474
return -EINVAL;
@@ -86,15 +86,13 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec
8686
iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len)
8787
return -EINVAL;
8888

89-
s = cifs_strndup_from_utf16((char *)sym->PathBuffer + sub_offs, sub_len, true,
90-
cifs_sb->local_nls);
91-
if (!s)
92-
return -ENOMEM;
93-
convert_delimiter(s, '/');
94-
cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, s);
95-
96-
*path = s;
97-
return 0;
89+
return smb2_parse_native_symlink(path,
90+
(char *)sym->PathBuffer + sub_offs,
91+
sub_len,
92+
true,
93+
le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
94+
full_path,
95+
cifs_sb);
9896
}
9997

10098
int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf)
@@ -126,6 +124,7 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32
126124
goto out;
127125
if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
128126
rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov,
127+
oparms->path,
129128
&data->symlink_target);
130129
if (!rc) {
131130
memset(smb2_data, 0, sizeof(*smb2_data));

fs/smb/client/smb2inode.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
828828

829829
static int parse_create_response(struct cifs_open_info_data *data,
830830
struct cifs_sb_info *cifs_sb,
831+
const char *full_path,
831832
const struct kvec *iov)
832833
{
833834
struct smb2_create_rsp *rsp = iov->iov_base;
@@ -841,6 +842,7 @@ static int parse_create_response(struct cifs_open_info_data *data,
841842
break;
842843
case STATUS_STOPPED_ON_SYMLINK:
843844
rc = smb2_parse_symlink_response(cifs_sb, iov,
845+
full_path,
844846
&data->symlink_target);
845847
if (rc)
846848
return rc;
@@ -930,14 +932,14 @@ int smb2_query_path_info(const unsigned int xid,
930932

931933
switch (rc) {
932934
case 0:
933-
rc = parse_create_response(data, cifs_sb, &out_iov[0]);
935+
rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]);
934936
break;
935937
case -EOPNOTSUPP:
936938
/*
937939
* BB TODO: When support for special files added to Samba
938940
* re-verify this path.
939941
*/
940-
rc = parse_create_response(data, cifs_sb, &out_iov[0]);
942+
rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]);
941943
if (rc || !data->reparse_point)
942944
goto out;
943945

fs/smb/client/smb2proto.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,14 @@ extern int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
111111
struct cifs_sb_info *cifs_sb,
112112
const unsigned char *path, char *pbuf,
113113
unsigned int *pbytes_read);
114-
int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path);
114+
int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
115+
bool unicode, bool relative,
116+
const char *full_path,
117+
struct cifs_sb_info *cifs_sb);
118+
int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb,
119+
const struct kvec *iov,
120+
const char *full_path,
121+
char **path);
115122
int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock,
116123
void *buf);
117124
extern int smb2_unlock_range(struct cifsFileInfo *cfile,

0 commit comments

Comments
 (0)