ext4: avoid utf8_strncasecmp() with unstable name
Subsystems (3)
PATCH v2 1/1 ext4: avoid utf8_strncasecmp() with unstable name
From: Eric Biggers <[email protected]>
If the dentry name passed to ->d_compare() fits in dentry::d_iname, then it may be concurrently modified by a rename. This can cause undefined behavior (possibly out-of-bounds memory accesses or crashes) in utf8_strncasecmp(), since fs/unicode/ isn't written to handle strings that may be concurrently modified.
Fix this by first copying the filename to a stack buffer if needed. This way we get a stable snapshot of the filename.
---
v2: use memcpy() + barrier() instead of a byte-by-byte copy.
fs/ext4/dir.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
fs/ext4/dir.c +16 -0
@@ -675,6 +675,7 @@ static int ext4_d_compare(const struct dentry *dentry, unsigned int len,struct qstr qstr = {.name = str, .len = len };const struct dentry *parent =READ_ONCE (dentry->d_parent);const struct inode *inode =READ_ONCE (parent->d_inode); +char strbuf[DNAME_INLINE_LEN ];if (!inode || !IS_CASEFOLDED (inode) || !EXT4_SB (inode->i_sb)->s_encoding) {
@@ -683,6 +684,21 @@ static int ext4_d_compare(const struct dentry *dentry, unsigned int len,return memcmp (str, name->name, len); } +/* +* If the dentry name is stored in-line, then it may be concurrently +* modified by a rename. If this happens, the VFS will eventually retry +* the lookup, so it doesn't matter what ->d_compare() returns. +* However, it's unsafe to call utf8_strncasecmp() with an unstable +* string. Therefore, we have to copy the name into a temporary buffer. +*/ +if (len <=DNAME_INLINE_LEN -1 ) { +memcpy (strbuf, str, len); + strbuf[len] =0 ; + qstr.name = strbuf; +/* prevent compiler from optimizing out the temporary buffer */ +barrier (); + } +return ext4_ci_compare (inode, name, &qstr, false); }
Signature · 1 line
2.26.2
Quoted 12 lines · Eric Biggers
From: Eric Biggers <[email protected]>
If the dentry name passed to ->d_compare() fits in
dentry::d_iname, then it may be concurrently modified by a rename. This can cause undefined behavior (possibly out-of-bounds memory accesses or crashes) inutf8_strncasecmp(), sincefs/unicode/isn't written to handle strings that may be concurrently modified.Fix this by first copying the filename to a stack buffer if needed. This way we get a stable snapshot of the filename.
Quoted 6 lines
Fixes b886ee3e778e ("ext4: Support case-insensitive file name lookups")Cc <[email protected]> # v5.2+Cc Al Viro <[email protected]>Cc Daniel Rosenberg <[email protected]>Cc Gabriel Krisman Bertazi <[email protected]>Signed-off-by Eric Biggers <[email protected]>
LGTM.
Quoted 42 lines
---
v2: use
memcpy()+barrier()instead of a byte-by-byte copy.
fs/ext4/dir.c| 16 ++++++++++++++++1 file changed, 16 insertions(+)fs/ext4/dir.c +16
-1@@ -675,6 +675,7 @@ static int ext4_d_compare(const struct dentry *dentry, unsigned int len,struct qstr qstr = {.name = str, .len = len };const struct dentry *parent =READ_ONCE (dentry->d_parent);const struct inode *inode =READ_ONCE (parent->d_inode); +char strbuf[DNAME_INLINE_LEN ];if (!inode || !IS_CASEFOLDED (inode) || !EXT4_SB (inode->i_sb)->s_encoding) {@@ -683,6 +684,21 @@ static int ext4_d_compare(const struct dentry *dentry, unsigned int len,return memcmp (str, name->name, len); } +/* +* If the dentry name is stored in-line, then it may be concurrently +* modified by a rename. If this happens, the VFS will eventually retry +* the lookup, so it doesn't matter what ->d_compare() returns. +* However, it's unsafe to call utf8_strncasecmp() with an unstable +* string. Therefore, we have to copy the name into a temporary buffer. +*/ +if (len <=DNAME_INLINE_LEN -1 ) { +memcpy (strbuf, str, len); + strbuf[len] =0 ; + qstr.name = strbuf; +/* prevent compiler from optimizing out the temporary buffer */ +barrier (); + } +return ext4_ci_compare (inode, name, &qstr, false); } --
Quoted 2 lines
2.26.2
Cheers, Andreas
Quoted 20 lines · Andreas Dilger
Quoted 12 lines · Eric Biggers
From: Eric Biggers <[email protected]>
If the dentry name passed to ->d_compare() fits in
dentry::d_iname, then it may be concurrently modified by a rename. This can cause undefined behavior (possibly out-of-bounds memory accesses or crashes) inutf8_strncasecmp(), sincefs/unicode/isn't written to handle strings that may be concurrently modified.Fix this by first copying the filename to a stack buffer if needed. This way we get a stable snapshot of the filename.
Quoted 6 lines
Fixes b886ee3e778e ("ext4: Support case-insensitive file name lookups")Cc <[email protected]> # v5.2+Cc Al Viro <[email protected]>Cc Daniel Rosenberg <[email protected]>Cc Gabriel Krisman Bertazi <[email protected]>Signed-off-by Eric Biggers <[email protected]>
Quoted 3 lines
LGTM.
Reviewed-by Andreas Dilger <[email protected]>
Thanks, applied.
- Ted
Quoted 17 lines · Eric Biggers
From: Eric Biggers <[email protected]>
If the dentry name passed to ->d_compare() fits in
dentry::d_iname, then it may be concurrently modified by a rename. This can cause undefined behavior (possibly out-of-bounds memory accesses or crashes) inutf8_strncasecmp(), sincefs/unicode/isn't written to handle strings that may be concurrently modified.Fix this by first copying the filename to a stack buffer if needed. This way we get a stable snapshot of the filename.
Fixes b886ee3e778e ("ext4: Support case-insensitive file name lookups")Cc <[email protected]> # v5.2+Cc Al Viro <[email protected]>Cc Daniel Rosenberg <[email protected]>Cc Gabriel Krisman Bertazi <[email protected]>Signed-off-by Eric Biggers <[email protected]>
Quoted 6 lines
---
v2: use
memcpy()+barrier()instead of a byte-by-byte copy.fs/ext4/dir.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
Ted, could you take this through the ext4 tree as a fix for 5.8? The f2fs patch has been merged already.
- Eric