CVE-2020-28097 (Other, published 2021-06-24T12:15Z)
Description: value: The vgacon subsystem in the Linux kernel before 5.8.10 mishandles software scrollback. There is a vgacon_scrolldelta out-of-bounds read, aka CID-973c096f6a85.
Severity: LOW (CVSS v3 base score: 5.9)
Weakness: CWE-125 \u2014 Out-of-bounds Read
The product reads data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/torvalds/linux
Files changed:
arch/powerpc/configs/pasemi_defconfig
arch/powerpc/configs/ppc6xx_defconfig
arch/x86/configs/i386_defconfig
arch/x86/configs/x86_64_defconfig
drivers/video/console/Kconfig
drivers/video/console/vgacon.c
Commit message: vgacon: remove software scrollback support

Yunhai Zhang recently fixed a VGA software scrollback bug in commit
ebfdfeeae8c0 ("vgacon: Fix for missing check in scrollback handling"),
but that then made people look more closely at some of this code, and
there were more problems on the vgacon side, but also the fbcon software
scrollback.

We don't really have anybody who maintains this code - probably because
nobody actually _uses_ it any more.  Sure, people still use both VGA and
the framebuffer consoles, but they are no longer the main user
interfaces to the kernel, and haven't been for decades, so these kinds
of extra features end up bitrotting and not really being used.

So rather than try to maintain a likely unused set of code, I'll just
aggressively remove it, and see if anybody even notices.  Maybe there
are people who haven't jumped on the whole GUI badnwagon yet, and think
it's just a fad.  And maybe those people use the scrollback code.

If that turns out to be the case, we can resurrect this again, once
we've found the sucker^Wmaintainer for it who actually uses it.

Reported-by: NopNop Nop <nopitydays@gmail.com>
Tested-by: Willy Tarreau <w@1wt.eu>
Cc: 张云海 <zhangyunhai@nsfocus.com>
Acked-by: Andy Lutomirski <luto@amacapital.net>
Acked-by: Willy Tarreau <w@1wt.eu>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>


Vulnerable code:
CONFIG_VGACON_SOFT_SCROLLBACK=y
CONFIG_VGACON_SOFT_SCROLLBACK=y
CONFIG_VGACON_SOFT_SCROLLBACK=y
CONFIG_VGACON_SOFT_SCROLLBACK=y
config VGACON_SOFT_SCROLLBACK
       bool "Enable Scrollback Buffer in System RAM"
       depends on VGA_CONSOLE
       default n
       help
	 The scrollback buffer of the standard VGA console is located in
	 the VGA RAM.  The size of this RAM is fixed and is quite small.
	 If you require a larger scrollback buffer, this can be placed in
	 System RAM which is dynamically allocated during initialization.
	 Placing the scrollback buffer in System RAM will slightly slow
	 down the console.

	 If you want this feature, say 'Y' here and enter the amount of
	 RAM to allocate for this buffer.  If unsure, say 'N'.

config VGACON_SOFT_SCROLLBACK_SIZE
       int "Scrollback Buffer Size (in KB)"
       depends on VGACON_SOFT_SCROLLBACK
       range 1 1024
       default "64"
       help
	  Enter the amount of System RAM to allocate for scrollback
	  buffers of VGA consoles. Each 64KB will give you approximately
	  16 80x25 screenfuls of scrollback buffer.

