mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-04 04:06:26 +00:00
723f4ef904
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>
370 lines
10 KiB
C
370 lines
10 KiB
C
// SPDX-License-Identifier: LGPL-2.1
|
|
/*
|
|
*
|
|
* Copyright (C) International Business Machines Corp., 2002, 2011
|
|
* Author(s): Steve French (sfrench@us.ibm.com),
|
|
* Pavel Shilovsky ((pshilovsky@samba.org) 2012
|
|
*
|
|
*/
|
|
#include <linux/fs.h>
|
|
#include <linux/filelock.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pagemap.h>
|
|
#include <asm/div64.h>
|
|
#include "cifsfs.h"
|
|
#include "cifspdu.h"
|
|
#include "cifsglob.h"
|
|
#include "cifsproto.h"
|
|
#include "cifs_debug.h"
|
|
#include "cifs_fs_sb.h"
|
|
#include "cifs_unicode.h"
|
|
#include "fscache.h"
|
|
#include "smb2proto.h"
|
|
#include "../common/smb2status.h"
|
|
|
|
static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
|
|
{
|
|
struct smb2_err_rsp *err = iov->iov_base;
|
|
struct smb2_symlink_err_rsp *sym = ERR_PTR(-EINVAL);
|
|
u32 len;
|
|
|
|
if (err->ErrorContextCount) {
|
|
struct smb2_error_context_rsp *p, *end;
|
|
|
|
len = (u32)err->ErrorContextCount * (offsetof(struct smb2_error_context_rsp,
|
|
ErrorContextData) +
|
|
sizeof(struct smb2_symlink_err_rsp));
|
|
if (le32_to_cpu(err->ByteCount) < len || iov->iov_len < len + sizeof(*err) + 1)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
p = (struct smb2_error_context_rsp *)err->ErrorData;
|
|
end = (struct smb2_error_context_rsp *)((u8 *)err + iov->iov_len);
|
|
do {
|
|
if (le32_to_cpu(p->ErrorId) == SMB2_ERROR_ID_DEFAULT) {
|
|
sym = (struct smb2_symlink_err_rsp *)&p->ErrorContextData;
|
|
break;
|
|
}
|
|
cifs_dbg(FYI, "%s: skipping unhandled error context: 0x%x\n",
|
|
__func__, le32_to_cpu(p->ErrorId));
|
|
|
|
len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8);
|
|
p = (struct smb2_error_context_rsp *)((u8 *)&p->ErrorContextData + len);
|
|
} while (p < end);
|
|
} else if (le32_to_cpu(err->ByteCount) >= sizeof(*sym) &&
|
|
iov->iov_len >= SMB2_SYMLINK_STRUCT_SIZE) {
|
|
sym = (struct smb2_symlink_err_rsp *)err->ErrorData;
|
|
}
|
|
|
|
if (!IS_ERR(sym) && (le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG ||
|
|
le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK))
|
|
sym = ERR_PTR(-EINVAL);
|
|
|
|
return sym;
|
|
}
|
|
|
|
int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov,
|
|
const char *full_path, char **path)
|
|
{
|
|
struct smb2_symlink_err_rsp *sym;
|
|
unsigned int sub_offs, sub_len;
|
|
unsigned int print_offs, print_len;
|
|
|
|
if (!cifs_sb || !iov || !iov->iov_base || !iov->iov_len || !path)
|
|
return -EINVAL;
|
|
|
|
sym = symlink_data(iov);
|
|
if (IS_ERR(sym))
|
|
return PTR_ERR(sym);
|
|
|
|
sub_len = le16_to_cpu(sym->SubstituteNameLength);
|
|
sub_offs = le16_to_cpu(sym->SubstituteNameOffset);
|
|
print_len = le16_to_cpu(sym->PrintNameLength);
|
|
print_offs = le16_to_cpu(sym->PrintNameOffset);
|
|
|
|
if (iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + sub_offs + sub_len ||
|
|
iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len)
|
|
return -EINVAL;
|
|
|
|
return smb2_parse_native_symlink(path,
|
|
(char *)sym->PathBuffer + sub_offs,
|
|
sub_len,
|
|
true,
|
|
le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
|
|
full_path,
|
|
cifs_sb);
|
|
}
|
|
|
|
int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf)
|
|
{
|
|
int rc;
|
|
__le16 *smb2_path;
|
|
__u8 smb2_oplock;
|
|
struct cifs_open_info_data *data = buf;
|
|
struct smb2_file_all_info file_info = {};
|
|
struct smb2_file_all_info *smb2_data = data ? &file_info : NULL;
|
|
struct kvec err_iov = {};
|
|
int err_buftype = CIFS_NO_BUFFER;
|
|
struct cifs_fid *fid = oparms->fid;
|
|
struct network_resiliency_req nr_ioctl_req;
|
|
|
|
smb2_path = cifs_convert_path_to_utf16(oparms->path, oparms->cifs_sb);
|
|
if (smb2_path == NULL)
|
|
return -ENOMEM;
|
|
|
|
oparms->desired_access |= FILE_READ_ATTRIBUTES;
|
|
smb2_oplock = SMB2_OPLOCK_LEVEL_BATCH;
|
|
|
|
rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, &err_iov,
|
|
&err_buftype);
|
|
if (rc && data) {
|
|
struct smb2_hdr *hdr = err_iov.iov_base;
|
|
|
|
if (unlikely(!err_iov.iov_base || err_buftype == CIFS_NO_BUFFER))
|
|
goto out;
|
|
if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
|
|
rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov,
|
|
oparms->path,
|
|
&data->symlink_target);
|
|
if (!rc) {
|
|
memset(smb2_data, 0, sizeof(*smb2_data));
|
|
oparms->create_options |= OPEN_REPARSE_POINT;
|
|
rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data,
|
|
NULL, NULL, NULL);
|
|
oparms->create_options &= ~OPEN_REPARSE_POINT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rc)
|
|
goto out;
|
|
|
|
if (oparms->tcon->use_resilient) {
|
|
/* default timeout is 0, servers pick default (120 seconds) */
|
|
nr_ioctl_req.Timeout =
|
|
cpu_to_le32(oparms->tcon->handle_timeout);
|
|
nr_ioctl_req.Reserved = 0;
|
|
rc = SMB2_ioctl(xid, oparms->tcon, fid->persistent_fid,
|
|
fid->volatile_fid, FSCTL_LMR_REQUEST_RESILIENCY,
|
|
(char *)&nr_ioctl_req, sizeof(nr_ioctl_req),
|
|
CIFSMaxBufSize, NULL, NULL /* no return info */);
|
|
if (rc == -EOPNOTSUPP) {
|
|
cifs_dbg(VFS,
|
|
"resiliency not supported by server, disabling\n");
|
|
oparms->tcon->use_resilient = false;
|
|
} else if (rc)
|
|
cifs_dbg(FYI, "error %d setting resiliency\n", rc);
|
|
|
|
rc = 0;
|
|
}
|
|
|
|
if (smb2_data) {
|
|
/* if open response does not have IndexNumber field - get it */
|
|
if (smb2_data->IndexNumber == 0) {
|
|
rc = SMB2_get_srv_num(xid, oparms->tcon,
|
|
fid->persistent_fid,
|
|
fid->volatile_fid,
|
|
&smb2_data->IndexNumber);
|
|
if (rc) {
|
|
/*
|
|
* let get_inode_info disable server inode
|
|
* numbers
|
|
*/
|
|
smb2_data->IndexNumber = 0;
|
|
rc = 0;
|
|
}
|
|
}
|
|
memcpy(&data->fi, smb2_data, sizeof(data->fi));
|
|
}
|
|
|
|
*oplock = smb2_oplock;
|
|
out:
|
|
free_rsp_buf(err_buftype, err_iov.iov_base);
|
|
kfree(smb2_path);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
smb2_unlock_range(struct cifsFileInfo *cfile, struct file_lock *flock,
|
|
const unsigned int xid)
|
|
{
|
|
int rc = 0, stored_rc;
|
|
unsigned int max_num, num = 0, max_buf;
|
|
struct smb2_lock_element *buf, *cur;
|
|
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
|
|
struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
|
|
struct cifsLockInfo *li, *tmp;
|
|
__u64 length = 1 + flock->fl_end - flock->fl_start;
|
|
LIST_HEAD(tmp_llist);
|
|
|
|
/*
|
|
* Accessing maxBuf is racy with cifs_reconnect - need to store value
|
|
* and check it before using.
|
|
*/
|
|
max_buf = tcon->ses->server->maxBuf;
|
|
if (max_buf < sizeof(struct smb2_lock_element))
|
|
return -EINVAL;
|
|
|
|
BUILD_BUG_ON(sizeof(struct smb2_lock_element) > PAGE_SIZE);
|
|
max_buf = min_t(unsigned int, max_buf, PAGE_SIZE);
|
|
max_num = max_buf / sizeof(struct smb2_lock_element);
|
|
buf = kcalloc(max_num, sizeof(struct smb2_lock_element), GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
cur = buf;
|
|
|
|
cifs_down_write(&cinode->lock_sem);
|
|
list_for_each_entry_safe(li, tmp, &cfile->llist->locks, llist) {
|
|
if (flock->fl_start > li->offset ||
|
|
(flock->fl_start + length) <
|
|
(li->offset + li->length))
|
|
continue;
|
|
if (current->tgid != li->pid)
|
|
/*
|
|
* flock and OFD lock are associated with an open
|
|
* file description, not the process.
|
|
*/
|
|
if (!(flock->c.flc_flags & (FL_FLOCK | FL_OFDLCK)))
|
|
continue;
|
|
if (cinode->can_cache_brlcks) {
|
|
/*
|
|
* We can cache brlock requests - simply remove a lock
|
|
* from the file's list.
|
|
*/
|
|
list_del(&li->llist);
|
|
cifs_del_lock_waiters(li);
|
|
kfree(li);
|
|
continue;
|
|
}
|
|
cur->Length = cpu_to_le64(li->length);
|
|
cur->Offset = cpu_to_le64(li->offset);
|
|
cur->Flags = cpu_to_le32(SMB2_LOCKFLAG_UNLOCK);
|
|
/*
|
|
* We need to save a lock here to let us add it again to the
|
|
* file's list if the unlock range request fails on the server.
|
|
*/
|
|
list_move(&li->llist, &tmp_llist);
|
|
if (++num == max_num) {
|
|
stored_rc = smb2_lockv(xid, tcon,
|
|
cfile->fid.persistent_fid,
|
|
cfile->fid.volatile_fid,
|
|
current->tgid, num, buf);
|
|
if (stored_rc) {
|
|
/*
|
|
* We failed on the unlock range request - add
|
|
* all locks from the tmp list to the head of
|
|
* the file's list.
|
|
*/
|
|
cifs_move_llist(&tmp_llist,
|
|
&cfile->llist->locks);
|
|
rc = stored_rc;
|
|
} else
|
|
/*
|
|
* The unlock range request succeed - free the
|
|
* tmp list.
|
|
*/
|
|
cifs_free_llist(&tmp_llist);
|
|
cur = buf;
|
|
num = 0;
|
|
} else
|
|
cur++;
|
|
}
|
|
if (num) {
|
|
stored_rc = smb2_lockv(xid, tcon, cfile->fid.persistent_fid,
|
|
cfile->fid.volatile_fid, current->tgid,
|
|
num, buf);
|
|
if (stored_rc) {
|
|
cifs_move_llist(&tmp_llist, &cfile->llist->locks);
|
|
rc = stored_rc;
|
|
} else
|
|
cifs_free_llist(&tmp_llist);
|
|
}
|
|
up_write(&cinode->lock_sem);
|
|
|
|
kfree(buf);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
smb2_push_mand_fdlocks(struct cifs_fid_locks *fdlocks, const unsigned int xid,
|
|
struct smb2_lock_element *buf, unsigned int max_num)
|
|
{
|
|
int rc = 0, stored_rc;
|
|
struct cifsFileInfo *cfile = fdlocks->cfile;
|
|
struct cifsLockInfo *li;
|
|
unsigned int num = 0;
|
|
struct smb2_lock_element *cur = buf;
|
|
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
|
|
|
|
list_for_each_entry(li, &fdlocks->locks, llist) {
|
|
cur->Length = cpu_to_le64(li->length);
|
|
cur->Offset = cpu_to_le64(li->offset);
|
|
cur->Flags = cpu_to_le32(li->type |
|
|
SMB2_LOCKFLAG_FAIL_IMMEDIATELY);
|
|
if (++num == max_num) {
|
|
stored_rc = smb2_lockv(xid, tcon,
|
|
cfile->fid.persistent_fid,
|
|
cfile->fid.volatile_fid,
|
|
current->tgid, num, buf);
|
|
if (stored_rc)
|
|
rc = stored_rc;
|
|
cur = buf;
|
|
num = 0;
|
|
} else
|
|
cur++;
|
|
}
|
|
if (num) {
|
|
stored_rc = smb2_lockv(xid, tcon,
|
|
cfile->fid.persistent_fid,
|
|
cfile->fid.volatile_fid,
|
|
current->tgid, num, buf);
|
|
if (stored_rc)
|
|
rc = stored_rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
smb2_push_mandatory_locks(struct cifsFileInfo *cfile)
|
|
{
|
|
int rc = 0, stored_rc;
|
|
unsigned int xid;
|
|
unsigned int max_num, max_buf;
|
|
struct smb2_lock_element *buf;
|
|
struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
|
|
struct cifs_fid_locks *fdlocks;
|
|
|
|
xid = get_xid();
|
|
|
|
/*
|
|
* Accessing maxBuf is racy with cifs_reconnect - need to store value
|
|
* and check it for zero before using.
|
|
*/
|
|
max_buf = tlink_tcon(cfile->tlink)->ses->server->maxBuf;
|
|
if (max_buf < sizeof(struct smb2_lock_element)) {
|
|
free_xid(xid);
|
|
return -EINVAL;
|
|
}
|
|
|
|
BUILD_BUG_ON(sizeof(struct smb2_lock_element) > PAGE_SIZE);
|
|
max_buf = min_t(unsigned int, max_buf, PAGE_SIZE);
|
|
max_num = max_buf / sizeof(struct smb2_lock_element);
|
|
buf = kcalloc(max_num, sizeof(struct smb2_lock_element), GFP_KERNEL);
|
|
if (!buf) {
|
|
free_xid(xid);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
list_for_each_entry(fdlocks, &cinode->llist, llist) {
|
|
stored_rc = smb2_push_mand_fdlocks(fdlocks, xid, buf, max_num);
|
|
if (stored_rc)
|
|
rc = stored_rc;
|
|
}
|
|
|
|
kfree(buf);
|
|
free_xid(xid);
|
|
return rc;
|
|
}
|