cifs: fix use-after-free bug in find_writable_file
[pandora-kernel.git] / fs / cifs / file.c
index cf0b153..f9d2863 100644 (file)
@@ -265,6 +265,8 @@ cifs_new_fileinfo(__u16 fileHandle, struct file *file,
        mutex_init(&pCifsFile->fh_mutex);
        INIT_WORK(&pCifsFile->oplock_break, cifs_oplock_break);
 
+       cifs_sb_active(inode->i_sb);
+
        spin_lock(&cifs_file_list_lock);
        list_add(&pCifsFile->tlist, &(tlink_tcon(tlink)->openFileList));
        /* if readable file instance put first in list*/
@@ -293,7 +295,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
        struct inode *inode = cifs_file->dentry->d_inode;
        struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink);
        struct cifsInodeInfo *cifsi = CIFS_I(inode);
-       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct super_block *sb = inode->i_sb;
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
        struct cifsLockInfo *li, *tmp;
 
        spin_lock(&cifs_file_list_lock);
@@ -345,6 +348,7 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
 
        cifs_put_tlink(cifs_file->tlink);
        dput(cifs_file->dentry);
+       cifs_sb_deactive(sb);
        kfree(cifs_file);
 }
 
@@ -380,7 +384,7 @@ int cifs_open(struct inode *inode, struct file *file)
        cFYI(1, "inode = 0x%p file flags are 0x%x for %s",
                 inode, file->f_flags, full_path);
 
-       if (enable_oplocks)
+       if (tcon->ses->server->oplocks)
                oplock = REQ_OPLOCK;
        else
                oplock = 0;
@@ -505,7 +509,7 @@ static int cifs_reopen_file(struct cifsFileInfo *pCifsFile, bool can_flush)
        cFYI(1, "inode = 0x%p file flags 0x%x for %s",
                 inode, pCifsFile->f_flags, full_path);
 
-       if (enable_oplocks)
+       if (tcon->ses->server->oplocks)
                oplock = REQ_OPLOCK;
        else
                oplock = 0;
@@ -702,6 +706,13 @@ cifs_find_lock_conflict(struct cifsInodeInfo *cinode, struct cifsLockInfo *lock,
                                         lock->type, lock->netfid, conf_lock);
 }
 
+/*
+ * Check if there is another lock that prevents us to set the lock (mandatory
+ * style). If such a lock exists, update the flock structure with its
+ * properties. Otherwise, set the flock type to F_UNLCK if we can cache brlocks
+ * or leave it the same if we can't. Returns 0 if we don't need to request to
+ * the server or 1 otherwise.
+ */
 static int
 cifs_lock_test(struct cifsInodeInfo *cinode, __u64 offset, __u64 length,
               __u8 type, __u16 netfid, struct file_lock *flock)
@@ -739,6 +750,12 @@ cifs_lock_add(struct cifsInodeInfo *cinode, struct cifsLockInfo *lock)
        mutex_unlock(&cinode->lock_mutex);
 }
 
+/*
+ * Set the byte-range lock (mandatory style). Returns:
+ * 1) 0, if we set the lock and don't need to request to the server;
+ * 2) 1, if no locks prevent us but we need to request to the server;
+ * 3) -EACCESS, if there is a lock that prevents us and wait is false.
+ */
 static int
 cifs_lock_add_if(struct cifsInodeInfo *cinode, struct cifsLockInfo *lock,
                 bool wait)
@@ -778,6 +795,13 @@ try_again:
        return rc;
 }
 
+/*
+ * Check if there is another lock that prevents us to set the lock (posix
+ * style). If such a lock exists, update the flock structure with its
+ * properties. Otherwise, set the flock type to F_UNLCK if we can cache brlocks
+ * or leave it the same if we can't. Returns 0 if we don't need to request to
+ * the server or 1 otherwise.
+ */
 static int
 cifs_posix_lock_test(struct file *file, struct file_lock *flock)
 {
@@ -800,6 +824,12 @@ cifs_posix_lock_test(struct file *file, struct file_lock *flock)
        return rc;
 }
 
+/*
+ * Set the byte-range lock (posix style). Returns:
+ * 1) 0, if we set the lock and don't need to request to the server;
+ * 2) 1, if we need to request to the server;
+ * 3) <0, if the error occurs while setting the lock.
+ */
 static int
 cifs_posix_lock_set(struct file *file, struct file_lock *flock)
 {
@@ -809,13 +839,21 @@ cifs_posix_lock_set(struct file *file, struct file_lock *flock)
        if ((flock->fl_flags & FL_POSIX) == 0)
                return rc;
 
+try_again:
        mutex_lock(&cinode->lock_mutex);
        if (!cinode->can_cache_brlcks) {
                mutex_unlock(&cinode->lock_mutex);
                return rc;
        }
-       rc = posix_lock_file_wait(file, flock);
+
+       rc = posix_lock_file(file, flock, NULL);
        mutex_unlock(&cinode->lock_mutex);
+       if (rc == FILE_LOCK_DEFERRED) {
+               rc = wait_event_interruptible(flock->fl_wait, !flock->fl_next);
+               if (!rc)
+                       goto try_again;
+               locks_delete_block(flock);
+       }
        return rc;
 }
 
