hpfs: deadlock and race in directory lseek()
authorAl Viro <viro@zeniv.linux.org.uk>
Sat, 18 May 2013 06:38:52 +0000 (02:38 -0400)
committerBen Hutchings <ben@decadent.org.uk>
Tue, 1 Apr 2014 23:59:00 +0000 (00:59 +0100)
commit 31abdab9c11bb1694ecd1476a7edbe8e964d94ac upstream.

For one thing, there's an ABBA deadlock on hpfs fs-wide lock and i_mutex
in hpfs_dir_lseek() - there's a lot of methods that grab the former with
the caller already holding the latter, so it must take i_mutex first.

For another, locking the damn thing, carefully validating the offset,
then dropping locks and assigning the offset is obviously racy.

Moreover, we _must_ do hpfs_add_pos(), or the machinery in dnode.c
won't modify the sucker on B-tree surgeries.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
fs/hpfs/dir.c

index 2fa0089..46549c7 100644 (file)
@@ -33,25 +33,27 @@ static loff_t hpfs_dir_lseek(struct file *filp, loff_t off, int whence)
        if (whence == SEEK_DATA || whence == SEEK_HOLE)
                return -EINVAL;
 
+       mutex_lock(&i->i_mutex);
        hpfs_lock(s);
 
        /*printk("dir lseek\n");*/
        if (new_off == 0 || new_off == 1 || new_off == 11 || new_off == 12 || new_off == 13) goto ok;
-       mutex_lock(&i->i_mutex);
        pos = ((loff_t) hpfs_de_as_down_as_possible(s, hpfs_inode->i_dno) << 4) + 1;
        while (pos != new_off) {
                if (map_pos_dirent(i, &pos, &qbh)) hpfs_brelse4(&qbh);
                else goto fail;
                if (pos == 12) goto fail;
        }
-       mutex_unlock(&i->i_mutex);
+       hpfs_add_pos(i, &filp->f_pos);
 ok:
+       filp->f_pos = new_off;
        hpfs_unlock(s);
-       return filp->f_pos = new_off;
-fail:
        mutex_unlock(&i->i_mutex);
+       return new_off;
+fail:
        /*printk("illegal lseek: %016llx\n", new_off);*/
        hpfs_unlock(s);
+       mutex_unlock(&i->i_mutex);
        return -ESPIPE;
 }