Skip to contentSkip to conversation

ext4: avoid utf8_strncasecmp() with unstable name

PATCH Applied Eric Biggers 3 participants 4 messages Jun 1, 2020 – Jun 11, 2020

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

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.

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]>

---

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
index c654205f648dd..1d82336b1cd45 100644
@@ -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
Reviewed-by on v2 1/1
#1 lore raw
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) 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.

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]>

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
index c654205f648dd..1d82336b1cd45 100644
@@ -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

maintainer Applied
#2 lore raw
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) 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.

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]>
Quoted 3 lines

LGTM.

Thanks, applied.

- Ted

#3 lore raw
In reply to Eric Biggers
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) 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.

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]>
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