@@ -848,7 +886,7 @@ cifs_push_mandatory_locks(struct cifsFileInfo *cfile)
        if (!buf) {
                mutex_unlock(&cinode->lock_mutex);
                FreeXid(xid);
-               return rc;
+               return -ENOMEM;
        }
 
        for (i = 0; i < 2; i++) {
@@ -894,16 +932,26 @@ cifs_push_mandatory_locks(struct cifsFileInfo *cfile)
        for (lockp = &inode->i_flock; *lockp != NULL; \
             lockp = &(*lockp)->fl_next)
 
+struct lock_to_push {
+       struct list_head llist;
+       __u64 offset;
+       __u64 length;
+       __u32 pid;
+       __u16 netfid;
+       __u8 type;
+};
+
 static int
 cifs_push_posix_locks(struct cifsFileInfo *cfile)
 {
        struct cifsInodeInfo *cinode = CIFS_I(cfile->dentry->d_inode);
        struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
        struct file_lock *flock, **before;
-       struct cifsLockInfo *lck, *tmp;
+       unsigned int count = 0, i = 0;
        int rc = 0, xid, type;
+       struct list_head locks_to_send, *el;
+       struct lock_to_push *lck, *tmp;
        __u64 length;
-       struct list_head locks_to_send;
 
        xid = GetXid();
 
@@ -914,29 +962,56 @@ cifs_push_posix_locks(struct cifsFileInfo *cfile)
                return rc;
        }
 
+       lock_flocks();
+       cifs_for_each_lock(cfile->dentry->d_inode, before) {
+               if ((*before)->fl_flags & FL_POSIX)
+                       count++;
+       }
+       unlock_flocks();
+
        INIT_LIST_HEAD(&locks_to_send);
 
+       /*
+        * Allocating count locks is enough because no FL_POSIX locks can be
+        * added to the list while we are holding cinode->lock_mutex that
+        * protects locking operations of this inode.
+        */
+       for (; i < count; i++) {
+               lck = kmalloc(sizeof(struct lock_to_push), GFP_KERNEL);
+               if (!lck) {
+                       rc = -ENOMEM;
+                       goto err_out;
+               }
+               list_add_tail(&lck->llist, &locks_to_send);
+       }
+
+       el = locks_to_send.next;
        lock_flocks();
        cifs_for_each_lock(cfile->dentry->d_inode, before) {
                flock = *before;
+               if ((flock->fl_flags & FL_POSIX) == 0)
+                       continue;
+               if (el == &locks_to_send) {
+                       /*
+                        * The list ended. We don't have enough allocated
+                        * structures - something is really wrong.
+                        */
+                       cERROR(1, "Can't push all brlocks!");
+                       break;
+               }
                length = 1 + flock->fl_end - flock->fl_start;
                if (flock->fl_type == F_RDLCK || flock->fl_type == F_SHLCK)
                        type = CIFS_RDLCK;
                else
                        type = CIFS_WRLCK;
-
-               lck = cifs_lock_init(flock->fl_start, length, type,
-                                    cfile->netfid);
-               if (!lck) {
-                       rc = -ENOMEM;
-                       goto send_locks;
-               }
+               lck = list_entry(el, struct lock_to_push, llist);
                lck->pid = flock->fl_pid;
-
-               list_add_tail(&lck->llist, &locks_to_send);
+               lck->netfid = cfile->netfid;
+               lck->length = length;
+               lck->type = type;
+               lck->offset = flock->fl_start;
+               el = el->next;
        }
-
-send_locks:
        unlock_flocks();
 
        list_for_each_entry_safe(lck, tmp, &locks_to_send, llist) {
@@ -953,11 +1028,18 @@ send_locks:
                kfree(lck);
        }
 
+out:
        cinode->can_cache_brlcks = false;
        mutex_unlock(&cinode->lock_mutex);
 
        FreeXid(xid);
        return rc;
+err_out:
+       list_for_each_entry_safe(lck, tmp, &locks_to_send, llist) {
+               list_del(&lck->llist);
+               kfree(lck);
+       }
+       goto out;
 }
 
 static int
