* Forwarded: [PATCH] jfs: fix slab-out-of-bounds write in jfs_readdir() with multibyte names
2026-10-05 15:12 [syzbot] KASAN: slab-out-of-bounds Write in utf32_to_utf8 syzbot
@ 2026-10-06 5:53 ` syzbot
2026-10-07 15:42 ` Forwarded: KASAN: slab-out-of-bounds Write in utf32_to_utf8 syzbot
2026-10-07 18:06 ` syzbot
2 siblings, 0 replies; 4+ messages in thread
From: syzbot @ 2026-10-06 5:53 UTC (permalink / raw)
To: linux-kernel, syzkaller-bugs
For archival purposes, forwarding an incoming command email to
linux-kernel@vger.kernel.org, syzkaller-bugs@googlegroups.com.
***
Subject: [PATCH] jfs: fix slab-out-of-bounds write in jfs_readdir() with multibyte names
Author: kartikey406@gmail.com
#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git master
syzbot reported a KASAN slab-out-of-bounds write in utf32_to_utf8(),
reached via jfs_strfromUCS_le() from jfs_readdir() while handling
getdents64() on a crafted JFS image.
jfs_readdir() converts on-disk UTF-16 directory entry names into
dirent_buf, a PAGE_SIZE buffer. Before converting an entry, it checks
that the name fits by assuming one output byte per UTF-16 unit:
jfs_dirent + d->namlen + 1 > dirent_buf + PAGE_SIZE
With iocharset=utf8 a single UTF-16 unit can expand to up to three
bytes, so a name can be approved for space it does not have.
jfs_strfromUCS_le() makes this worse: it has no destination size and
always passes NLS_MAX_CHARSET_SIZE as the output bound to uni2char(),
so the converter believes it has room regardless of how much of the
buffer is actually left. The conversion then writes past the end of
dirent_buf, and the trailing NUL can land one byte out of bounds too.
Fix this in two places:
- In jfs_readdir(), reserve the worst case of NLS_MAX_CHARSET_SIZE
bytes per UTF-16 unit when checking whether an entry fits.
- Give jfs_strfromUCS_le() a destination size (tolen) and pass the
real remaining space to uni2char() instead of the constant, keeping
one byte for the terminating NUL. Clamp the length in the
non-codepage path in the same way. Callers pass the distance to the
end of dirent_buf.
Reported-by: syzbot+a2748ba908c108e7e525@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=a2748ba908c108e7e525
Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
---
fs/jfs/jfs_dtree.c | 8 +++++---
fs/jfs/jfs_unicode.c | 15 ++++++++++++---
fs/jfs/jfs_unicode.h | 2 +-
3 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c
index 8ce6e4458cc2..5ccc90e3c068 100644
--- a/fs/jfs/jfs_dtree.c
+++ b/fs/jfs/jfs_dtree.c
@@ -2738,6 +2738,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx)
int jfs_dirents;
int overflow, fix_page, page_fixed = 0;
static int unique_pos = 2; /* If we can't fix broken index */
+ char *buf_end;
if (ctx->pos == DIREND)
return 0;
@@ -2892,6 +2893,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx)
return -ENOMEM;
}
+ buf_end = (char *)dirent_buf + PAGE_SIZE;
while (1) {
jfs_dirent = dirent_buf;
jfs_dirents = 0;
@@ -2910,7 +2912,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx)
d = (struct ldtentry *) & p->slot[stbl[i]];
- if (((long) jfs_dirent + d->namlen + 1) >
+ if (((long) jfs_dirent + (long)d->namlen * NLS_MAX_CHARSET_SIZE + 1) >
((long)dirent_buf + PAGE_SIZE)) {
/* DBCS codepages could overrun dirent_buf */
index = i;
@@ -2961,7 +2963,7 @@ 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,
+ outlen = jfs_strfromUCS_le(name_ptr, buf_end - name_ptr, d->name, len,
codepage);
jfs_dirent->name_len = outlen;
@@ -2981,7 +2983,7 @@ 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,
+ outlen = jfs_strfromUCS_le(name_ptr, buf_end - name_ptr , t->name,
len, codepage);
jfs_dirent->name_len += outlen;
diff --git a/fs/jfs/jfs_unicode.c b/fs/jfs/jfs_unicode.c
index 0c1e9027245a..bcff7e0031b2 100644
--- a/fs/jfs/jfs_unicode.c
+++ b/fs/jfs/jfs_unicode.c
@@ -16,27 +16,36 @@
* 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 tolen, const __le16 * from,
int len, struct nls_table *codepage)
{
- int i;
+ int i, room;
int outlen = 0;
static int warn_again = 5; /* Only warn up to 5 times total */
int warn = !!warn_again; /* once per string */
+ if(tolen <= 0)
+ return 0;
+
if (codepage) {
for (i = 0; (i < len) && from[i]; i++) {
int charlen;
+ room = tolen - outlen - 1;
+ if(room <= 0)
+ break;
charlen =
codepage->uni2char(le16_to_cpu(from[i]),
&to[outlen],
- NLS_MAX_CHARSET_SIZE);
+ room);
if (charlen > 0)
outlen += charlen;
else
to[outlen++] = '?';
}
} else {
+ if(len > tolen -1)
+ len = tolen - 1;
+
for (i = 0; (i < len) && from[i]; i++) {
if (unlikely(le16_to_cpu(from[i]) & 0xff00)) {
to[i] = '?';
diff --git a/fs/jfs/jfs_unicode.h b/fs/jfs/jfs_unicode.h
index b6a78d4aef1b..ea03dd5a0e0c 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.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* Forwarded: KASAN: slab-out-of-bounds Write in utf32_to_utf8
2026-10-05 15:12 [syzbot] KASAN: slab-out-of-bounds Write in utf32_to_utf8 syzbot
2026-10-06 5:53 ` Forwarded: [PATCH] jfs: fix slab-out-of-bounds write in jfs_readdir() with multibyte names syzbot
@ 2026-10-07 15:42 ` syzbot
2026-10-07 18:06 ` syzbot
2 siblings, 0 replies; 4+ messages in thread
From: syzbot @ 2026-10-07 15:42 UTC (permalink / raw)
To: linux-kernel, syzkaller-bugs
For archival purposes, forwarding an incoming command email to
linux-kernel@vger.kernel.org, syzkaller-bugs@googlegroups.com.
***
Subject: KASAN: slab-out-of-bounds Write in utf32_to_utf8
Author: j.bhargav.u@gmail.com
#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
master
From 17072f0e3fe2eb3e6e6323c60b52f3dee8c02e82 Mon Sep 17 00:00:00 2001
From: Bhargav Joshi <j.bhargav.u@gmail.com>
Date: Wed, 7 Oct 2026 02:49:53 +0530
Subject: [PATCH] jfs: fix out-of-bounds write in jfs_readdir()
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@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=a2748ba908c108e7e525
Assisted-by: ChatGPT:LLM
Signed-off-by: Bhargav Joshi <j.bhargav.u@gmail.com>
---
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
^ permalink raw reply [flat|nested] 4+ messages in thread