config VGACON_SOFT_SCROLLBACK_PERSISTENT_ENABLE_BY_DEFAULT
	bool "Persistent Scrollback History for each console by default"
	depends on VGACON_SOFT_SCROLLBACK
	default n
	help
	  Say Y here if the scrollback history should persist by default when
	  switching between consoles. Otherwise, the scrollback history will be
	  flushed each time the console is switched. This feature can also be
	  enabled using the boot command line parameter
	  'vgacon.scrollback_persistent=1'.

	  This feature might break your tool of choice to flush the scrollback
	  buffer, e.g. clear(1) will work fine but Debian's clear_console(1)
	  will be broken, which might cause security issues.
	  You can use the escape sequence \e[3J instead if this feature is
	  activated.

	  Note that a buffer of VGACON_SOFT_SCROLLBACK_SIZE is taken for each
	  created tty device.
	  So if you use a RAM-constrained system, say N here.

#ifdef CONFIG_VGACON_SOFT_SCROLLBACK
/* software scrollback */
struct vgacon_scrollback_info {
	void *data;
	int tail;
	int size;
	int rows;
	int cnt;
	int cur;
	int save;
	int restore;
};

static struct vgacon_scrollback_info *vgacon_scrollback_cur;
static struct vgacon_scrollback_info vgacon_scrollbacks[MAX_NR_CONSOLES];
static bool scrollback_persistent = \
	IS_ENABLED(CONFIG_VGACON_SOFT_SCROLLBACK_PERSISTENT_ENABLE_BY_DEFAULT);
module_param_named(scrollback_persistent, scrollback_persistent, bool, 0000);
MODULE_PARM_DESC(scrollback_persistent, "Enable persistent scrollback for all vga consoles");

static void vgacon_scrollback_reset(int vc_num, size_t reset_size)
{
	struct vgacon_scrollback_info *scrollback = &vgacon_scrollbacks[vc_num];

	if (scrollback->data && reset_size > 0)
		memset(scrollback->data, 0, reset_size);

	scrollback->cnt  = 0;
	scrollback->tail = 0;
	scrollback->cur  = 0;
}

static void vgacon_scrollback_init(int vc_num)
{
	int pitch = vga_video_num_columns * 2;
	size_t size = CONFIG_VGACON_SOFT_SCROLLBACK_SIZE * 1024;
	int rows = size / pitch;
	void *data;

	data = kmalloc_array(CONFIG_VGACON_SOFT_SCROLLBACK_SIZE, 1024,
			     GFP_NOWAIT);

	vgacon_scrollbacks[vc_num].data = data;
	vgacon_scrollback_cur = &vgacon_scrollbacks[vc_num];

	vgacon_scrollback_cur->rows = rows - 1;
	vgacon_scrollback_cur->size = rows * pitch;

	vgacon_scrollback_reset(vc_num, size);
}

static void vgacon_scrollback_switch(int vc_num)
{
	if (!scrollback_persistent)
		vc_num = 0;

	if (!vgacon_scrollbacks[vc_num].data) {
		vgacon_scrollback_init(vc_num);
	} else {
		if (scrollback_persistent) {
			vgacon_scrollback_cur = &vgacon_scrollbacks[vc_num];
		} else {
			size_t size = CONFIG_VGACON_SOFT_SCROLLBACK_SIZE * 1024;

			vgacon_scrollback_reset(vc_num, size);
		}
	}
}

static void vgacon_scrollback_startup(void)
{
	vgacon_scrollback_cur = &vgacon_scrollbacks[0];
	vgacon_scrollback_init(0);
}

static void vgacon_scrollback_update(struct vc_data *c, int t, int count)
{
	void *p;

	if (!vgacon_scrollback_cur->data || !vgacon_scrollback_cur->size ||
	    c->vc_num != fg_console)
		return;

	p = (void *) (c->vc_origin + t * c->vc_size_row);

	while (count--) {
		if ((vgacon_scrollback_cur->tail + c->vc_size_row) >
		    vgacon_scrollback_cur->size)
			vgacon_scrollback_cur->tail = 0;

		scr_memcpyw(vgacon_scrollback_cur->data +
			    vgacon_scrollback_cur->tail,
			    p, c->vc_size_row);

		vgacon_scrollback_cur->cnt++;
		p += c->vc_size_row;
		vgacon_scrollback_cur->tail += c->vc_size_row;

		if (vgacon_scrollback_cur->tail >= vgacon_scrollback_cur->size)
			vgacon_scrollback_cur->tail = 0;

		if (vgacon_scrollback_cur->cnt > vgacon_scrollback_cur->rows)
			vgacon_scrollback_cur->cnt = vgacon_scrollback_cur->rows;

		vgacon_scrollback_cur->cur = vgacon_scrollback_cur->cnt;
	}
}

static void vgacon_restore_screen(struct vc_data *c)
{
	c->vc_origin = c->vc_visible_origin;
	vgacon_scrollback_cur->save = 0;

	if (!vga_is_gfx && !vgacon_scrollback_cur->restore) {
		scr_memcpyw((u16 *) c->vc_origin, (u16 *) c->vc_screenbuf,
			    c->vc_screenbuf_size > vga_vram_size ?
			    vga_vram_size : c->vc_screenbuf_size);
		vgacon_scrollback_cur->restore = 1;
		vgacon_scrollback_cur->cur = vgacon_scrollback_cur->cnt;
	}
}

static void vgacon_scrolldelta(struct vc_data *c, int lines)
{
	int start, end, count, soff;

	if (!lines) {
		vgacon_restore_screen(c);
		return;
	}

	if (!vgacon_scrollback_cur->data)
		return;

	if (!vgacon_scrollback_cur->save) {
		vgacon_cursor(c, CM_ERASE);
		vgacon_save_screen(c);
		c->vc_origin = (unsigned long)c->vc_screenbuf;
		vgacon_scrollback_cur->save = 1;
	}

	vgacon_scrollback_cur->restore = 0;
	start = vgacon_scrollback_cur->cur + lines;
	end = start + abs(lines);

	if (start < 0)
		start = 0;

	if (start > vgacon_scrollback_cur->cnt)
		start = vgacon_scrollback_cur->cnt;

	if (end < 0)
		end = 0;

	if (end > vgacon_scrollback_cur->cnt)
		end = vgacon_scrollback_cur->cnt;

	vgacon_scrollback_cur->cur = start;
	count = end - start;
	soff = vgacon_scrollback_cur->tail -
		((vgacon_scrollback_cur->cnt - end) * c->vc_size_row);
	soff -= count * c->vc_size_row;

	if (soff < 0)
		soff += vgacon_scrollback_cur->size;

	count = vgacon_scrollback_cur->cnt - start;

	if (count > c->vc_rows)
		count = c->vc_rows;

	if (count) {
		int copysize;

		int diff = c->vc_rows - count;
		void *d = (void *) c->vc_visible_origin;
		void *s = (void *) c->vc_screenbuf;

		count *= c->vc_size_row;
		/* how much memory to end of buffer left? */
		copysize = min(count, vgacon_scrollback_cur->size - soff);
		scr_memcpyw(d, vgacon_scrollback_cur->data + soff, copysize);
		d += copysize;
		count -= copysize;

		if (count) {
			scr_memcpyw(d, vgacon_scrollback_cur->data, count);
			d += count;
		}

		if (diff)
			scr_memcpyw(d, s, diff * c->vc_size_row);
	} else
		vgacon_cursor(c, CM_MOVE);
}

static void vgacon_flush_scrollback(struct vc_data *c)
{
	size_t size = CONFIG_VGACON_SOFT_SCROLLBACK_SIZE * 1024;

	vgacon_scrollback_reset(c->vc_num, size);
}
#else
#define vgacon_scrollback_startup(...) do { } while (0)
#define vgacon_scrollback_init(...)    do { } while (0)
#define vgacon_scrollback_update(...)  do { } while (0)
#define vgacon_scrollback_switch(...)  do { } while (0)

static void vgacon_flush_scrollback(struct vc_data *c)
{
}
#endif /* CONFIG_VGACON_SOFT_SCROLLBACK */

	if (!vga_init_done) {
		vgacon_scrollback_startup();
		vga_init_done = true;
	}
	vgacon_scrollback_switch(c->vc_num);
		vgacon_scrollback_update(c, t, lines);
	.con_flush_scrollback = vgacon_flush_scrollback,

Fixed code:
	vga_init_done = true;