diff mbox series

[v3] hugetlbfs: Fix integer overflow check in hugetlbfs_file_mmap()

Message ID tencent_C2D6865561F23A8141BB145149ACC682B408@qq.com (mailing list archive)
State New
Headers show
Series [v3] hugetlbfs: Fix integer overflow check in hugetlbfs_file_mmap() | expand

Commit Message

Linke Li July 20, 2023, 2:49 p.m. UTC
From: Linke Li <lilinke99@gmail.com>

```
	vma_len = (loff_t)(vma->vm_end - vma->vm_start);
	len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
	/* check for overflow */
	if (len < vma_len)
		return -EINVAL;
```

There is a signed integer overflow in the code, which is undefined
behavior according to the C stacnard. Although this works, it's
still a bit ugly and static checkers will complain.

Using macro "check_add_overflow" to do the overflow check can
effectively detect integer overflow and avoid any undefined behavior.

Signed-off-by: Linke Li <lilinke99@gmail.com>
---
v3: fix checkpatch warning and better description.
 fs/hugetlbfs/inode.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

Comments

Mike Kravetz July 20, 2023, 6:41 p.m. UTC | #1
On 07/20/23 22:49, Linke Li wrote:
> From: Linke Li <lilinke99@gmail.com>
> 
> ```
> 	vma_len = (loff_t)(vma->vm_end - vma->vm_start);
> 	len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
> 	/* check for overflow */
> 	if (len < vma_len)
> 		return -EINVAL;
> ```
> 
> There is a signed integer overflow in the code, which is undefined
> behavior according to the C stacnard. Although this works, it's
> still a bit ugly and static checkers will complain.
> 
> Using macro "check_add_overflow" to do the overflow check can
> effectively detect integer overflow and avoid any undefined behavior.
> 
> Signed-off-by: Linke Li <lilinke99@gmail.com>
> ---
> v3: fix checkpatch warning and better description.
>  fs/hugetlbfs/inode.c | 5 +----
>  1 file changed, 1 insertion(+), 4 deletions(-)
> 
> diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
> index 7b17ccfa039d..326a8c0af5f6 100644
> --- a/fs/hugetlbfs/inode.c
> +++ b/fs/hugetlbfs/inode.c
> @@ -154,10 +154,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
>  	if (vma->vm_pgoff & (~huge_page_mask(h) >> PAGE_SHIFT))
>  		return -EINVAL;
>  
> -	vma_len = (loff_t)(vma->vm_end - vma->vm_start);

I don't think you wanted to delete the above line as ...

> -	len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
> -	/* check for overflow */
> -	if (len < vma_len)
> +	if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))

.. vma_len is uninitialized here.

>  		return -EINVAL;
>  
>  	inode_lock(inode);
> -- 
> 2.25.1
>
Nick Desaulniers July 20, 2023, 7:03 p.m. UTC | #2
On Thu, Jul 20, 2023 at 7:50 AM Linke Li <lilinke99@foxmail.com> wrote:
>
> From: Linke Li <lilinke99@gmail.com>
>
> ```
>         vma_len = (loff_t)(vma->vm_end - vma->vm_start);
>         len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
>         /* check for overflow */
>         if (len < vma_len)
>                 return -EINVAL;
> ```
>
> There is a signed integer overflow in the code, which is undefined
> behavior according to the C stacnard. Although this works, it's

typo: s/stacnard/standard/

I think the commit message should explicitly mention that the Linux
kernel is built with -fno-strict-overflow, but IMO that kind of makes
this patch moot.

