mirror of
https://github.com/torvalds/linux.git
synced 2025-11-30 23:16:01 +07:00
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>
566 lines
12 KiB
C
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;
|
|
}
|