@@ -1456,10 +1538,11 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
 struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
                                        bool fsuid_only)
 {
-       struct cifsFileInfo *open_file;
+       struct cifsFileInfo *open_file, *inv_file = NULL;
        struct cifs_sb_info *cifs_sb;
        bool any_available = false;
        int rc;
+       unsigned int refind = 0;
 
        /* Having a null inode here (because mapping->host was set to zero by
        the VFS or MM) should not happen but we had reports of on oops (due to
@@ -1479,40 +1562,25 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
 
        spin_lock(&cifs_file_list_lock);
 refind_writable:
+       if (refind > MAX_REOPEN_ATT) {
+               spin_unlock(&cifs_file_list_lock);
+               return NULL;
+       }
        list_for_each_entry(open_file, &cifs_inode->openFileList, flist) {
                if (!any_available && open_file->pid != current->tgid)
                        continue;
                if (fsuid_only && open_file->uid != current_fsuid())
                        continue;
                if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
-                       cifsFileInfo_get(open_file);
-
                        if (!open_file->invalidHandle) {
                                /* found a good writable file */
+                               cifsFileInfo_get(open_file);
                                spin_unlock(&cifs_file_list_lock);
                                return open_file;
+                       } else {
+                               if (!inv_file)
+                                       inv_file = open_file;
                        }
-
-                       spin_unlock(&cifs_file_list_lock);
-
-                       /* Had to unlock since following call can block */
-                       rc = cifs_reopen_file(open_file, false);
-                       if (!rc)
-                               return open_file;
-
-                       /* if it fails, try another handle if possible */
-                       cFYI(1, "wp failed on reopen file");
-                       cifsFileInfo_put(open_file);
-
-                       spin_lock(&cifs_file_list_lock);
-
-                       /* else we simply continue to the next entry. Thus
-                          we do not loop on reopen errors.  If we
-                          can not reopen the file, for example if we
-                          reconnected to a server with another client
-                          racing to delete or lock the file we would not
-                          make progress if we restarted before the beginning
-                          of the loop here. */
                }
        }
        /* couldn't find useable FH with same pid, try any available */
@@ -1520,7 +1588,31 @@ refind_writable:
                any_available = true;
                goto refind_writable;
        }
+
+       if (inv_file) {
+               any_available = false;
+               cifsFileInfo_get(inv_file);
+       }
+
        spin_unlock(&cifs_file_list_lock);
+
+       if (inv_file) {
+               rc = cifs_reopen_file(inv_file, false);
+               if (!rc)
+                       return inv_file;
+               else {
+                       spin_lock(&cifs_file_list_lock);
+                       list_move_tail(&inv_file->flist,
+                                       &cifs_inode->openFileList);
+                       spin_unlock(&cifs_file_list_lock);
+                       cifsFileInfo_put(inv_file);
+                       spin_lock(&cifs_file_list_lock);
+                       ++refind;
+                       inv_file = NULL;
+                       goto refind_writable;
+               }
+       }
+
        return NULL;
 }
 
@@ -2016,7 +2108,7 @@ cifs_iovec_write(struct file *file, const struct iovec *iov,
 {
        unsigned int written;
        unsigned long num_pages, npages, i;
-       size_t copied, len, cur_len;
+       size_t bytes, copied, len, cur_len;
        ssize_t total_written = 0;
        struct kvec *to_send;
        struct page **pages;
@@ -2074,17 +2166,45 @@ cifs_iovec_write(struct file *file, const struct iovec *iov,
        do {
                size_t save_len = cur_len;
                for (i = 0; i < npages; i++) {
-                       copied = min_t(const size_t, cur_len, PAGE_CACHE_SIZE);
+                       bytes = min_t(const size_t, cur_len, PAGE_CACHE_SIZE);
                        copied = iov_iter_copy_from_user(pages[i], &it, 0,
-                                                        copied);
+                                                        bytes);
                        cur_len -= copied;
                        iov_iter_advance(&it, copied);
                        to_send[i+1].iov_base = kmap(pages[i]);
                        to_send[i+1].iov_len = copied;
+                       /*
+                        * If we didn't copy as much as we expected, then that
+                        * may mean we trod into an unmapped area. Stop copying
+                        * at that point. On the next pass through the big
+                        * loop, we'll likely end up getting a zero-length
+                        * write and bailing out of it.
+                        */
+                       if (copied < bytes)
+                               break;
                }
 
                cur_len = save_len - cur_len;
 
+               /*
+                * If we have no data to send, then that probably means that
+                * the copy above failed altogether. That's most likely because
+                * the address in the iovec was bogus. Set the rc to -EFAULT,
+                * free anything we allocated and bail out.
+                */
+               if (!cur_len) {
+                       kunmap(pages[0]);
+                       if (!total_written)
+                               total_written = -EFAULT;
+                       break;
+               }
+
+               /*
+                * i + 1 now represents the number of pages we actually used in
+                * the copy phase above.
+                */
+               npages = min(npages, i + 1);
+
                do {
                        if (open_file->invalidHandle) {
                                rc = cifs_reopen_file(open_file, false);