> still a bit ugly and static checkers will complain.
>
> Using macro "check_add_overflow" to do the overflow check can
> effectively detect integer overflow and avoid any undefined behavior.
>
> Signed-off-by: Linke Li <lilinke99@gmail.com>
> ---
> v3: fix checkpatch warning and better description.
>  fs/hugetlbfs/inode.c | 5 +----
>  1 file changed, 1 insertion(+), 4 deletions(-)
>
> diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
> index 7b17ccfa039d..326a8c0af5f6 100644
> --- a/fs/hugetlbfs/inode.c
> +++ b/fs/hugetlbfs/inode.c
> @@ -154,10 +154,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
>         if (vma->vm_pgoff & (~huge_page_mask(h) >> PAGE_SHIFT))
>                 return -EINVAL;
>
> -       vma_len = (loff_t)(vma->vm_end - vma->vm_start);
> -       len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
> -       /* check for overflow */
> -       if (len < vma_len)
> +       if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))
>                 return -EINVAL;
>
>         inode_lock(inode);
> --
> 2.25.1
>
kernel test robot July 20, 2023, 11:36 p.m. UTC | #3
Hi Linke,

kernel test robot noticed the following build warnings:

[auto build test WARNING on linus/master]
[also build test WARNING on v6.5-rc2 next-20230720]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Linke-Li/hugetlbfs-Fix-integer-overflow-check-in-hugetlbfs_file_mmap/20230720-225128
base:   linus/master
patch link:    https://lore.kernel.org/r/tencent_C2D6865561F23A8141BB145149ACC682B408%40qq.com
patch subject: [PATCH v3] hugetlbfs: Fix integer overflow check in hugetlbfs_file_mmap()
config: s390-randconfig-r024-20230720 (https://download.01.org/0day-ci/archive/20230721/202307210737.HUPwpBdW-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project.git 4a5ac14ee968ff0ad5d2cc1ffa0299048db4c88a)
reproduce: (https://download.01.org/0day-ci/archive/20230721/202307210737.HUPwpBdW-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202307210737.HUPwpBdW-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> fs/hugetlbfs/inode.c:157:25: warning: variable 'vma_len' is uninitialized when used here [-Wuninitialized]
     157 |         if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))
         |                                ^~~~~~~
   include/linux/overflow.h:67:47: note: expanded from macro 'check_add_overflow'
      67 |         __must_check_overflow(__builtin_add_overflow(a, b, d))
         |                                                      ^
   fs/hugetlbfs/inode.c:123:21: note: initialize the variable 'vma_len' to silence this warning
     123 |         loff_t len, vma_len;
         |                            ^
         |                             = 0
   1 warning generated.


vim +/vma_len +157 fs/hugetlbfs/inode.c

   108	
   109	/*
   110	 * Mask used when checking the page offset value passed in via system
   111	 * calls.  This value will be converted to a loff_t which is signed.
   112	 * Therefore, we want to check the upper PAGE_SHIFT + 1 bits of the
   113	 * value.  The extra bit (- 1 in the shift value) is to take the sign
   114	 * bit into account.
   115	 */
   116	#define PGOFF_LOFFT_MAX \
   117		(((1UL << (PAGE_SHIFT + 1)) - 1) <<  (BITS_PER_LONG - (PAGE_SHIFT + 1)))
   118	
   119	static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
   120	{
   121		struct inode *inode = file_inode(file);
   122		struct hugetlbfs_inode_info *info = HUGETLBFS_I(inode);
   123		loff_t len, vma_len;
   124		int ret;
   125		struct hstate *h = hstate_file(file);
   126	
   127		/*
   128		 * vma address alignment (but not the pgoff alignment) has
   129		 * already been checked by prepare_hugepage_range.  If you add
   130		 * any error returns here, do so after setting VM_HUGETLB, so
   131		 * is_vm_hugetlb_page tests below unmap_region go the right
   132		 * way when do_mmap unwinds (may be important on powerpc
   133		 * and ia64).
   134		 */
   135		vm_flags_set(vma, VM_HUGETLB | VM_DONTEXPAND);
   136		vma->vm_ops = &hugetlb_vm_ops;
   137	
   138		ret = seal_check_future_write(info->seals, vma);
   139		if (ret)
   140			return ret;
   141	
   142		/*
   143		 * page based offset in vm_pgoff could be sufficiently large to
   144		 * overflow a loff_t when converted to byte offset.  This can
   145		 * only happen on architectures where sizeof(loff_t) ==
   146		 * sizeof(unsigned long).  So, only check in those instances.
   147		 */
   148		if (sizeof(unsigned long) == sizeof(loff_t)) {
   149			if (vma->vm_pgoff & PGOFF_LOFFT_MAX)
   150				return -EINVAL;
   151		}
   152	
   153		/* must be huge page aligned */
   154		if (vma->vm_pgoff & (~huge_page_mask(h) >> PAGE_SHIFT))
   155			return -EINVAL;
   156	
 > 157		if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))
   158			return -EINVAL;
   159	
   160		inode_lock(inode);
   161		file_accessed(file);
   162	
   163		ret = -ENOMEM;
   164		if (!hugetlb_reserve_pages(inode,
   165					vma->vm_pgoff >> huge_page_order(h),
   166					len >> huge_page_shift(h), vma,
   167					vma->vm_flags))
   168			goto out;
   169	
   170		ret = 0;
   171		if (vma->vm_flags & VM_WRITE && inode->i_size < len)
   172			i_size_write(inode, len);
   173	out:
   174		inode_unlock(inode);
   175	
   176		return ret;
   177	}
   178
