[PATCH v1] ntfs: fix lookup of POSIX names with Windows-invalid characters

From: Baolin Liu

Date: Sat Sep 12 2026 - 21:03:27 EST


From: Hyunchul Lee <hyc.lee@xxxxxxxxx>

POSIX FILE_NAME entries may contain characters such as '?' that are
invalid in the Windows namespace. Directory index lookup currently
passes an invalid-character error value to ntfs_collate_names(),
causing lookup and removal of such names to fail with -EINVAL.

Allow callers to disable invalid-character errors when collation is
used for index traversal, and suppress those errors when comparing
POSIX FILE_NAME attributes.

Signed-off-by: Hyunchul Lee <hyc.lee@xxxxxxxxx>
Tested-by: Baolin Liu <liubaolin@xxxxxxxxxx>
---
fs/ntfs/attrib.c | 8 ++++----
fs/ntfs/collate.c | 6 +++---
fs/ntfs/dir.c | 8 ++++----
fs/ntfs/ntfs.h | 4 ++--
fs/ntfs/unistr.c | 29 ++++++++++++++++++++---------
5 files changed, 33 insertions(+), 22 deletions(-)

diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index c7c09a751c6a..808b44671fff 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -909,7 +909,7 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,

rc = ntfs_collate_names(name, name_len,
(__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)),
- a->name_length, 1, IGNORE_CASE,
+ a->name_length, true, IGNORE_CASE,
upcase, upcase_len);
/*
* If @name collates before a->name, there is no
@@ -922,7 +922,7 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
continue;
rc = ntfs_collate_names(name, name_len,
(__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)),
- a->name_length, 1, CASE_SENSITIVE,
+ a->name_length, true, CASE_SENSITIVE,
upcase, upcase_len);
if (rc == -1)
return -ENOENT;
@@ -1313,7 +1313,7 @@ static int ntfs_external_attr_find(const __le32 type,
register int rc;

rc = ntfs_collate_names(name, name_len, al_name,
- al_name_len, 1, IGNORE_CASE,
+ al_name_len, true, IGNORE_CASE,
vol->upcase, vol->upcase_len);
/*
* If @name collates before al_name, there is no
@@ -1326,7 +1326,7 @@ static int ntfs_external_attr_find(const __le32 type,
continue;

rc = ntfs_collate_names(name, name_len, al_name,
- al_name_len, 1, CASE_SENSITIVE,
+ al_name_len, true, CASE_SENSITIVE,
vol->upcase, vol->upcase_len);
if (rc == -1)
goto not_found;
diff --git a/fs/ntfs/collate.c b/fs/ntfs/collate.c
index 744fdfd7bf58..5417288c5825 100644
--- a/fs/ntfs/collate.c
+++ b/fs/ntfs/collate.c
@@ -100,11 +100,11 @@ static int ntfs_collate_file_name(struct ntfs_volume *vol,
{
int rc;

- rc = ntfs_file_compare_values(data1, data2, -EINVAL,
- IGNORE_CASE, vol->upcase, vol->upcase_len);
+ rc = ntfs_file_compare_values(data1, data2,
+ true, IGNORE_CASE, vol->upcase, vol->upcase_len);
if (!rc)
rc = ntfs_file_compare_values(data1, data2,
- -EINVAL, CASE_SENSITIVE, vol->upcase, vol->upcase_len);
+ true, CASE_SENSITIVE, vol->upcase, vol->upcase_len);
return rc;
}

diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
index 2d594cbb4ebe..af16c7d6ea0b 100644
--- a/fs/ntfs/dir.c
+++ b/fs/ntfs/dir.c
@@ -238,7 +238,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
*/
rc = ntfs_collate_names(uname, uname_len,
(__le16 *)&ie->key.file_name.file_name,
- ie->key.file_name.file_name_length, 1,
+ ie->key.file_name.file_name_length, false,
IGNORE_CASE, vol->upcase, vol->upcase_len);
/*
* If uname collates before the name of the current entry, there
@@ -257,7 +257,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
*/
rc = ntfs_collate_names(uname, uname_len,
(__le16 *)&ie->key.file_name.file_name,
- ie->key.file_name.file_name_length, 1,
+ ie->key.file_name.file_name_length, false,
CASE_SENSITIVE, vol->upcase, vol->upcase_len);
if (rc == -1)
break;
@@ -474,7 +474,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
*/
rc = ntfs_collate_names(uname, uname_len,
(__le16 *)&ie->key.file_name.file_name,
- ie->key.file_name.file_name_length, 1,
+ ie->key.file_name.file_name_length, false,
IGNORE_CASE, vol->upcase, vol->upcase_len);
/*
* If uname collates before the name of the current entry, there
@@ -493,7 +493,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
*/
rc = ntfs_collate_names(uname, uname_len,
(__le16 *)&ie->key.file_name.file_name,
- ie->key.file_name.file_name_length, 1,
+ ie->key.file_name.file_name_length, false,
CASE_SENSITIVE, vol->upcase, vol->upcase_len);
if (rc == -1)
break;
diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h
index 45f77848a9cf..1bee1626abdf 100644
--- a/fs/ntfs/ntfs.h
+++ b/fs/ntfs/ntfs.h
@@ -234,14 +234,14 @@ bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len,
const __le16 *upcase, const u32 upcase_size);
int ntfs_collate_names(const __le16 *name1, const u32 name1_len,
const __le16 *name2, const u32 name2_len,
- const int err_val, const u32 ic,
+ const bool check_invalid, const u32 ic,
const __le16 *upcase, const u32 upcase_len);
int ntfs_ucsncmp(const __le16 *s1, const __le16 *s2, size_t n);
int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n,
const __le16 *upcase, const u32 upcase_size);
int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1,
const struct file_name_attr *file_name_attr2,
- const int err_val, const u32 ic,
+ const bool check_invalid, const u32 ic,
const __le16 *upcase, const u32 upcase_len);
int ntfs_nlstoucs(const struct ntfs_volume *vol, const char *ins,
const int ins_len, __le16 **outs, int max_name_len);
diff --git a/fs/ntfs/unistr.c b/fs/ntfs/unistr.c
index 7f11a2825527..733bd6fe8599 100644
--- a/fs/ntfs/unistr.c
+++ b/fs/ntfs/unistr.c
@@ -64,7 +64,8 @@ bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len,
* @name1_len: first Unicode name length
* @name2: second Unicode name to compare
* @name2_len: second Unicode name length
- * @err_val: if @name1 contains an invalid character return this value
+ * @check_invalid: if true and @name1 contains an invalid character,
+ * return -EINVAL
* @ic: either CASE_SENSITIVE or IGNORE_CASE
* @upcase: upcase table (ignored if @ic is CASE_SENSITIVE)
* @upcase_len: upcase table size (ignored if @ic is CASE_SENSITIVE)
@@ -74,13 +75,14 @@ bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len,
* -1 if the first name collates before the second one,
* 0 if the names match,
* 1 if the second name collates before the first one, or
- * @err_val if an invalid character is found in @name1 during the comparison.
+ * -EINVAL if @check_invalid is true and an invalid character is found in
+ * @name1 during the comparison.
*
* The following characters are considered invalid: '"', '*', '<', '>' and '?'.
*/
int ntfs_collate_names(const __le16 *name1, const u32 name1_len,
const __le16 *name2, const u32 name2_len,
- const int err_val, const u32 ic,
+ const bool check_invalid, const u32 ic,
const __le16 *upcase, const u32 upcase_len)
{
u32 cnt, min_len;
@@ -98,8 +100,8 @@ int ntfs_collate_names(const __le16 *name1, const u32 name1_len,
if (c2 < upcase_len)
c2 = le16_to_cpu(upcase[c2]);
}
- if (c1 < 64 && legal_ansi_char_array[c1] & 8)
- return err_val;
+ if (check_invalid && c1 < 64 && legal_ansi_char_array[c1] & 8)
+ return -EINVAL;
if (c1 < c2)
return -1;
if (c1 > c2)
@@ -111,8 +113,8 @@ int ntfs_collate_names(const __le16 *name1, const u32 name1_len,
return 0;
/* name1_len > name2_len */
c1 = le16_to_cpu(*name1);
- if (c1 < 64 && legal_ansi_char_array[c1] & 8)
- return err_val;
+ if (check_invalid && c1 < 64 && legal_ansi_char_array[c1] & 8)
+ return -EINVAL;
return 1;
}

@@ -191,14 +193,23 @@ int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n,

int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1,
const struct file_name_attr *file_name_attr2,
- const int err_val, const u32 ic,
+ const bool check_invalid, const u32 ic,
const __le16 *upcase, const u32 upcase_len)
{
+ bool compare_check = check_invalid;
+
+ /*
+ * POSIX file names may contain characters that are invalid in the
+ * Windows namespace, so compare them without treating them as errors.
+ */
+ if (file_name_attr1->file_name_type == FILE_NAME_POSIX)
+ compare_check = false;
+
return ntfs_collate_names((__le16 *)&file_name_attr1->file_name,
file_name_attr1->file_name_length,
(__le16 *)&file_name_attr2->file_name,
file_name_attr2->file_name_length,
- err_val, ic, upcase, upcase_len);
+ compare_check, ic, upcase, upcase_len);
}

/*
--
2.51.0