Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hfsplus: fix worst-case unicode to char conversion of file names and attributes

This is a series of 3 patches which corrects issues in HFS+ concerning
the use of non-english file names and attributes. Names and attributes
are stored internally as UTF-16 units up to a fixed maximum size, and
convert to and from user-representation by NLS. The code incorrectly
assume that NLS string lengths are equal to unicode lengths, which is
only true for English ascii usage.

This patch (of 3):

The HFS Plus Volume Format specification (TN1150) states that file names
are stored internally as a maximum of 255 unicode characters, as defined
by The Unicode Standard, Version 2.0 [Unicode, Inc. ISBN
0-201-48345-9]. File names are converted by the NLS system on Linux
before presented to the user.

255 CJK characters converts to UTF-8 with 1 unicode character to up to 3
bytes, and to GB18030 with 1 unicode character to up to 4 bytes. Thus,
trying in a UTF-8 locale to list files with names of more than 85 CJK
characters results in:

$ ls /mnt
ls: reading directory /mnt: File name too long

The receiving buffer to hfsplus_uni2asc() needs to be 255 x
NLS_MAX_CHARSET_SIZE bytes, not 255 bytes as the code has always been.

Similar consideration applies to attributes, which are stored internally
as a maximum of 127 UTF-16BE units. See XNU source for an up-to-date
reference on attributes.

Strictly speaking, the maximum value of NLS_MAX_CHARSET_SIZE = 6 is not
attainable in the case of conversion to UTF-8, as going beyond 3 bytes
requires the use of surrogate pairs, i.e. consuming two input units.

Thanks Anton Altaparmakov for reviewing an earlier version of this
change.

This patch fixes all callers of hfsplus_uni2asc(), and also enables the
use of long non-English file names in HFS+. The getting and setting,
and general usage of long non-English attributes requires further
forthcoming work, in the following patches of this series.

[akpm@linux-foundation.org: fix build]
Signed-off-by: Hin-Tak Leung <htl10@users.sourceforge.net>
Reviewed-by: Anton Altaparmakov <anton@tuxera.com>
Cc: Vyacheslav Dubeyko <slava@dubeyko.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Sougata Santra <sougata@tuxera.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Hin-Tak Leung and committed by
Linus Torvalds
017f8da4 6d6bd94f

+21 -5
+9 -2
fs/hfsplus/dir.c
··· 12 12 #include <linux/fs.h> 13 13 #include <linux/slab.h> 14 14 #include <linux/random.h> 15 + #include <linux/nls.h> 15 16 16 17 #include "hfsplus_fs.h" 17 18 #include "hfsplus_raw.h" ··· 128 127 struct inode *inode = file_inode(file); 129 128 struct super_block *sb = inode->i_sb; 130 129 int len, err; 131 - char strbuf[HFSPLUS_MAX_STRLEN + 1]; 130 + char *strbuf; 132 131 hfsplus_cat_entry entry; 133 132 struct hfs_find_data fd; 134 133 struct hfsplus_readdir_data *rd; ··· 140 139 err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); 141 140 if (err) 142 141 return err; 142 + strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN + 1, GFP_KERNEL); 143 + if (!strbuf) { 144 + err = -ENOMEM; 145 + goto out; 146 + } 143 147 hfsplus_cat_build_key(sb, fd.search_key, inode->i_ino, NULL); 144 148 err = hfs_brec_find(&fd, hfs_find_rec_by_key); 145 149 if (err) ··· 199 193 hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, 200 194 fd.entrylength); 201 195 type = be16_to_cpu(entry.type); 202 - len = HFSPLUS_MAX_STRLEN; 196 + len = NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN; 203 197 err = hfsplus_uni2asc(sb, &fd.key->cat.name, strbuf, &len); 204 198 if (err) 205 199 goto out; ··· 252 246 } 253 247 memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key)); 254 248 out: 249 + kfree(strbuf); 255 250 hfs_find_exit(&fd); 256 251 return err; 257 252 }
+12 -3
fs/hfsplus/xattr.c
··· 8 8 9 9 #include "hfsplus_fs.h" 10 10 #include <linux/posix_acl_xattr.h> 11 + #include <linux/nls.h> 11 12 #include "xattr.h" 12 13 #include "acl.h" 13 14 ··· 646 645 struct hfs_find_data fd; 647 646 u16 key_len = 0; 648 647 struct hfsplus_attr_key attr_key; 649 - char strbuf[HFSPLUS_ATTR_MAX_STRLEN + 650 - XATTR_MAC_OSX_PREFIX_LEN + 1] = {0}; 648 + char *strbuf; 651 649 int xattr_name_len; 652 650 653 651 if ((!S_ISREG(inode->i_mode) && ··· 664 664 if (err) { 665 665 pr_err("can't init xattr find struct\n"); 666 666 return err; 667 + } 668 + 669 + strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 670 + XATTR_MAC_OSX_PREFIX_LEN + 1, GFP_KERNEL); 671 + if (!strbuf) { 672 + res = -ENOMEM; 673 + goto out; 667 674 } 668 675 669 676 err = hfsplus_find_attr(inode->i_sb, inode->i_ino, NULL, &fd); ··· 699 692 if (be32_to_cpu(attr_key.cnid) != inode->i_ino) 700 693 goto end_listxattr; 701 694 702 - xattr_name_len = HFSPLUS_ATTR_MAX_STRLEN; 695 + xattr_name_len = NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN; 703 696 if (hfsplus_uni2asc(inode->i_sb, 704 697 (const struct hfsplus_unistr *)&fd.key->attr.key_name, 705 698 strbuf, &xattr_name_len)) { ··· 725 718 } 726 719 727 720 end_listxattr: 721 + kfree(strbuf); 722 + out: 728 723 hfs_find_exit(&fd); 729 724 return res; 730 725 }