Matthew Wilcox July 24, 2023, 3:47 a.m. UTC | #4
On Thu, Jul 20, 2023 at 10:49:52PM +0800, Linke Li wrote:
> +++ b/fs/hugetlbfs/inode.c
> @@ -154,10 +154,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
>  	if (vma->vm_pgoff & (~huge_page_mask(h) >> PAGE_SHIFT))
>  		return -EINVAL;
>  
> -	vma_len = (loff_t)(vma->vm_end - vma->vm_start);
> -	len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
> -	/* check for overflow */
> -	if (len < vma_len)
> +	if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))
>  		return -EINVAL;

Doesn't this check duplicate that performed by file_mmap_ok()?  Can't we
just delete the check, or is there a code path that leads here while
avoiding file_mmap_ok()?
kernel test robot July 24, 2023, 8:11 a.m. UTC | #5
Hello,

kernel test robot noticed "WARNING:at_mm/hugetlb.c:#hugetlb_reserve_pages" on:

commit: c5ffbcff59bdefdc2f54a0d12955da6e3fe79e21 ("[PATCH v3] hugetlbfs: Fix integer overflow check in hugetlbfs_file_mmap()")
url: https://github.com/intel-lab-lkp/linux/commits/Linke-Li/hugetlbfs-Fix-integer-overflow-check-in-hugetlbfs_file_mmap/20230720-225128
base: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git bfa3037d828050896ae52f6467b6ca2489ae6fb1
patch link: https://lore.kernel.org/all/tencent_C2D6865561F23A8141BB145149ACC682B408@qq.com/
patch subject: [PATCH v3] hugetlbfs: Fix integer overflow check in hugetlbfs_file_mmap()

in testcase: trinity
version: trinity-i386-abe9de86-1_20230429
with following parameters:

	runtime: 300s
	group: group-04
	nr_groups: 5

test-description: Trinity is a linux system call fuzz tester.
test-url: http://codemonkey.org.uk/projects/trinity/


compiler: clang-15
test machine: qemu-system-i386 -enable-kvm -cpu SandyBridge -smp 2 -m 4G

(please refer to attached dmesg/kmsg for entire log/backtrace)



If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <oliver.sang@intel.com>
| Closes: https://lore.kernel.org/oe-lkp/202307241509.546e3574-oliver.sang@intel.com



