]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
cifs: fix rename() by ensuring source handle opened with DELETE bit
authorAurelien Aptel <aaptel@suse.com>
Fri, 21 Feb 2020 10:19:06 +0000 (11:19 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Mar 2020 06:18:30 +0000 (07:18 +0100)
commit 86f740f2aed5ea7fe1aa86dc2df0fb4ab0f71088 upstream.

To rename a file in SMB2 we open it with the DELETE access and do a
special SetInfo on it. If the handle is missing the DELETE bit the
server will fail the SetInfo with STATUS_ACCESS_DENIED.

We currently try to reuse any existing opened handle we have with
cifs_get_writable_path(). That function looks for handles with WRITE
access but doesn't check for DELETE, making rename() fail if it finds
a handle to reuse. Simple reproducer below.

To select handles with the DELETE bit, this patch adds a flag argument
to cifs_get_writable_path() and find_writable_file() and the existing
'bool fsuid_only' argument is converted to a flag.

The cifsFileInfo struct only stores the UNIX open mode but not the
original SMB access flags. Since the DELETE bit is not mapped in that
mode, this patch stores the access mask in cifs_fid on file open,
which is accessible from cifsFileInfo.

Simple reproducer:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define E(s) perror(s), exit(1)

int main(int argc, char *argv[])
{
int fd, ret;
if (argc != 3) {
fprintf(stderr, "Usage: %s A B\n"
"create&open A in write mode, "
"rename A to B, close A\n", argv[0]);
return 0;
}

fd = openat(AT_FDCWD, argv[1], O_WRONLY|O_CREAT|O_SYNC, 0666);
if (fd == -1) E("openat()");

ret = rename(argv[1], argv[2]);
if (ret) E("rename()");

ret = close(fd);
if (ret) E("close()");

return ret;
}

$ gcc -o bugrename bugrename.c
$ ./bugrename /mnt/a /mnt/b
rename(): Permission denied

Fixes: 8de9e86c67ba ("cifs: create a helper to find a writeable handle by path name")
CC: Stable <stable@vger.kernel.org>
Signed-off-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com>
Reviewed-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/cifssmb.c
fs/cifs/file.c
fs/cifs/inode.c
fs/cifs/smb1ops.c
fs/cifs/smb2inode.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c

index 239338d5708684a277bd6ceb84072b0695986c02..af789aac8ef77439fefbf7738cab52b3703e4b79 100644 (file)
@@ -1277,6 +1277,7 @@ struct cifs_fid {
        __u64 volatile_fid;     /* volatile file id for smb2 */
        __u8 lease_key[SMB2_LEASE_KEY_SIZE];    /* lease key for smb2 */
        __u8 create_guid[16];
+       __u32 access;
        struct cifs_pending_open *pending_open;
        unsigned int epoch;
 #ifdef CONFIG_CIFS_DEBUG2
@@ -1737,6 +1738,12 @@ static inline bool is_retryable_error(int error)
        return false;
 }
 
+
+/* cifs_get_writable_file() flags */
+#define FIND_WR_ANY         0
+#define FIND_WR_FSUID_ONLY  1
+#define FIND_WR_WITH_DELETE 2
+
 #define   MID_FREE 0
 #define   MID_REQUEST_ALLOCATED 1
 #define   MID_REQUEST_SUBMITTED 2
index d6100299458ad015b5ded9c2fbd5b415cd61ddc3..3b583150bcd5b42cc2657cfd18fa3088b6245396 100644 (file)
@@ -134,11 +134,12 @@ extern bool backup_cred(struct cifs_sb_info *);
 extern bool is_size_safe_to_change(struct cifsInodeInfo *, __u64 eof);
 extern void cifs_update_eof(struct cifsInodeInfo *cifsi, loff_t offset,
                            unsigned int bytes_written);
-extern struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *, bool);
+extern struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *, int);
 extern int cifs_get_writable_file(struct cifsInodeInfo *cifs_inode,
-                                 bool fsuid_only,
+                                 int flags,
                                  struct cifsFileInfo **ret_file);
 extern int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name,
