diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2014-03-20 15:18:22 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2014-03-23 00:32:55 -0400 |
commit | b37199e626b31e1175fb06764c5d1d687723aac2 (patch) | |
tree | 0ac51a84f15fa251800fb40191dcf4a86c4c04c2 /fs | |
parent | e825196d48d2b89a6ec3a8eff280098d2a78207e (diff) |
rcuwalk: recheck mount_lock after mountpoint crossing attempts
We can get false negative from __lookup_mnt() if an unrelated vfsmount
gets moved. In that case legitimize_mnt() is guaranteed to fail,
and we will fall back to non-RCU walk... unless we end up running
into a hard error on a filesystem object we wouldn't have reached
if not for that false negative. IOW, delaying that check until
the end of pathname resolution is wrong - we should recheck right
after we attempt to cross the mountpoint. We don't need to recheck
unless we see d_mountpoint() being true - in that case even if
we have just raced with mount/umount, we can simply go on as if
we'd come at the moment when the sucker wasn't a mountpoint; if we
run into a hard error as the result, it was a legitimate outcome.
__lookup_mnt() returning NULL is different in that respect, since
it might've happened due to operation on completely unrelated
mountpoint.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/namei.c | 29 |
1 files changed, 13 insertions, 16 deletions
diff --git a/fs/namei.c b/fs/namei.c index 2f730ef9b4b3..4b491b431990 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
@@ -1109,7 +1109,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path, | |||
1109 | return false; | 1109 | return false; |
1110 | 1110 | ||
1111 | if (!d_mountpoint(path->dentry)) | 1111 | if (!d_mountpoint(path->dentry)) |
1112 | break; | 1112 | return true; |
1113 | 1113 | ||
1114 | mounted = __lookup_mnt(path->mnt, path->dentry); | 1114 | mounted = __lookup_mnt(path->mnt, path->dentry); |
1115 | if (!mounted) | 1115 | if (!mounted) |
@@ -1125,20 +1125,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path, | |||
1125 | */ | 1125 | */ |
1126 | *inode = path->dentry->d_inode; | 1126 | *inode = path->dentry->d_inode; |
1127 | } | 1127 | } |
1128 | return true; | 1128 | return read_seqretry(&mount_lock, nd->m_seq); |
1129 | } | ||
1130 | |||
1131 | static void follow_mount_rcu(struct nameidata *nd) | ||
1132 | { | ||
1133 | while (d_mountpoint(nd->path.dentry)) { | ||
1134 | struct mount *mounted; | ||
1135 | mounted = __lookup_mnt(nd->path.mnt, nd->path.dentry); | ||
1136 | if (!mounted) | ||
1137 | break; | ||
1138 | nd->path.mnt = &mounted->mnt; | ||
1139 | nd->path.dentry = mounted->mnt.mnt_root; | ||
1140 | nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq); | ||
1141 | } | ||
1142 | } | 1129 | } |
1143 | 1130 | ||
1144 | static int follow_dotdot_rcu(struct nameidata *nd) | 1131 | static int follow_dotdot_rcu(struct nameidata *nd) |
@@ -1166,7 +1153,17 @@ static int follow_dotdot_rcu(struct nameidata *nd) | |||
1166 | break; | 1153 | break; |
1167 | nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq); | 1154 | nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq); |
1168 | } | 1155 | } |
1169 | follow_mount_rcu(nd); | 1156 | while (d_mountpoint(nd->path.dentry)) { |
1157 | struct mount *mounted; | ||
1158 | mounted = __lookup_mnt(nd->path.mnt, nd->path.dentry); | ||
1159 | if (!mounted) | ||
1160 | break; | ||
1161 | nd->path.mnt = &mounted->mnt; | ||
1162 | nd->path.dentry = mounted->mnt.mnt_root; | ||
1163 | nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq); | ||
1164 | if (!read_seqretry(&mount_lock, nd->m_seq)) | ||
1165 | goto failed; | ||
1166 | } | ||
1170 | nd->inode = nd->path.dentry->d_inode; | 1167 | nd->inode = nd->path.dentry->d_inode; |
1171 | return 0; | 1168 | return 0; |
1172 | 1169 | ||