aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2017-01-02 20:18:43 -0500
committerEric W. Biederman <ebiederm@xmission.com>2017-01-09 19:34:43 -0500
commit3895dbf8985f656675b5bde610723a29cbce3fa7 (patch)
tree91d4517f09918fd573998eb40b8f35f08ed1c470
parent0c744ea4f77d72b3dcebb7a8f2684633ec79be88 (diff)
mnt: Protect the mountpoint hashtable with mount_lock
Protecting the mountpoint hashtable with namespace_sem was sufficient until a call to umount_mnt was added to mntput_no_expire. At which point it became possible for multiple calls of put_mountpoint on the same hash chain to happen on the same time. Kristen Johansen <kjlx@templeofstupid.com> reported: > This can cause a panic when simultaneous callers of put_mountpoint > attempt to free the same mountpoint. This occurs because some callers > hold the mount_hash_lock, while others hold the namespace lock. Some > even hold both. > > In this submitter's case, the panic manifested itself as a GP fault in > put_mountpoint() when it called hlist_del() and attempted to dereference > a m_hash.pprev that had been poisioned by another thread. Al Viro observed that the simple fix is to switch from using the namespace_sem to the mount_lock to protect the mountpoint hash table. I have taken Al's suggested patch moved put_mountpoint in pivot_root (instead of taking mount_lock an additional time), and have replaced new_mountpoint with get_mountpoint a function that does the hash table lookup and addition under the mount_lock. The introduction of get_mounptoint ensures that only the mount_lock is needed to manipulate the mountpoint hashtable. d_set_mounted is modified to only set DCACHE_MOUNTED if it is not already set. This allows get_mountpoint to use the setting of DCACHE_MOUNTED to ensure adding a struct mountpoint for a dentry happens exactly once. Cc: stable@vger.kernel.org Fixes: ce07d891a089 ("mnt: Honor MNT_LOCKED when detaching mounts") Reported-by: Krister Johansen <kjlx@templeofstupid.com> Suggested-by: Al Viro <viro@ZenIV.linux.org.uk> Acked-by: Al Viro <viro@ZenIV.linux.org.uk> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
-rw-r--r--fs/dcache.c7
-rw-r--r--fs/namespace.c64
2 files changed, 50 insertions, 21 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index 769903dbc19d..95d71eda8142 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1336,8 +1336,11 @@ int d_set_mounted(struct dentry *dentry)
1336 } 1336 }
1337 spin_lock(&dentry->d_lock); 1337 spin_lock(&dentry->d_lock);
1338 if (!d_unlinked(dentry)) { 1338 if (!d_unlinked(dentry)) {
1339 dentry->d_flags |= DCACHE_MOUNTED; 1339 ret = -EBUSY;
1340 ret = 0; 1340 if (!d_mountpoint(dentry)) {
1341 dentry->d_flags |= DCACHE_MOUNTED;
1342 ret = 0;
1343 }
1341 } 1344 }
1342 spin_unlock(&dentry->d_lock); 1345 spin_unlock(&dentry->d_lock);
1343out: 1346out:
diff --git a/fs/namespace.c b/fs/namespace.c
index b5b1259e064f..487ba30bb5c6 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -742,26 +742,50 @@ static struct mountpoint *lookup_mountpoint(struct dentry *dentry)
742 return NULL; 742 return NULL;
743} 743}
744 744
745static struct mountpoint *new_mountpoint(struct dentry *dentry) 745static struct mountpoint *get_mountpoint(struct dentry *dentry)
746{ 746{
747 struct hlist_head *chain = mp_hash(dentry); 747 struct mountpoint *mp, *new = NULL;
748 struct mountpoint *mp;
749 int ret; 748 int ret;
750 749
751 mp = kmalloc(sizeof(struct mountpoint), GFP_KERNEL); 750 if (d_mountpoint(dentry)) {
752 if (!mp) 751mountpoint:
752 read_seqlock_excl(&mount_lock);
753 mp = lookup_mountpoint(dentry);
754 read_sequnlock_excl(&mount_lock);
755 if (mp)
756 goto done;
757 }
758
759 if (!new)
760 new = kmalloc(sizeof(struct mountpoint), GFP_KERNEL);
761 if (!new)
753 return ERR_PTR(-ENOMEM); 762 return ERR_PTR(-ENOMEM);
754 763
764
765 /* Exactly one processes may set d_mounted */
755 ret = d_set_mounted(dentry); 766 ret = d_set_mounted(dentry);
756 if (ret) {
757 kfree(mp);
758 return ERR_PTR(ret);
759 }
760 767
761 mp->m_dentry = dentry; 768 /* Someone else set d_mounted? */
762 mp->m_count = 1; 769 if (ret == -EBUSY)
763 hlist_add_head(&mp->m_hash, chain); 770 goto mountpoint;
764 INIT_HLIST_HEAD(&mp->m_list); 771
772 /* The dentry is not available as a mountpoint? */
773 mp = ERR_PTR(ret);
774 if (ret)
775 goto done;
776
777 /* Add the new mountpoint to the hash table */
778 read_seqlock_excl(&mount_lock);
779 new->m_dentry = dentry;
780 new->m_count = 1;
781 hlist_add_head(&new->m_hash, mp_hash(dentry));
782 INIT_HLIST_HEAD(&new->m_list);
783 read_sequnlock_excl(&mount_lock);
784
785 mp = new;
786 new = NULL;
787done:
788 kfree(new);
765 return mp; 789 return mp;
766} 790}
767 791
@@ -1595,11 +1619,11 @@ void __detach_mounts(struct dentry *dentry)
1595 struct mount *mnt; 1619 struct mount *mnt;
1596 1620
1597 namespace_lock(); 1621 namespace_lock();
1622 lock_mount_hash();
1598 mp = lookup_mountpoint(dentry); 1623 mp = lookup_mountpoint(dentry);
1599 if (IS_ERR_OR_NULL(mp)) 1624 if (IS_ERR_OR_NULL(mp))
1600 goto out_unlock; 1625 goto out_unlock;
1601 1626
1602 lock_mount_hash();
1603 event++; 1627 event++;
1604 while (!hlist_empty(&mp->m_list)) { 1628 while (!hlist_empty(&mp->m_list)) {
1605 mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); 1629 mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list);
@@ -1609,9 +1633,9 @@ void __detach_mounts(struct dentry *dentry)
1609 } 1633 }
1610 else umount_tree(mnt, UMOUNT_CONNECTED); 1634 else umount_tree(mnt, UMOUNT_CONNECTED);
1611 } 1635 }
1612 unlock_mount_hash();
1613 put_mountpoint(mp); 1636 put_mountpoint(mp);
1614out_unlock: 1637out_unlock:
1638 unlock_mount_hash();
1615 namespace_unlock(); 1639 namespace_unlock();
1616} 1640}
1617 1641
@@ -2038,9 +2062,7 @@ retry:
2038 namespace_lock(); 2062 namespace_lock();
2039 mnt = lookup_mnt(path); 2063 mnt = lookup_mnt(path);
2040 if (likely(!mnt)) { 2064 if (likely(!mnt)) {
2041 struct mountpoint *mp = lookup_mountpoint(dentry); 2065 struct mountpoint *mp = get_mountpoint(dentry);
2042 if (!mp)
2043 mp = new_mountpoint(dentry);
2044 if (IS_ERR(mp)) { 2066 if (IS_ERR(mp)) {
2045 namespace_unlock(); 2067 namespace_unlock();
2046 inode_unlock(dentry->d_inode); 2068 inode_unlock(dentry->d_inode);
@@ -2059,7 +2081,11 @@ retry:
2059static void unlock_mount(struct mountpoint *where) 2081static void unlock_mount(struct mountpoint *where)
2060{ 2082{
2061 struct dentry *dentry = where->m_dentry; 2083 struct dentry *dentry = where->m_dentry;
2084
2085 read_seqlock_excl(&mount_lock);
2062 put_mountpoint(where); 2086 put_mountpoint(where);
2087 read_sequnlock_excl(&mount_lock);
2088
2063 namespace_unlock(); 2089 namespace_unlock();
2064 inode_unlock(dentry->d_inode); 2090 inode_unlock(dentry->d_inode);
2065} 2091}
@@ -3135,9 +3161,9 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
3135 touch_mnt_namespace(current->nsproxy->mnt_ns); 3161 touch_mnt_namespace(current->nsproxy->mnt_ns);
3136 /* A moved mount should not expire automatically */ 3162 /* A moved mount should not expire automatically */
3137 list_del_init(&new_mnt->mnt_expire); 3163 list_del_init(&new_mnt->mnt_expire);
3164 put_mountpoint(root_mp);
3138 unlock_mount_hash(); 3165 unlock_mount_hash();
3139 chroot_fs_refs(&root, &new); 3166 chroot_fs_refs(&root, &new);
3140 put_mountpoint(root_mp);
3141 error = 0; 3167 error = 0;
3142out4: 3168out4:
3143 unlock_mount(old_mp); 3169 unlock_mount(old_mp);