[PATCH] jfs: bound Unicode name conversion to the dirent buffer

From: Pengpeng Hou

Date: Mon Mar 23 2026 - 03:26:56 EST


jfs_readdir() only checks that d->namlen characters fit in the page,
but jfs_strfromUCS_le() can expand each UCS-2 character into multiple
output bytes when a codepage is active and then always appends a NUL.
That lets a long multi-byte name run past the end of the temporary
dirent page.

Make jfs_strfromUCS_le() size-aware and stop the readdir walk when the
converted name no longer fits in the remaining dirent buffer space.

Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
fs/jfs/jfs_dtree.c | 23 +++++++++++++++++++----
fs/jfs/jfs_unicode.c | 21 ++++++++++++++++-----
fs/jfs/jfs_unicode.h | 3 ++-
3 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c
index 9ab3f2fc61d1..62c1ff7a2a38 100644
--- a/fs/jfs/jfs_dtree.c
+++ b/fs/jfs/jfs_dtree.c
@@ -2964,8 +2964,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,
+ dirent_buf + PAGE_SIZE -
+ (unsigned long)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) */
@@ -2984,8 +2991,16 @@ 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,
+ dirent_buf + PAGE_SIZE -
+ (unsigned long)name_ptr,
+ t->name, len,
+ codepage);
+ if (outlen < 0) {
+ index = i;
+ overflow = 1;
+ break;
+ }
jfs_dirent->name_len += outlen;

next = t->next;
diff --git a/fs/jfs/jfs_unicode.c b/fs/jfs/jfs_unicode.c
index 0c1e9027245a..3ac6fd88a7eb 100644
--- a/fs/jfs/jfs_unicode.c
+++ b/fs/jfs/jfs_unicode.c
@@ -16,7 +16,7 @@
* FUNCTION: Convert little-endian unicode string to character string
*
*/
-int jfs_strfromUCS_le(char *to, const __le16 * from,
+int jfs_strfromUCS_le(char *to, size_t to_size, const __le16 *from,
int len, struct nls_table *codepage)
{
int i;
@@ -24,13 +24,22 @@ int jfs_strfromUCS_le(char *to, const __le16 * from,
static int warn_again = 5; /* Only warn up to 5 times total */
int warn = !!warn_again; /* once per string */

+ if (!to_size)
+ return -ENAMETOOLONG;
+
if (codepage) {
for (i = 0; (i < len) && from[i]; i++) {
int charlen;
+
+ if (outlen >= to_size - 1)
+ return -ENAMETOOLONG;
+
charlen =
codepage->uni2char(le16_to_cpu(from[i]),
&to[outlen],
- NLS_MAX_CHARSET_SIZE);
+ min_t(size_t,
+ NLS_MAX_CHARSET_SIZE,
+ to_size - outlen - 1));
if (charlen > 0)
outlen += charlen;
else
@@ -38,8 +47,11 @@ int jfs_strfromUCS_le(char *to, const __le16 * from,
}
} else {
for (i = 0; (i < len) && from[i]; i++) {
+ if (outlen >= to_size - 1)
+ return -ENAMETOOLONG;
+
if (unlikely(le16_to_cpu(from[i]) & 0xff00)) {
- to[i] = '?';
+ to[outlen++] = '?';
if (unlikely(warn)) {
warn--;
warn_again--;
@@ -52,9 +64,8 @@ int jfs_strfromUCS_le(char *to, const __le16 * from,

}
else
- to[i] = (char) (le16_to_cpu(from[i]));
+ to[outlen++] = (char)(le16_to_cpu(from[i]));
}
- outlen = i;
}
to[outlen] = 0;
return outlen;
diff --git a/fs/jfs/jfs_unicode.h b/fs/jfs/jfs_unicode.h
index b6a78d4aef1b..39b5710891ef 100644
--- a/fs/jfs/jfs_unicode.h
+++ b/fs/jfs/jfs_unicode.h
@@ -12,7 +12,8 @@
#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 *to, size_t to_size, const __le16 *from,
+ int len, struct nls_table *codepage);

#define free_UCSname(COMP) kfree((COMP)->name)

--
2.50.1 (Apple Git-155)