+                                 int flags,
                                  struct cifsFileInfo **ret_file);
 extern struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *, bool);
 extern int cifs_get_readable_path(struct cifs_tcon *tcon, const char *name,
index cc86a67225d1b87fc56f57dbd6e0ad700edb18ce..69c38c379f33c3663de643c6832804307a80d8a6 100644 (file)
@@ -1492,6 +1492,7 @@ openRetry:
        *oplock = rsp->OplockLevel;
        /* cifs fid stays in le */
        oparms->fid->netfid = rsp->Fid;
+       oparms->fid->access = desired_access;
 
        /* Let caller know file was created so we can set the mode. */
        /* Do we care about the CreateAction in any other cases? */
@@ -2115,7 +2116,7 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
                wdata2->tailsz = tailsz;
                wdata2->bytes = cur_len;
 
-               rc = cifs_get_writable_file(CIFS_I(inode), false,
+               rc = cifs_get_writable_file(CIFS_I(inode), FIND_WR_ANY,
                                            &wdata2->cfile);
                if (!wdata2->cfile) {
                        cifs_dbg(VFS, "No writable handle to retry writepages rc=%d\n",
index 043288b5c728a958dae722ace99a8bdd6a7cd52f..5b1460486535ea7a9039f6c7f3933b21c6b543f0 100644 (file)
@@ -1964,7 +1964,7 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
 
 /* Return -EBADF if no handle is found and general rc otherwise */
 int
-cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, bool fsuid_only,
+cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
                       struct cifsFileInfo **ret_file)
 {
        struct cifsFileInfo *open_file, *inv_file = NULL;
@@ -1972,7 +1972,8 @@ cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, bool fsuid_only,
        bool any_available = false;
        int rc = -EBADF;
        unsigned int refind = 0;
-
+       bool fsuid_only = flags & FIND_WR_FSUID_ONLY;
+       bool with_delete = flags & FIND_WR_WITH_DELETE;
        *ret_file = NULL;
 
        /*
@@ -2004,6 +2005,8 @@ refind_writable:
                        continue;
                if (fsuid_only && !uid_eq(open_file->uid, current_fsuid()))
                        continue;
+               if (with_delete && !(open_file->fid.access & DELETE))
+                       continue;
                if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
                        if (!open_file->invalidHandle) {
                                /* found a good writable file */
@@ -2051,12 +2054,12 @@ refind_writable:
 }
 
 struct cifsFileInfo *
-find_writable_file(struct cifsInodeInfo *cifs_inode, bool fsuid_only)
+find_writable_file(struct cifsInodeInfo *cifs_inode, int flags)
 {
        struct cifsFileInfo *cfile;
        int rc;
 
-       rc = cifs_get_writable_file(cifs_inode, fsuid_only, &cfile);
+       rc = cifs_get_writable_file(cifs_inode, flags, &cfile);
        if (rc)
                cifs_dbg(FYI, "couldn't find writable handle rc=%d", rc);
 
@@ -2065,6 +2068,7 @@ find_writable_file(struct cifsInodeInfo *cifs_inode, bool fsuid_only)
 
 int
 cifs_get_writable_path(struct cifs_tcon *tcon, const char *name,
+                      int flags,
                       struct cifsFileInfo **ret_file)
 {
        struct list_head *tmp;
@@ -2091,7 +2095,7 @@ cifs_get_writable_path(struct cifs_tcon *tcon, const char *name,
                kfree(full_path);
                cinode = CIFS_I(d_inode(cfile->dentry));
                spin_unlock(&tcon->open_file_lock);
-               return cifs_get_writable_file(cinode, 0, ret_file);
+               return cifs_get_writable_file(cinode, flags, ret_file);
        }
 
        spin_unlock(&tcon->open_file_lock);
@@ -2168,7 +2172,8 @@ static int cifs_partialpagewrite(struct page *page, unsigned from, unsigned to)
        if (mapping->host->i_size - offset < (loff_t)to)
                to = (unsigned)(mapping->host->i_size - offset);
 
-       rc = cifs_get_writable_file(CIFS_I(mapping->host), false, &open_file);
+       rc = cifs_get_writable_file(CIFS_I(mapping->host), FIND_WR_ANY,
+                                   &open_file);
        if (!rc) {
                bytes_written = cifs_write(open_file, open_file->pid,
                                           write_data, to - from, &offset);
@@ -2361,7 +2366,7 @@ retry:
                if (cfile)
                        cifsFileInfo_put(cfile);
 
-               rc = cifs_get_writable_file(CIFS_I(inode), false, &cfile);
+               rc = cifs_get_writable_file(CIFS_I(inode), FIND_WR_ANY, &cfile);
 
                /* in case of an error store it to return later */
                if (rc)
index 271ef6ba405e1840ddd589f53c9575b4e64f210d..e9a7536c2a5e89578c7e1ee9de3488aff2467869 100644 (file)
@@ -2283,7 +2283,7 @@ cifs_set_file_size(struct inode *inode, struct iattr *attrs,
         * writebehind data than the SMB timeout for the SetPathInfo
         * request would allow
         */
-       open_file = find_writable_file(cifsInode, true);
+       open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
        if (open_file) {
                tcon = tlink_tcon(open_file->tlink);
                server = tcon->ses->server;
@@ -2433,7 +2433,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
                args->ctime = NO_CHANGE_64;
 
        args->device = 0;
-       open_file = find_writable_file(cifsInode, true);
+       open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
        if (open_file) {
                u16 nfid = open_file->fid.netfid;
                u32 npid = open_file->pid;
@@ -2536,7 +2536,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
        rc = 0;
 
        if (attrs->ia_valid & ATTR_MTIME) {
-               rc = cifs_get_writable_file(cifsInode, false, &wfile);
+               rc = cifs_get_writable_file(cifsInode, FIND_WR_ANY, &wfile);
                if (!rc) {
                        tcon = tlink_tcon(wfile->tlink);
                        rc = tcon->ses->server->ops->flush(xid, tcon, &wfile->fid);
index d70a2bb062dfc72febe5d50c39c74ebdc4a5878a..e523c05a44876418225cb73ad906c0792d7335c0 100644 (file)
@@ -765,7 +765,7 @@ smb_set_file_info(struct inode *inode, const char *full_path,
        struct cifs_tcon *tcon;
 
        /* if the file is already open for write, just use that fileid */
-       open_file = find_writable_file(cinode, true);
+       open_file = find_writable_file(cinode, FIND_WR_FSUID_ONLY);
        if (open_file) {
                fid.netfid = open_file->fid.netfid;
                netpid = open_file->pid;
index 5ef5e97a6d13eb8171c5b49ce2f02096dc18a391..bd3669532a091f0ffb47de23195a7b8e4e3614f6 100644 (file)
@@ -526,7 +526,7 @@ smb2_mkdir_setinfo(struct inode *inode, const char *name,
        cifs_i = CIFS_I(inode);
        dosattrs = cifs_i->cifsAttrs | ATTR_READONLY;
        data.Attributes = cpu_to_le32(dosattrs);
-       cifs_get_writable_path(tcon, name, &cfile);
+       cifs_get_writable_path(tcon, name, FIND_WR_ANY, &cfile);
        tmprc = smb2_compound_op(xid, tcon, cifs_sb, name,
                                 FILE_WRITE_ATTRIBUTES, FILE_CREATE,
                                 CREATE_NOT_FILE, ACL_NO_MODE,
@@ -582,7 +582,7 @@ smb2_rename_path(const unsigned int xid, struct cifs_tcon *tcon,
 {
        struct cifsFileInfo *cfile;
 
-       cifs_get_writable_path(tcon, from_name, &cfile);
+       cifs_get_writable_path(tcon, from_name, FIND_WR_WITH_DELETE, &cfile);
 
        return smb2_set_path_attr(xid, tcon, from_name, to_name,
                                  cifs_sb, DELETE, SMB2_OP_RENAME, cfile);
index 65f76be0f45456261c8bb4501c139f3d6b40f410..5b62840853ff606f02233108b1bf146549697f13 100644 (file)
@@ -1366,6 +1366,7 @@ smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock)
 
        cfile->fid.persistent_fid = fid->persistent_fid;
        cfile->fid.volatile_fid = fid->volatile_fid;
+       cfile->fid.access = fid->access;
 #ifdef CONFIG_CIFS_DEBUG2
        cfile->fid.mid = fid->mid;
 #endif /* CIFS_DEBUG2 */
@@ -3225,7 +3226,7 @@ static loff_t smb3_llseek(struct file *file, struct cifs_tcon *tcon, loff_t offs
         * some servers (Windows2016) will not reflect recent writes in
         * QUERY_ALLOCATED_RANGES until SMB2_flush is called.
         */
-       wrcfile = find_writable_file(cifsi, false);
+       wrcfile = find_writable_file(cifsi, FIND_WR_ANY);
        if (wrcfile) {
                filemap_write_and_wait(inode->i_mapping);
                smb2_flush_file(xid, tcon, &wrcfile->fid);
index 6c9497c18f0b80ff3260094d1d02c2fdeb497fbb..fc32fe546c1aa513a861fbdec3b8f0f6c14f4090 100644 (file)
@@ -2749,6 +2749,7 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
        atomic_inc(&tcon->num_remote_opens);
        oparms->fid->persistent_fid = rsp->PersistentFileId;
        oparms->fid->volatile_fid = rsp->VolatileFileId;
+       oparms->fid->access = oparms->desired_access;
 #ifdef CONFIG_CIFS_DEBUG2
        oparms->fid->mid = le64_to_cpu(rsp->sync_hdr.MessageId);
 #endif /* CIFS_DEBUG2 */