[   39.198826][ T3397] ------------[ cut here ]------------
[   39.199142][ T3397] hugetlb_reserve_pages called with a negative range
[   39.199541][ T3397] WARNING: CPU: 0 PID: 3397 at mm/hugetlb.c:6852 hugetlb_reserve_pages+0x39/0x410
[   39.200009][ T3397] Modules linked in:
[   39.200211][ T3397] CPU: 0 PID: 3397 Comm: trinity-main Not tainted 6.5.0-rc2-00053-gc5ffbcff59bd #1
[   39.200711][ T3397] EIP: hugetlb_reserve_pages+0x39/0x410
[   39.200992][ T3397] Code: 80 cc 03 00 00 8b 58 34 8b 40 38 c7 45 e4 00 00 00 00 89 cf 29 d7 7d 24 68 28 48 ca 82 68 43 ec bd 82 e8 3a 9a e2 ff 83 c4
08 <0f> 0b 31 db 89 d8 83 c4 18 5e 5f 5b 5d 31 c9 31 d2 c3 89 55 e0 89
[   39.201989][ T3397] EAX: 00000000 EBX: 83cdb4a8 ECX: 00000000 EDX: 00000000
[   39.202347][ T3397] ESI: ec0002d8 EDI: ffffffff EBP: ec7d9dc8 ESP: ec7d9da4
[   39.202749][ T3397] DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 0068 EFLAGS: 00010292
[   39.203139][ T3397] CR0: 80050033 CR2: 01100000 CR3: 6b83c000 CR4: 00040690
[   39.203534][ T3397] DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000
[   39.203893][ T3397] DR6: fffe0ff0 DR7: 00000400
[   39.204132][ T3397] Call Trace:
[   39.204302][ T3397]  ? show_regs+0x4d/0x60
[   39.204553][ T3397]  ? hugetlb_reserve_pages+0x39/0x410
[   39.204827][ T3397]  ? __warn+0xa2/0x180
[   39.205036][ T3397]  ? hugetlb_reserve_pages+0x39/0x410
[   39.205308][ T3397]  ? hugetlb_reserve_pages+0x39/0x410
[   39.205613][ T3397]  ? report_bug+0x96/0x120
[   39.205845][ T3397]  ? vprintk_emit+0x6b/0x120
[   39.206082][ T3397]  ? exc_overflow+0x40/0x40
[   39.206315][ T3397]  ? handle_bug+0x29/0x50
[   39.206569][ T3397]  ? exc_invalid_op+0x17/0x40
[   39.206816][ T3397]  ? handle_exception+0x115/0x115
[   39.207075][ T3397]  ? exc_overflow+0x40/0x40
[   39.207307][ T3397]  ? hugetlb_reserve_pages+0x39/0x410
[   39.207612][ T3397]  ? exc_overflow+0x40/0x40
[   39.207852][ T3397]  ? hugetlb_reserve_pages+0x39/0x410
[   39.208126][ T3397]  ? hugetlbfs_file_mmap+0xb3/0x1b0
[   39.208392][ T3397]  hugetlbfs_file_mmap+0x116/0x1b0
[   39.208689][ T3397]  mmap_region+0x4c1/0x930
[   39.208918][ T3397]  ? vm_unmapped_area+0x1be/0x2b0
[   39.209183][ T3397]  do_mmap+0x352/0x4f0
[   39.209396][ T3397]  vm_mmap_pgoff+0x80/0x100
[   39.209659][ T3397]  ksys_mmap_pgoff+0x1aa/0x290
[   39.209907][ T3397]  __ia32_sys_mmap_pgoff+0x1c/0x30
[   39.210168][ T3397]  __do_fast_syscall_32+0x90/0xe0
[   39.210425][ T3397]  ? lockdep_hardirqs_on+0x85/0x110
[   39.210720][ T3397]  do_fast_syscall_32+0x28/0x60
[   39.210973][ T3397]  do_SYSENTER_32+0x12/0x20
[   39.211204][ T3397]  entry_SYSENTER_32+0xa6/0x10c
[   39.211452][ T3397] EIP: 0x77fb6539
[   39.211668][ T3397] Code: 03 74 b4 01 10 07 03 74 b0 01 10 08 03 74 d8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 51 52 55 89 e5 0f 34 cd 80 <5d> 5a 59 c3 90 90 90 90 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00
[   39.212668][ T3397] EAX: ffffffda EBX: 00000000 ECX: 00001000 EDX: 00000001
[   39.213024][ T3397] ESI: 00050022 EDI: 00000023 EBP: 00000000 ESP: 7ff736bc
[   39.213379][ T3397] DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b EFLAGS: 00000246
[   39.213794][ T3397] irq event stamp: 2287591
[   39.214020][ T3397] hardirqs last  enabled at (2287599): [<8108610a>] __up_console_sem+0x5a/0x70
[   39.214468][ T3397] hardirqs last disabled at (2287606): [<810860f1>] __up_console_sem+0x41/0x70
[   39.214944][ T3397] softirqs last  enabled at (2287262): [<81007a66>] do_softirq_own_stack+0x26/0x30
[   39.215410][ T3397] softirqs last disabled at (2287253): [<81007a66>] do_softirq_own_stack+0x26/0x30
[   39.215891][ T3397] ---[ end trace 0000000000000000 ]---



