Skip to content

Commit

Permalink
vfat: bug fix for vfat cannot handle filename with 255
Browse files Browse the repository at this point in the history
This patch fix the problem that the buffer allocated for convert of unicode to
utf8 in fat/dir.c is too small.

And cannot handle filename with 255 asian characters when mounted with utf8
options.

Also it fix the filename length limitation checking in vfat/namei.c that the
filename length should be checked against the number of converted unicode
characters.

Not the length before NLS/UTF8 converted.

Signed-off-by: Keith Mok <ek9852@gmail.com>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Keith Mok authored and Linus Torvalds committed Apr 28, 2008
1 parent 061e974 commit f22032b
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 30 deletions.
44 changes: 28 additions & 16 deletions fs/fat/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ static inline int fat_get_entry(struct inode *dir, loff_t *pos,
* but ignore that right now.
* Ahem... Stack smashing in ring 0 isn't fun. Fixed.
*/
static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
struct nls_table *nls)
static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int len,
int uni_xlate, struct nls_table *nls)
{
wchar_t *ip, ec;
unsigned char *op, nc;
Expand All @@ -135,10 +135,11 @@ static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
ip = uni;
op = ascii;

while (*ip) {
while (*ip && ((len - NLS_MAX_CHARSET_SIZE) > 0)) {
ec = *ip++;
if ( (charlen = nls->uni2char(ec, op, NLS_MAX_CHARSET_SIZE)) > 0) {
op += charlen;
len -= charlen;
} else {
if (uni_xlate == 1) {
*op = ':';
Expand All @@ -149,16 +150,19 @@ static int uni16_to_x8(unsigned char *ascii, wchar_t *uni, int uni_xlate,
ec >>= 4;
}
op += 5;
len -= 5;
} else {
*op++ = '?';
len--;
}
}
/* We have some slack there, so it's OK */
if (op>ascii+256) {
op = ascii + 256;
break;
}
}

if (unlikely(*ip)) {
printk(KERN_WARNING "FAT: filename was truncated while "
"converting.");
}

*op = 0;
return (op - ascii);
}
Expand Down Expand Up @@ -311,16 +315,22 @@ int fat_search_long(struct inode *inode, const unsigned char *name,
struct nls_table *nls_io = sbi->nls_io;
struct nls_table *nls_disk = sbi->nls_disk;
wchar_t bufuname[14];
unsigned char xlate_len, nr_slots;
unsigned char nr_slots;
int xlate_len;
wchar_t *unicode = NULL;
unsigned char work[MSDOS_NAME], bufname[260]; /* 256 + 4 */
unsigned char work[MSDOS_NAME];
unsigned char *bufname = NULL;
int uni_xlate = sbi->options.unicode_xlate;
int utf8 = sbi->options.utf8;
int anycase = (sbi->options.name_check != 's');
unsigned short opt_shortname = sbi->options.shortname;
loff_t cpos = 0;
int chl, i, j, last_u, err;

bufname = (unsigned char*)__get_free_page(GFP_KERNEL);
if (!bufname)
return -ENOMEM;

err = -ENOENT;
while(1) {
if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
Expand Down Expand Up @@ -386,8 +396,8 @@ int fat_search_long(struct inode *inode, const unsigned char *name,

bufuname[last_u] = 0x0000;
xlate_len = utf8
?utf8_wcstombs(bufname, bufuname, sizeof(bufname))
:uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
?utf8_wcstombs(bufname, bufuname, PAGE_SIZE)
:uni16_to_x8(bufname, bufuname, PAGE_SIZE, uni_xlate, nls_io);
if (xlate_len == name_len)
if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
(anycase && !nls_strnicmp(nls_io, name, bufname,
Expand All @@ -396,8 +406,8 @@ int fat_search_long(struct inode *inode, const unsigned char *name,

if (nr_slots) {
xlate_len = utf8
?utf8_wcstombs(bufname, unicode, sizeof(bufname))
:uni16_to_x8(bufname, unicode, uni_xlate, nls_io);
?utf8_wcstombs(bufname, unicode, PAGE_SIZE)
:uni16_to_x8(bufname, unicode, PAGE_SIZE, uni_xlate, nls_io);
if (xlate_len != name_len)
continue;
if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
Expand All @@ -416,6 +426,8 @@ int fat_search_long(struct inode *inode, const unsigned char *name,
sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
err = 0;
EODir:
if (bufname)
free_page((unsigned long)bufname);
if (unicode)
free_page((unsigned long)unicode);

Expand Down Expand Up @@ -598,7 +610,7 @@ static int __fat_readdir(struct inode *inode, struct file *filp, void *dirent,
if (isvfat) {
bufuname[j] = 0x0000;
i = utf8 ? utf8_wcstombs(bufname, bufuname, sizeof(bufname))
: uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
: uni16_to_x8(bufname, bufuname, sizeof(bufname), uni_xlate, nls_io);
}

fill_name = bufname;
Expand All @@ -610,7 +622,7 @@ static int __fat_readdir(struct inode *inode, struct file *filp, void *dirent,
int buf_size = PAGE_SIZE - (261 * sizeof(unicode[0]));
int long_len = utf8
? utf8_wcstombs(longname, unicode, buf_size)
: uni16_to_x8(longname, unicode, uni_xlate, nls_io);
: uni16_to_x8(longname, unicode, buf_size, uni_xlate, nls_io);

if (!both) {
fill_name = longname;
Expand Down
25 changes: 11 additions & 14 deletions fs/vfat/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,10 @@ static inline int vfat_is_used_badchars(const wchar_t *s, int len)
for (i = 0; i < len; i++)
if (vfat_bad_char(s[i]))
return -EINVAL;
return 0;
}

static int vfat_valid_longname(const unsigned char *name, unsigned int len)
{
if (name[len - 1] == ' ')
if (s[i - 1] == ' ') /* last character cannot be space */
return -EINVAL;
if (len >= 256)
return -ENAMETOOLONG;

return 0;
}

Expand Down Expand Up @@ -485,11 +480,14 @@ xlate_to_uni(const unsigned char *name, int len, unsigned char *outname,
*/
*outlen -= (name_len - len);

if (*outlen > 255)
return -ENAMETOOLONG;

op = &outname[*outlen * sizeof(wchar_t)];
} else {
if (nls) {
for (i = 0, ip = name, op = outname, *outlen = 0;
i < len && *outlen <= 260;
i < len && *outlen <= 255;
*outlen += 1)
{
if (escape && (*ip == ':')) {
Expand Down Expand Up @@ -525,18 +523,20 @@ xlate_to_uni(const unsigned char *name, int len, unsigned char *outname,
op += 2;
}
}
if (i < len)
return -ENAMETOOLONG;
} else {
for (i = 0, ip = name, op = outname, *outlen = 0;
i < len && *outlen <= 260;
i < len && *outlen <= 255;
i++, *outlen += 1)
{
*op++ = *ip++;
*op++ = 0;
}
if (i < len)
return -ENAMETOOLONG;
}
}
if (*outlen > 260)
return -ENAMETOOLONG;

*longlen = *outlen;
if (*outlen % 13) {
Expand Down Expand Up @@ -574,9 +574,6 @@ static int vfat_build_slots(struct inode *dir, const unsigned char *name,
loff_t offset;

*nr_slots = 0;
err = vfat_valid_longname(name, len);
if (err)
return err;

page = __get_free_page(GFP_KERNEL);
if (!page)
Expand Down

0 comments on commit f22032b

Please sign in to comment.