Skip to content

Commit

Permalink
Merge branch 'fix-unmapped-word-at-a-time'
Browse files Browse the repository at this point in the history
Jana Saout confirmed that this fixes the page faults he saw.

His problem was triggered by ocfs2 and autofs symlink lookups, where the
symlink allocation was at the end of a page.  But the deeper reason
seems to be the use of Xen-PV, which is what then causes him to have all
these unmapped pages, which is what then makes it a problem when the
unaligned word-at-a-time code fetches data past the end of a page.

* fix-unmapped-word-at-a-time:
  vfs: make word-at-a-time accesses handle a non-existing page
  • Loading branch information
Linus Torvalds committed May 4, 2012
2 parents 0a6ba09 + e419b4c commit f462204
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 7 deletions.
2 changes: 1 addition & 1 deletion arch/x86/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ config X86
select CLKEVT_I8253
select ARCH_HAVE_NMI_SAFE_CMPXCHG
select GENERIC_IOMAP
select DCACHE_WORD_ACCESS if !DEBUG_PAGEALLOC
select DCACHE_WORD_ACCESS

config INSTRUCTION_DECODER
def_bool (KPROBES || PERF_EVENTS)
Expand Down
33 changes: 33 additions & 0 deletions arch/x86/include/asm/word-at-a-time.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,37 @@ static inline unsigned long has_zero(unsigned long a)
return ((a - REPEAT_BYTE(0x01)) & ~a) & REPEAT_BYTE(0x80);
}

/*
* Load an unaligned word from kernel space.
*
* In the (very unlikely) case of the word being a page-crosser
* and the next page not being mapped, take the exception and
* return zeroes in the non-existing part.
*/
static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret, dummy;

asm(
"1:\tmov %2,%0\n"
"2:\n"
".section .fixup,\"ax\"\n"
"3:\t"
"lea %2,%1\n\t"
"and %3,%1\n\t"
"mov (%1),%0\n\t"
"leal %2,%%ecx\n\t"
"andl %4,%%ecx\n\t"
"shll $3,%%ecx\n\t"
"shr %%cl,%0\n\t"
"jmp 2b\n"
".previous\n"
_ASM_EXTABLE(1b, 3b)
:"=&r" (ret),"=&c" (dummy)
:"m" (*(unsigned long *)addr),
"i" (-sizeof(unsigned long)),
"i" (sizeof(unsigned long)-1));
return ret;
}

#endif /* _ASM_WORD_AT_A_TIME_H */
26 changes: 22 additions & 4 deletions fs/dcache.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,29 @@ int proc_nr_dentry(ctl_table *table, int write, void __user *buffer,
* Compare 2 name strings, return 0 if they match, otherwise non-zero.
* The strings are both count bytes long, and count is non-zero.
*/
#ifdef CONFIG_DCACHE_WORD_ACCESS

#include <asm/word-at-a-time.h>
/*
* NOTE! 'cs' and 'scount' come from a dentry, so it has a
* aligned allocation for this particular component. We don't
* strictly need the load_unaligned_zeropad() safety, but it
* doesn't hurt either.
*
* In contrast, 'ct' and 'tcount' can be from a pathname, and do
* need the careful unaligned handling.
*/
static inline int dentry_cmp(const unsigned char *cs, size_t scount,
const unsigned char *ct, size_t tcount)
{
#ifdef CONFIG_DCACHE_WORD_ACCESS
unsigned long a,b,mask;

if (unlikely(scount != tcount))
return 1;

for (;;) {
a = *(unsigned long *)cs;
b = *(unsigned long *)ct;
a = load_unaligned_zeropad(cs);
b = load_unaligned_zeropad(ct);
if (tcount < sizeof(unsigned long))
break;
if (unlikely(a != b))
Expand All @@ -165,7 +176,13 @@ static inline int dentry_cmp(const unsigned char *cs, size_t scount,
}
mask = ~(~0ul << tcount*8);
return unlikely(!!((a ^ b) & mask));
}

#else

static inline int dentry_cmp(const unsigned char *cs, size_t scount,
const unsigned char *ct, size_t tcount)
{
if (scount != tcount)
return 1;

Expand All @@ -177,9 +194,10 @@ static inline int dentry_cmp(const unsigned char *cs, size_t scount,
tcount--;
} while (tcount);
return 0;
#endif
}

#endif

static void __d_free(struct rcu_head *head)
{
struct dentry *dentry = container_of(head, struct dentry, d_u.d_rcu);
Expand Down
4 changes: 2 additions & 2 deletions fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -1429,7 +1429,7 @@ unsigned int full_name_hash(const unsigned char *name, unsigned int len)
unsigned long hash = 0;

for (;;) {
a = *(unsigned long *)name;
a = load_unaligned_zeropad(name);
if (len < sizeof(unsigned long))
break;
hash += a;
Expand Down Expand Up @@ -1459,7 +1459,7 @@ static inline unsigned long hash_name(const char *name, unsigned int *hashp)
do {
hash = (hash + a) * 9;
len += sizeof(unsigned long);
a = *(unsigned long *)(name+len);
a = load_unaligned_zeropad(name+len);
/* Do we have any NUL or '/' bytes in this word? */
mask = has_zero(a) | has_zero(a ^ REPEAT_BYTE('/'));
} while (!mask);
Expand Down

0 comments on commit f462204

Please sign in to comment.