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

exfat: fix out-of-bounds in exfat_nls_to_ucs2()

Since the len argument value passed to exfat_ioctl_set_volume_label()
from exfat_nls_to_utf16() is passed 1 too large, an out-of-bounds read
occurs when dereferencing p_cstring in exfat_nls_to_ucs2() later.

And because of the NLS_NAME_OVERLEN macro, another error occurs when
creating a file with a period at the end using utf8 and other iocharsets.

So to avoid this, you should remove the code that uses NLS_NAME_OVERLEN
macro and make the len argument value be the length of the label string,
but with a maximum length of FSLABEL_MAX - 1.

Reported-by: syzbot+98cc76a76de46b3714d4@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=98cc76a76de46b3714d4
Fixes: d01579d590f7 ("exfat: Add support for FS_IOC_{GET,SET}FSLABEL")
Suggested-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Jeongjun Park <aha310510@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>

authored by

Jeongjun Park and committed by
Namjae Jeon
2d863611 82ebecdc

+5 -8
-1
fs/exfat/exfat_fs.h
··· 29 29 enum { 30 30 NLS_NAME_NO_LOSSY = 0, /* no lossy */ 31 31 NLS_NAME_LOSSY = 1 << 0, /* just detected incorrect filename(s) */ 32 - NLS_NAME_OVERLEN = 1 << 1, /* the length is over than its limit */ 33 32 }; 34 33 35 34 #define EXFAT_HASH_BITS 8
+4 -3
fs/exfat/file.c
··· 509 509 static int exfat_ioctl_set_volume_label(struct super_block *sb, 510 510 unsigned long arg) 511 511 { 512 - int ret = 0, lossy; 513 - char label[FSLABEL_MAX]; 512 + int ret = 0, lossy, label_len; 513 + char label[FSLABEL_MAX] = {0}; 514 514 struct exfat_uni_name uniname; 515 515 516 516 if (!capable(CAP_SYS_ADMIN)) ··· 520 520 return -EFAULT; 521 521 522 522 memset(&uniname, 0, sizeof(uniname)); 523 + label_len = strnlen(label, FSLABEL_MAX - 1); 523 524 if (label[0]) { 524 - ret = exfat_nls_to_utf16(sb, label, FSLABEL_MAX, 525 + ret = exfat_nls_to_utf16(sb, label, label_len, 525 526 &uniname, &lossy); 526 527 if (ret < 0) 527 528 return ret;
+1 -1
fs/exfat/namei.c
··· 442 442 return namelen; /* return error value */ 443 443 444 444 if ((lossy && !lookup) || !namelen) 445 - return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL; 445 + return -EINVAL; 446 446 447 447 return 0; 448 448 }
-3
fs/exfat/nls.c
··· 616 616 unilen++; 617 617 } 618 618 619 - if (p_cstring[i] != '\0') 620 - lossy |= NLS_NAME_OVERLEN; 621 - 622 619 *uniname = '\0'; 623 620 p_uniname->name_len = unilen; 624 621 p_uniname->name_hash = exfat_calc_chksum16(upname, unilen << 1, 0,