[PATCH] jfs: fix out-of-bounds write in jfs_readdir()
From: Bhargav Joshi
Date: Tue Oct 06 2026 - 17:19:53 EST
jfs_readdir() converts on-disk UTF-16 directory names into a
PAGE_SIZE buffer. The existing space check assumes that each UTF-16
unit produces one output byte:
if (((long) jfs_dirent + d->namlen + 1) >
((long)dirent_buf + PAGE_SIZE))
This is insufficient for multibyte NLS encodings, where a UTF-16 unit
can expand to multiple output bytes. jfs_strfromUCS_le() also passes
NLS_MAX_CHARSET_SIZE to uni2char() without accounting for the space
actually remaining in the destination buffer, allowing the conversion
to write past dirent_buf.
Pass remaining buffer size to jfs_strfromUCS_le, Ensure that at least
NLS_MAX_CHARSET_SIZE bytes remain before calling uni2char(). If the
remaining space is insufficient, return -ENAMETOOLONG so jfs_readdir()
can retry the entry in a fresh buffer.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: syzbot+a2748ba908c108e7e525@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=a2748ba908c108e7e525
Assisted-by: ChatGPT:LLM
Signed-off-by: Bhargav Joshi <j.bhargav.u@xxxxxxxxx>
---
fs/jfs/jfs_dtree.c | 24 ++++++++++++++++++++----
fs/jfs/jfs_unicode.c | 14 ++++++++++++--
fs/jfs/jfs_unicode.h | 2 +-
3 files changed, 33 insertions(+), 7 deletions(-)
diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c
index 8ce6e4458cc25..7b80c34e841db 100644
--- a/fs/jfs/jfs_dtree.c
+++ b/fs/jfs/jfs_dtree.c
@@ -2961,8 +2961,15 @@ int jfs_readdir(struct file *file, struct
dir_context *ctx)
}
/* copy the name of head/only segment */
- outlen = jfs_strfromUCS_le(name_ptr, d->name, len,
- codepage);
+ outlen = jfs_strfromUCS_le(name_ptr,
+ (char *)dirent_buf +
+ PAGE_SIZE - name_ptr,
+ d->name, len, codepage);
+ if (outlen < 0) {
+ index = i;
+ overflow = 1;
+ break;
+ }
jfs_dirent->name_len = outlen;
/* copy name in the additional segment(s) */
@@ -2981,12 +2988,21 @@ int jfs_readdir(struct file *file, struct
dir_context *ctx)
goto skip_one;
}
len = min(d_namleft, DTSLOTDATALEN);
- outlen = jfs_strfromUCS_le(name_ptr, t->name,
- len, codepage);
+ outlen = jfs_strfromUCS_le(name_ptr,
+ (char *)dirent_buf +
+ PAGE_SIZE - name_ptr,
+ t->name, len, codepage);
+ if (outlen < 0) {
+ index = i;
+ overflow = 1;
+ break;
+ }
jfs_dirent->name_len += outlen;
next = t->next;
}
+ if (overflow == 1)
+ break;
jfs_dirents++;
jfs_dirent = next_jfs_dirent(jfs_dirent);
diff --git a/fs/jfs/jfs_unicode.c b/fs/jfs/jfs_unicode.c
index 0c1e9027245a6..f73c718c5dd03 100644
--- a/fs/jfs/jfs_unicode.c
+++ b/fs/jfs/jfs_unicode.c
@@ -16,17 +16,25 @@
* FUNCTION: Convert little-endian unicode string to character string
*
*/
-int jfs_strfromUCS_le(char *to, const __le16 * from,
+int jfs_strfromUCS_le(char *to, int to_size, const __le16 *from,
int len, struct nls_table *codepage)
{
- int i;
+ int i, remaining_size;
int outlen = 0;
static int warn_again = 5; /* Only warn up to 5 times total */
int warn = !!warn_again; /* once per string */
+ if (to_size <= 0)
+ return -ENAMETOOLONG;
+
if (codepage) {
for (i = 0; (i < len) && from[i]; i++) {
int charlen;
+
+ remaining_size = to_size - outlen - 1;
+ if (remaining_size < NLS_MAX_CHARSET_SIZE)
+ return -ENAMETOOLONG;
+
charlen =
codepage->uni2char(le16_to_cpu(from[i]),
&to[outlen],
@@ -38,6 +46,8 @@ int jfs_strfromUCS_le(char *to, const __le16 * from,
}
} else {
for (i = 0; (i < len) && from[i]; i++) {
+ if (i >= to_size - 1)
+ return -ENAMETOOLONG;
if (unlikely(le16_to_cpu(from[i]) & 0xff00)) {
to[i] = '?';
if (unlikely(warn)) {
diff --git a/fs/jfs/jfs_unicode.h b/fs/jfs/jfs_unicode.h
index b6a78d4aef1b0..ea03dd5a0e0cb 100644
--- a/fs/jfs/jfs_unicode.h
+++ b/fs/jfs/jfs_unicode.h
@@ -12,7 +12,7 @@
#include "jfs_types.h"
extern int get_UCSname(struct component_name *, struct dentry *);
-extern int jfs_strfromUCS_le(char *, const __le16 *, int, struct nls_table *);
+extern int jfs_strfromUCS_le(char *, int, const __le16 *, int, struct
nls_table *);
#define free_UCSname(COMP) kfree((COMP)->name)
--
2.56.0