To reproduce:

        # build kernel
	cd linux
	cp config-6.5.0-rc2-00053-gc5ffbcff59bd .config
	make HOSTCC=clang-15 CC=clang-15 ARCH=i386 olddefconfig prepare modules_prepare bzImage modules
	make HOSTCC=clang-15 CC=clang-15 ARCH=i386 INSTALL_MOD_PATH=<mod-install-dir> modules_install
	cd <mod-install-dir>
	find lib/ | cpio -o -H newc --quiet | gzip > modules.cgz


        git clone https://github.com/intel/lkp-tests.git
        cd lkp-tests
        bin/lkp qemu -k <bzImage> -m modules.cgz job-script # job-script is attached in this email

        # if come across any failure that blocks the test,
        # please remove ~/.lkp and /lkp dir to run from a clean state.
Mike Kravetz July 24, 2023, 6:59 p.m. UTC | #6
On 07/24/23 04:47, Matthew Wilcox wrote:
> On Thu, Jul 20, 2023 at 10:49:52PM +0800, Linke Li wrote:
> > +++ b/fs/hugetlbfs/inode.c
> > @@ -154,10 +154,7 @@ static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
> >  	if (vma->vm_pgoff & (~huge_page_mask(h) >> PAGE_SHIFT))
> >  		return -EINVAL;
> >  
> > -	vma_len = (loff_t)(vma->vm_end - vma->vm_start);
> > -	len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
> > -	/* check for overflow */
> > -	if (len < vma_len)
> > +	if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))
> >  		return -EINVAL;
> 
> Doesn't this check duplicate that performed by file_mmap_ok()?  Can't we
> just delete the check, or is there a code path that leads here while
> avoiding file_mmap_ok()?

Thanks for pointing that out.
Yes, from my reading/understanding that is a repeat.

It looks like most of the overflow checking in hugetlbfs_file_mmap is a
repeat of checks done previously.  I remember adding this code in
response to a checker or someone pointing out the potential for overflow:

	/*
	 * page based offset in vm_pgoff could be sufficiently large to
	 * overflow a loff_t when converted to byte offset.  This can
	 * only happen on architectures where sizeof(loff_t) ==
	 * sizeof(unsigned long).  So, only check in those instances.
	 */
	if (sizeof(unsigned long) == sizeof(loff_t)) {
		if (vma->vm_pgoff & PGOFF_LOFFT_MAX)
			return -EINVAL;
	}

However, file_mmap_ok seems to handle this as well.  The important thing that
needs to be done in hugetlbfs_file_mmap is checking for huge page alignment.

I have added this code cleanup to my list if someone does not do it first.
diff mbox series

Patch

diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 7b17ccfa039d..326a8c0af5f6 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -154,10 +154,7 @@  static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma)
 	if (vma->vm_pgoff & (~huge_page_mask(h) >> PAGE_SHIFT))
 		return -EINVAL;
 
-	vma_len = (loff_t)(vma->vm_end - vma->vm_start);
-	len = vma_len + ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
-	/* check for overflow */
-	if (len < vma_len)
+	if (check_add_overflow(vma_len, (loff_t)vma->vm_pgoff << PAGE_SHIFT, &len))
 		return -EINVAL;
 
 	inode_lock(inode);