[2.6.38] Deadlock between rename_lock and vfsmount_lock.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Tetsuo Handa wrote:
> I got a freeze (without lockdep warning) with a small out-of-tree patch
> applied on 2.6.38. It seems to me that the deadlock occurred when running
> pivot_root(). I guess it is spinning at spin_lock() below.

It seems to me that this is a deadlock between rename_lock and vfsmount_lock.

CPU 0: __d_path()                       CPU 1: pivot_root()
write_seqlock(&rename_lock);            br_write_lock(vfsmount_lock);
br_read_lock(vfsmount_lock);            seq = read_seqbegin(&rename_lock);

__d_path() calls prepend_path() with rename_lock lock held for write.

char *__d_path(const struct path *path, struct path *root,
               char *buf, int buflen)
{
        char *res = buf + buflen;
        int error;

        prepend(&res, &buflen, "\0", 1);
        write_seqlock(&rename_lock);
        error = prepend_path(path, root, &res, &buflen);
        write_sequnlock(&rename_lock);

        if (error)
                return ERR_PTR(error);
        return res;
}

prepend_path() tries to hold vfsmount_lock for read.

static int prepend_path(const struct path *path, struct path *root,
                        char **buffer, int *buflen)
{
        struct dentry *dentry = path->dentry;
        struct vfsmount *vfsmnt = path->mnt;
        bool slash = false;
        int error = 0;

        br_read_lock(vfsmount_lock);
        while (dentry != root->dentry || vfsmnt != root->mnt) {
                struct dentry * parent;

                if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
                        /* Global root? */
                        if (vfsmnt->mnt_parent == vfsmnt) {
                                goto global_root;
                        }
                        dentry = vfsmnt->mnt_mountpoint;
                        vfsmnt = vfsmnt->mnt_parent;
                        continue;
                }
                parent = dentry->d_parent;
                prefetch(parent);
                spin_lock(&dentry->d_lock);
                error = prepend_name(buffer, buflen, &dentry->d_name);
                spin_unlock(&dentry->d_lock);
                if (!error)
                        error = prepend(buffer, buflen, "/", 1);
                if (error)
                        break;

                slash = true;
                dentry = parent;
        }

out:
        if (!error && !slash)
                error = prepend(buffer, buflen, "/", 1);

        br_read_unlock(vfsmount_lock);
        return error;

global_root:
        /*
         * Filesystems needing to implement special "root names"
         * should do so with ->d_dname()
         */
        if (IS_ROOT(dentry) &&
            (dentry->d_name.len != 1 || dentry->d_name.name[0] != '/')) {
                WARN(1, "Root dentry has weird name <%.*s>\n",
                     (int) dentry->d_name.len, dentry->d_name.name);
        }
        root->mnt = vfsmnt;
        root->dentry = dentry;
        goto out;
}

pivot_root() calls is_subdir() with vfsmount_lock lock held for write.

SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
                const char __user *, put_old)
{
(...snipped...)
        br_write_lock(vfsmount_lock);
        if (tmp != new.mnt) {
                for (;;) {
                        if (tmp->mnt_parent == tmp)
                                goto out3; /* already mounted on put_old */
                        if (tmp->mnt_parent == new.mnt)
                                break;
                        tmp = tmp->mnt_parent;
                }
                if (!is_subdir(tmp->mnt_mountpoint, new.dentry))
                        goto out3;
        } else if (!is_subdir(old.dentry, new.dentry))
                goto out3;
        detach_mnt(new.mnt, &parent_path);
        detach_mnt(root.mnt, &root_parent);
        /* mount old root on put_old */
        attach_mnt(root.mnt, &old);
        /* mount new_root on / */
        attach_mnt(new.mnt, &root_parent);
        touch_mnt_namespace(current->nsproxy->mnt_ns);
        br_write_unlock(vfsmount_lock);
(...snipped...)
}

is_subdir() tries to hold rename_lock for read.

int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
{
        int result;
        unsigned seq;

        if (new_dentry == old_dentry)
                return 1;

        do {
                /* for restarting inner loop in case of seq retry */
                seq = read_seqbegin(&rename_lock);
                /*
                 * Need rcu_readlock to protect against the d_parent trashing
                 * due to d_move
                 */
                rcu_read_lock();
                if (d_ancestor(old_dentry, new_dentry))
                        result = 1;
                else
                        result = 0;
                rcu_read_unlock();
        } while (read_seqretry(&rename_lock, seq));

        return result;
}

is_subdir() tries to hold rename_lock lock, but it is held by __d_path().
prepend_path() tries to hold vfsmount_lock lock but is held by pivot_root().

Regards.
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux