Files
linux/fs/hfsplus/unicode.c
Viacheslav Dubeyko 42520df65b hfsplus: fix slab-out-of-bounds read in hfsplus_strcasecmp()
The hfsplus_strcasecmp() logic can trigger the issue:

[  117.317703][ T9855] ==================================================================
[  117.318353][ T9855] BUG: KASAN: slab-out-of-bounds in hfsplus_strcasecmp+0x1bc/0x490
[  117.318991][ T9855] Read of size 2 at addr ffff88802160f40c by task repro/9855
[  117.319577][ T9855]
[  117.319773][ T9855] CPU: 0 UID: 0 PID: 9855 Comm: repro Not tainted 6.17.0-rc6 #33 PREEMPT(full)
[  117.319780][ T9855] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[  117.319783][ T9855] Call Trace:
[  117.319785][ T9855]  <TASK>
[  117.319788][ T9855]  dump_stack_lvl+0x1c1/0x2a0
[  117.319795][ T9855]  ? __virt_addr_valid+0x1c8/0x5c0
[  117.319803][ T9855]  ? __pfx_dump_stack_lvl+0x10/0x10
[  117.319808][ T9855]  ? rcu_is_watching+0x15/0xb0
[  117.319816][ T9855]  ? lock_release+0x4b/0x3e0
[  117.319821][ T9855]  ? __kasan_check_byte+0x12/0x40
[  117.319828][ T9855]  ? __virt_addr_valid+0x1c8/0x5c0
[  117.319835][ T9855]  ? __virt_addr_valid+0x4a5/0x5c0
[  117.319842][ T9855]  print_report+0x17e/0x7e0
[  117.319848][ T9855]  ? __virt_addr_valid+0x1c8/0x5c0
[  117.319855][ T9855]  ? __virt_addr_valid+0x4a5/0x5c0
[  117.319862][ T9855]  ? __phys_addr+0xd3/0x180
[  117.319869][ T9855]  ? hfsplus_strcasecmp+0x1bc/0x490
[  117.319876][ T9855]  kasan_report+0x147/0x180
[  117.319882][ T9855]  ? hfsplus_strcasecmp+0x1bc/0x490
[  117.319891][ T9855]  hfsplus_strcasecmp+0x1bc/0x490
[  117.319900][ T9855]  ? __pfx_hfsplus_cat_case_cmp_key+0x10/0x10
[  117.319906][ T9855]  hfs_find_rec_by_key+0xa9/0x1e0
[  117.319913][ T9855]  __hfsplus_brec_find+0x18e/0x470
[  117.319920][ T9855]  ? __pfx_hfsplus_bnode_find+0x10/0x10
[  117.319926][ T9855]  ? __pfx_hfs_find_rec_by_key+0x10/0x10
[  117.319933][ T9855]  ? __pfx___hfsplus_brec_find+0x10/0x10
[  117.319942][ T9855]  hfsplus_brec_find+0x28f/0x510
[  117.319949][ T9855]  ? __pfx_hfs_find_rec_by_key+0x10/0x10
[  117.319956][ T9855]  ? __pfx_hfsplus_brec_find+0x10/0x10
[  117.319963][ T9855]  ? __kmalloc_noprof+0x2a9/0x510
[  117.319969][ T9855]  ? hfsplus_find_init+0x8c/0x1d0
[  117.319976][ T9855]  hfsplus_brec_read+0x2b/0x120
[  117.319983][ T9855]  hfsplus_lookup+0x2aa/0x890
[  117.319990][ T9855]  ? __pfx_hfsplus_lookup+0x10/0x10
[  117.320003][ T9855]  ? d_alloc_parallel+0x2f0/0x15e0
[  117.320008][ T9855]  ? __lock_acquire+0xaec/0xd80
[  117.320013][ T9855]  ? __pfx_d_alloc_parallel+0x10/0x10
[  117.320019][ T9855]  ? __raw_spin_lock_init+0x45/0x100
[  117.320026][ T9855]  ? __init_waitqueue_head+0xa9/0x150
[  117.320034][ T9855]  __lookup_slow+0x297/0x3d0
[  117.320039][ T9855]  ? __pfx___lookup_slow+0x10/0x10
[  117.320045][ T9855]  ? down_read+0x1ad/0x2e0
[  117.320055][ T9855]  lookup_slow+0x53/0x70
[  117.320065][ T9855]  walk_component+0x2f0/0x430
[  117.320073][ T9855]  path_lookupat+0x169/0x440
[  117.320081][ T9855]  filename_lookup+0x212/0x590
[  117.320089][ T9855]  ? __pfx_filename_lookup+0x10/0x10
[  117.320098][ T9855]  ? strncpy_from_user+0x150/0x290
[  117.320105][ T9855]  ? getname_flags+0x1e5/0x540
[  117.320112][ T9855]  user_path_at+0x3a/0x60
[  117.320117][ T9855]  __x64_sys_umount+0xee/0x160
[  117.320123][ T9855]  ? __pfx___x64_sys_umount+0x10/0x10
[  117.320129][ T9855]  ? do_syscall_64+0xb7/0x3a0
[  117.320135][ T9855]  ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  117.320141][ T9855]  ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  117.320145][ T9855]  do_syscall_64+0xf3/0x3a0
[  117.320150][ T9855]  ? exc_page_fault+0x9f/0xf0
[  117.320154][ T9855]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  117.320158][ T9855] RIP: 0033:0x7f7dd7908b07
[  117.320163][ T9855] Code: 23 0d 00 f7 d8 64 89 01 48 83 c8 ff c3 66 0f 1f 44 00 00 31 f6 e9 09 00 00 00 66 0f 1f 84 00 00 08
[  117.320167][ T9855] RSP: 002b:00007ffd5ebd9698 EFLAGS: 00000202 ORIG_RAX: 00000000000000a6
[  117.320172][ T9855] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f7dd7908b07
[  117.320176][ T9855] RDX: 0000000000000009 RSI: 0000000000000009 RDI: 00007ffd5ebd9740
[  117.320179][ T9855] RBP: 00007ffd5ebda780 R08: 0000000000000005 R09: 00007ffd5ebd9530
[  117.320181][ T9855] R10: 00007f7dd799bfc0 R11: 0000000000000202 R12: 000055e2008b32d0
[  117.320184][ T9855] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
[  117.320189][ T9855]  </TASK>
[  117.320190][ T9855]
[  117.351311][ T9855] Allocated by task 9855:
[  117.351683][ T9855]  kasan_save_track+0x3e/0x80
[  117.352093][ T9855]  __kasan_kmalloc+0x8d/0xa0
[  117.352490][ T9855]  __kmalloc_noprof+0x288/0x510
[  117.352914][ T9855]  hfsplus_find_init+0x8c/0x1d0
[  117.353342][ T9855]  hfsplus_lookup+0x19c/0x890
[  117.353747][ T9855]  __lookup_slow+0x297/0x3d0
[  117.354148][ T9855]  lookup_slow+0x53/0x70
[  117.354514][ T9855]  walk_component+0x2f0/0x430
[  117.354921][ T9855]  path_lookupat+0x169/0x440
[  117.355325][ T9855]  filename_lookup+0x212/0x590
[  117.355740][ T9855]  user_path_at+0x3a/0x60
[  117.356115][ T9855]  __x64_sys_umount+0xee/0x160
[  117.356529][ T9855]  do_syscall_64+0xf3/0x3a0
[  117.356920][ T9855]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  117.357429][ T9855]
[  117.357636][ T9855] The buggy address belongs to the object at ffff88802160f000
[  117.357636][ T9855]  which belongs to the cache kmalloc-2k of size 2048
[  117.358827][ T9855] The buggy address is located 0 bytes to the right of
[  117.358827][ T9855]  allocated 1036-byte region [ffff88802160f000, ffff88802160f40c)
[  117.360061][ T9855]
[  117.360266][ T9855] The buggy address belongs to the physical page:
[  117.360813][ T9855] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x21608
[  117.361562][ T9855] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[  117.362285][ T9855] flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
[  117.362929][ T9855] page_type: f5(slab)
[  117.363282][ T9855] raw: 00fff00000000040 ffff88801a842f00 ffffea0000932000 dead000000000002
[  117.364015][ T9855] raw: 0000000000000000 0000000080080008 00000000f5000000 0000000000000000
[  117.364750][ T9855] head: 00fff00000000040 ffff88801a842f00 ffffea0000932000 dead000000000002
[  117.365491][ T9855] head: 0000000000000000 0000000080080008 00000000f5000000 0000000000000000
[  117.366232][ T9855] head: 00fff00000000003 ffffea0000858201 00000000ffffffff 00000000ffffffff
[  117.366968][ T9855] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
[  117.367711][ T9855] page dumped because: kasan: bad access detected
[  117.368259][ T9855] page_owner tracks the page as allocated
[  117.368745][ T9855] page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN1
[  117.370541][ T9855]  post_alloc_hook+0x240/0x2a0
[  117.370954][ T9855]  get_page_from_freelist+0x2101/0x21e0
[  117.371435][ T9855]  __alloc_frozen_pages_noprof+0x274/0x380
[  117.371935][ T9855]  alloc_pages_mpol+0x241/0x4b0
[  117.372360][ T9855]  allocate_slab+0x8d/0x380
[  117.372752][ T9855]  ___slab_alloc+0xbe3/0x1400
[  117.373159][ T9855]  __kmalloc_cache_noprof+0x296/0x3d0
[  117.373621][ T9855]  nexthop_net_init+0x75/0x100
[  117.374038][ T9855]  ops_init+0x35c/0x5c0
[  117.374400][ T9855]  setup_net+0x10c/0x320
[  117.374768][ T9855]  copy_net_ns+0x31b/0x4d0
[  117.375156][ T9855]  create_new_namespaces+0x3f3/0x720
[  117.375613][ T9855]  unshare_nsproxy_namespaces+0x11c/0x170
[  117.376094][ T9855]  ksys_unshare+0x4ca/0x8d0
[  117.376477][ T9855]  __x64_sys_unshare+0x38/0x50
[  117.376879][ T9855]  do_syscall_64+0xf3/0x3a0
[  117.377265][ T9855] page last free pid 9110 tgid 9110 stack trace:
[  117.377795][ T9855]  __free_frozen_pages+0xbeb/0xd50
[  117.378229][ T9855]  __put_partials+0x152/0x1a0
[  117.378625][ T9855]  put_cpu_partial+0x17c/0x250
[  117.379026][ T9855]  __slab_free+0x2d4/0x3c0
[  117.379404][ T9855]  qlist_free_all+0x97/0x140
[  117.379790][ T9855]  kasan_quarantine_reduce+0x148/0x160
[  117.380250][ T9855]  __kasan_slab_alloc+0x22/0x80
[  117.380662][ T9855]  __kmalloc_noprof+0x232/0x510
[  117.381074][ T9855]  tomoyo_supervisor+0xc0a/0x1360
[  117.381498][ T9855]  tomoyo_env_perm+0x149/0x1e0
[  117.381903][ T9855]  tomoyo_find_next_domain+0x15ad/0x1b90
[  117.382378][ T9855]  tomoyo_bprm_check_security+0x11c/0x180
[  117.382859][ T9855]  security_bprm_check+0x89/0x280
[  117.383289][ T9855]  bprm_execve+0x8f1/0x14a0
[  117.383673][ T9855]  do_execveat_common+0x528/0x6b0
[  117.384103][ T9855]  __x64_sys_execve+0x94/0xb0
[  117.384500][ T9855]
[  117.384706][ T9855] Memory state around the buggy address:
[  117.385179][ T9855]  ffff88802160f300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[  117.385854][ T9855]  ffff88802160f380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[  117.386534][ T9855] >ffff88802160f400: 00 04 fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  117.387204][ T9855]                       ^
[  117.387566][ T9855]  ffff88802160f480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  117.388243][ T9855]  ffff88802160f500: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  117.388918][ T9855] ==================================================================

The issue takes place if the length field of struct hfsplus_unistr
is bigger than HFSPLUS_MAX_STRLEN. The patch simply checks
the length of comparing strings. And if the strings' length
is bigger than HFSPLUS_MAX_STRLEN, then it is corrected
to this value.

v2
The string length correction has been added for hfsplus_strcmp().

Reported-by: Jiaming Zhang <r772577952@gmail.com>
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
cc: syzkaller@googlegroups.com
Link: https://lore.kernel.org/r/20250919191243.1370388-1-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
2025-09-22 15:11:33 -07:00

566 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/hfsplus/unicode.c
*
* Copyright (C) 2001
* Brad Boyer (flar@allandria.com)
* (C) 2003 Ardis Technologies <roman@ardistech.com>
*
* Handler routines for unicode strings
*/
#include <linux/types.h>
#include <linux/nls.h>
#include "hfsplus_fs.h"
#include "hfsplus_raw.h"
/* Fold the case of a unicode char, given the 16 bit value */
/* Returns folded char, or 0 if ignorable */
static inline u16 case_fold(u16 c)
{
u16 tmp;
tmp = hfsplus_case_fold_table[c >> 8];
if (tmp)
tmp = hfsplus_case_fold_table[tmp + (c & 0xff)];
else
tmp = c;
return tmp;
}
/* Compare unicode strings, return values like normal strcmp */
int hfsplus_strcasecmp(const struct hfsplus_unistr *s1,
const struct hfsplus_unistr *s2)
{
u16 len1, len2, c1, c2;
const hfsplus_unichr *p1, *p2;
len1 = be16_to_cpu(s1->length);
len2 = be16_to_cpu(s2->length);
p1 = s1->unicode;
p2 = s2->unicode;
if (len1 > HFSPLUS_MAX_STRLEN) {
len1 = HFSPLUS_MAX_STRLEN;
pr_err("invalid length %u has been corrected to %d\n",
be16_to_cpu(s1->length), len1);
}
if (len2 > HFSPLUS_MAX_STRLEN) {
len2 = HFSPLUS_MAX_STRLEN;
pr_err("invalid length %u has been corrected to %d\n",
be16_to_cpu(s2->length), len2);
}
while (1) {
c1 = c2 = 0;
while (len1 && !c1) {
c1 = case_fold(be16_to_cpu(*p1));
p1++;
len1--;
}
while (len2 && !c2) {
c2 = case_fold(be16_to_cpu(*p2));
p2++;
len2--;
}
if (c1 != c2)
return (c1 < c2) ? -1 : 1;
if (!c1 && !c2)
return 0;
}
}
/* Compare names as a sequence of 16-bit unsigned integers */
int hfsplus_strcmp(const struct hfsplus_unistr *s1,
const struct hfsplus_unistr *s2)
{
u16 len1, len2, c1, c2;
const hfsplus_unichr *p1, *p2;
int len;
len1 = be16_to_cpu(s1->length);
len2 = be16_to_cpu(s2->length);
p1 = s1->unicode;
p2 = s2->unicode;
if (len1 > HFSPLUS_MAX_STRLEN) {
len1 = HFSPLUS_MAX_STRLEN;
pr_err("invalid length %u has been corrected to %d\n",
be16_to_cpu(s1->length), len1);
}
if (len2 > HFSPLUS_MAX_STRLEN) {
len2 = HFSPLUS_MAX_STRLEN;
pr_err("invalid length %u has been corrected to %d\n",
be16_to_cpu(s2->length), len2);
}
for (len = min(len1, len2); len > 0; len--) {
c1 = be16_to_cpu(*p1);
c2 = be16_to_cpu(*p2);
if (c1 != c2)
return c1 < c2 ? -1 : 1;
p1++;
p2++;
}
return len1 < len2 ? -1 :
len1 > len2 ? 1 : 0;
}
#define Hangul_SBase 0xac00
#define Hangul_LBase 0x1100
#define Hangul_VBase 0x1161
#define Hangul_TBase 0x11a7
#define Hangul_SCount 11172
#define Hangul_LCount 19
#define Hangul_VCount 21
#define Hangul_TCount 28
#define Hangul_NCount (Hangul_VCount * Hangul_TCount)
static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)
{
int i, s, e;
s = 1;
e = p[1];
if (!e || cc < p[s * 2] || cc > p[e * 2])
return NULL;
do {
i = (s + e) / 2;
if (cc > p[i * 2])
s = i + 1;
else if (cc < p[i * 2])
e = i - 1;
else
return hfsplus_compose_table + p[i * 2 + 1];
} while (s <= e);
return NULL;
}
static int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr,
int max_len, char *astr, int *len_p)
{
const hfsplus_unichr *ip;
struct nls_table *nls = HFSPLUS_SB(sb)->nls;
u8 *op;
u16 cc, c0, c1;
u16 *ce1, *ce2;
int i, len, ustrlen, res, compose;
op = astr;
ip = ustr->unicode;
ustrlen = be16_to_cpu(ustr->length);
if (ustrlen > max_len) {
ustrlen = max_len;
pr_err("invalid length %u has been corrected to %d\n",
be16_to_cpu(ustr->length), ustrlen);
}
len = *len_p;
ce1 = NULL;
compose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
while (ustrlen > 0) {
c0 = be16_to_cpu(*ip++);
ustrlen--;
/* search for single decomposed char */
if (likely(compose))
ce1 = hfsplus_compose_lookup(hfsplus_compose_table, c0);
if (ce1)
cc = ce1[0];
else
cc = 0;
if (cc) {
/* start of a possibly decomposed Hangul char */
if (cc != 0xffff)
goto done;
if (!ustrlen)
goto same;
c1 = be16_to_cpu(*ip) - Hangul_VBase;
if (c1 < Hangul_VCount) {
/* compose the Hangul char */
cc = (c0 - Hangul_LBase) * Hangul_VCount;
cc = (cc + c1) * Hangul_TCount;
cc += Hangul_SBase;
ip++;
ustrlen--;
if (!ustrlen)
goto done;
c1 = be16_to_cpu(*ip) - Hangul_TBase;
if (c1 > 0 && c1 < Hangul_TCount) {
cc += c1;
ip++;
ustrlen--;
}
goto done;
}
}
while (1) {
/* main loop for common case of not composed chars */
if (!ustrlen)
goto same;
c1 = be16_to_cpu(*ip);
if (likely(compose))
ce1 = hfsplus_compose_lookup(
hfsplus_compose_table, c1);
if (ce1)
break;
switch (c0) {
case 0:
c0 = 0x2400;
break;
case '/':
c0 = ':';
break;
}
res = nls->uni2char(c0, op, len);
if (res < 0) {
if (res == -ENAMETOOLONG)
goto out;
*op = '?';
res = 1;
}
op += res;
len -= res;
c0 = c1;
ip++;
ustrlen--;
}
ce2 = hfsplus_compose_lookup(ce1, c0);
if (ce2) {
i = 1;
while (i < ustrlen) {
ce1 = hfsplus_compose_lookup(ce2,
be16_to_cpu(ip[i]));
if (!ce1)
break;
i++;
ce2 = ce1;
}
cc = ce2[0];
if (cc) {
ip += i;
ustrlen -= i;
goto done;
}
}
same:
switch (c0) {
case 0:
cc = 0x2400;
break;
case '/':
cc = ':';
break;
default:
cc = c0;
}
done:
res = nls->uni2char(cc, op, len);
if (res < 0) {
if (res == -ENAMETOOLONG)
goto out;
*op = '?';
res = 1;
}
op += res;
len -= res;
}
res = 0;
out:
*len_p = (char *)op - astr;
return res;
}
inline int hfsplus_uni2asc_str(struct super_block *sb,
const struct hfsplus_unistr *ustr, char *astr,
int *len_p)
{
return hfsplus_uni2asc(sb, ustr, HFSPLUS_MAX_STRLEN, astr, len_p);
}
inline int hfsplus_uni2asc_xattr_str(struct super_block *sb,
const struct hfsplus_attr_unistr *ustr,
char *astr, int *len_p)
{
return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr,
HFSPLUS_ATTR_MAX_STRLEN, astr, len_p);
}
/*
* Convert one or more ASCII characters into a single unicode character.
* Returns the number of ASCII characters corresponding to the unicode char.
*/
static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
wchar_t *uc)
{
int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
if (size <= 0) {
*uc = '?';
size = 1;
}
switch (*uc) {
case 0x2400:
*uc = 0;
break;
case ':':
*uc = '/';
break;
}
return size;
}
/* Decomposes a non-Hangul unicode character. */
static u16 *hfsplus_decompose_nonhangul(wchar_t uc, int *size)
{
int off;
off = hfsplus_decompose_table[(uc >> 12) & 0xf];
if (off == 0 || off == 0xffff)
return NULL;
off = hfsplus_decompose_table[off + ((uc >> 8) & 0xf)];
if (!off)
return NULL;
off = hfsplus_decompose_table[off + ((uc >> 4) & 0xf)];
if (!off)
return NULL;
off = hfsplus_decompose_table[off + (uc & 0xf)];
*size = off & 3;
if (*size == 0)
return NULL;
return hfsplus_decompose_table + (off / 4);
}
/*
* Try to decompose a unicode character as Hangul. Return 0 if @uc is not
* precomposed Hangul, otherwise return the length of the decomposition.
*
* This function was adapted from sample code from the Unicode Standard
* Annex #15: Unicode Normalization Forms, version 3.2.0.
*
* Copyright (C) 1991-2018 Unicode, Inc. All rights reserved. Distributed
* under the Terms of Use in http://www.unicode.org/copyright.html.
*/
static int hfsplus_try_decompose_hangul(wchar_t uc, u16 *result)
{
int index;
int l, v, t;
index = uc - Hangul_SBase;
if (index < 0 || index >= Hangul_SCount)
return 0;
l = Hangul_LBase + index / Hangul_NCount;
v = Hangul_VBase + (index % Hangul_NCount) / Hangul_TCount;
t = Hangul_TBase + index % Hangul_TCount;
result[0] = l;
result[1] = v;
if (t != Hangul_TBase) {
result[2] = t;
return 3;
}
return 2;
}
/* Decomposes a single unicode character. */
static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer)
{
u16 *result;
/* Hangul is handled separately */
result = hangul_buffer;
*size = hfsplus_try_decompose_hangul(uc, result);
if (*size == 0)
result = hfsplus_decompose_nonhangul(uc, size);
return result;
}
int hfsplus_asc2uni(struct super_block *sb,
struct hfsplus_unistr *ustr, int max_unistr_len,
const char *astr, int len)
{
int size, dsize, decompose;
u16 *dstr, outlen = 0;
wchar_t c;
u16 dhangul[3];
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
while (outlen < max_unistr_len && len > 0) {
size = asc2unichar(sb, astr, len, &c);
if (decompose)
dstr = decompose_unichar(c, &dsize, dhangul);
else
dstr = NULL;
if (dstr) {
if (outlen + dsize > max_unistr_len)
break;
do {
ustr->unicode[outlen++] = cpu_to_be16(*dstr++);
} while (--dsize > 0);
} else
ustr->unicode[outlen++] = cpu_to_be16(c);
astr += size;
len -= size;
}
ustr->length = cpu_to_be16(outlen);
if (len > 0)
return -ENAMETOOLONG;
return 0;
}
/*
* Hash a string to an integer as appropriate for the HFS+ filesystem.
* Composed unicode characters are decomposed and case-folding is performed
* if the appropriate bits are (un)set on the superblock.
*/
int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
{
struct super_block *sb = dentry->d_sb;
const char *astr;
const u16 *dstr;
int casefold, decompose, size, len;
unsigned long hash;
wchar_t c;
u16 c2;
u16 dhangul[3];
casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
hash = init_name_hash(dentry);
astr = str->name;
len = str->len;
while (len > 0) {
int dsize;
size = asc2unichar(sb, astr, len, &c);
astr += size;
len -= size;
if (decompose)
dstr = decompose_unichar(c, &dsize, dhangul);
else
dstr = NULL;
if (dstr) {
do {
c2 = *dstr++;
if (casefold)
c2 = case_fold(c2);
if (!casefold || c2)
hash = partial_name_hash(c2, hash);
} while (--dsize > 0);
} else {
c2 = c;
if (casefold)
c2 = case_fold(c2);
if (!casefold || c2)
hash = partial_name_hash(c2, hash);
}
}
str->hash = end_name_hash(hash);
return 0;
}
/*
* Compare strings with HFS+ filename ordering.
* Composed unicode characters are decomposed and case-folding is performed
* if the appropriate bits are (un)set on the superblock.
*/
int hfsplus_compare_dentry(const struct dentry *dentry,
unsigned int len, const char *str, const struct qstr *name)
{
struct super_block *sb = dentry->d_sb;
int casefold, decompose, size;
int dsize1, dsize2, len1, len2;
const u16 *dstr1, *dstr2;
const char *astr1, *astr2;
u16 c1, c2;
wchar_t c;
u16 dhangul_1[3], dhangul_2[3];
casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
astr1 = str;
len1 = len;
astr2 = name->name;
len2 = name->len;
dsize1 = dsize2 = 0;
dstr1 = dstr2 = NULL;
while (len1 > 0 && len2 > 0) {
if (!dsize1) {
size = asc2unichar(sb, astr1, len1, &c);
astr1 += size;
len1 -= size;
if (decompose)
dstr1 = decompose_unichar(c, &dsize1,
dhangul_1);
if (!decompose || !dstr1) {
c1 = c;
dstr1 = &c1;
dsize1 = 1;
}
}
if (!dsize2) {
size = asc2unichar(sb, astr2, len2, &c);
astr2 += size;
len2 -= size;
if (decompose)
dstr2 = decompose_unichar(c, &dsize2,
dhangul_2);
if (!decompose || !dstr2) {
c2 = c;
dstr2 = &c2;
dsize2 = 1;
}
}
c1 = *dstr1;
c2 = *dstr2;
if (casefold) {
c1 = case_fold(c1);
if (!c1) {
dstr1++;
dsize1--;
continue;
}
c2 = case_fold(c2);
if (!c2) {
dstr2++;
dsize2--;
continue;
}
}
if (c1 < c2)
return -1;
else if (c1 > c2)
return 1;
dstr1++;
dsize1--;
dstr2++;
dsize2--;
}
if (len1 < len2)
return -1;
if (len1 > len2)
return 1;
return 0;
}