aboutsummaryrefslogtreecommitdiffstats
path: root/fs/dcache.c
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2014-09-29 14:46:30 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2014-09-29 14:46:30 -0400
commit6d13f69444bd3d4888e43f7756449748f5a98bad (patch)
tree1a6b2e7df1dc85a0fbe4273621d54fda49523ec2 /fs/dcache.c
parent1e3827bf8aebe29af2d6e49b89d85dfae4d0154f (diff)
missing data dependency barrier in prepend_name()
AFAICS, prepend_name() is broken on SMP alpha. Disclaimer: I don't have SMP alpha boxen to reproduce it on. However, it really looks like the race is real. CPU1: d_path() on /mnt/ramfs/<255-character>/foo CPU2: mv /mnt/ramfs/<255-character> /mnt/ramfs/<63-character> CPU2 does d_alloc(), which allocates an external name, stores the name there including terminating NUL, does smp_wmb() and stores its address in dentry->d_name.name. It proceeds to d_add(dentry, NULL) and d_move() old dentry over to that. ->d_name.name value ends up in that dentry. In the meanwhile, CPU1 gets to prepend_name() for that dentry. It fetches ->d_name.name and ->d_name.len; the former ends up pointing to new name (64-byte kmalloc'ed array), the latter - 255 (length of the old name). Nothing to force the ordering there, and normally that would be OK, since we'd run into the terminating NUL and stop. Except that it's alpha, and we'd need a data dependency barrier to guarantee that we see that store of NUL __d_alloc() has done. In a similar situation dentry_cmp() would survive; it does explicit smp_read_barrier_depends() after fetching ->d_name.name. prepend_name() doesn't and it risks walking past the end of kmalloc'ed object and possibly oops due to taking a page fault in kernel mode. Cc: stable@vger.kernel.org # 3.12+ Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/dcache.c')
-rw-r--r--fs/dcache.c5
1 files changed, 5 insertions, 0 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index cb25a1a5e307..e7484f9c73b4 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2810,6 +2810,9 @@ static int prepend(char **buffer, int *buflen, const char *str, int namelen)
2810 * the beginning of the name. The sequence number check at the caller will 2810 * the beginning of the name. The sequence number check at the caller will
2811 * retry it again when a d_move() does happen. So any garbage in the buffer 2811 * retry it again when a d_move() does happen. So any garbage in the buffer
2812 * due to mismatched pointer and length will be discarded. 2812 * due to mismatched pointer and length will be discarded.
2813 *
2814 * Data dependency barrier is needed to make sure that we see that terminating
2815 * NUL. Alpha strikes again, film at 11...
2813 */ 2816 */
2814static int prepend_name(char **buffer, int *buflen, struct qstr *name) 2817static int prepend_name(char **buffer, int *buflen, struct qstr *name)
2815{ 2818{
@@ -2817,6 +2820,8 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
2817 u32 dlen = ACCESS_ONCE(name->len); 2820 u32 dlen = ACCESS_ONCE(name->len);
2818 char *p; 2821 char *p;
2819 2822
2823 smp_read_barrier_depends();
2824
2820 *buflen -= dlen + 1; 2825 *buflen -= dlen + 1;
2821 if (*buflen < 0) 2826 if (*buflen < 0)
2822 return -ENAMETOOLONG; 2827 return -ENAMETOOLONG;