Re: [PATCH V2] fbdev: Fix out-of-bounds access when rotating console after font resize

From: Zizhi Wo

Date: Sun Jul 26 2026 - 21:12:43 EST




在 2026/7/25 23:37, Helge Deller 写道:
On 7/22/26 08:22, Zizhi Wo wrote:
From: Zizhi Wo <wozizhi@xxxxxxxxxx>

[BUG]
Recently, we encountered a KASAN warning as follows:

BUG: KASAN: slab-out-of-bounds in ccw_putcs+0x8bd/0xa80
Read of size 1 at addr ff11000110067100 by task bash/1209
CPU: 10 UID: 0 PID: 1209 Comm: bash Not tainted 7.2.0-rc3 #69 PREEMPT(full)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-4.fc41 04/01/2014
  Call Trace:
   <TASK>
   ...
   kasan_report+0xf0/0x120
   ? ccw_putcs+0x8bd/0xa80
   ccw_putcs+0x8bd/0xa80
   ? __pfx_ccw_putcs+0x10/0x10
   fbcon_putcs+0x338/0x410
   ? __pfx_ccw_putcs+0x10/0x10
   do_update_region+0x21d/0x450
   invert_screen+0x29d/0x5e0
   ? __kmalloc_noprof+0x493/0x640
   ? vc_do_resize+0x17c/0xe50
   clear_selection+0x4c/0x60
   vc_do_resize+0xaee/0xe50
   fbcon_modechanged+0x2bd/0x640
   rotate_all_store+0x298/0x380
   ...

reproduce:
1) issue two ioctls: first a KDFONTOP ioctl with op.op = KD_FONT_OP_SET,
op.width = 1 and op.height = 1, then a TIOCL_SETSEL ioctl
2) echo 2 > /sys/devices/virtual/graphics/fbcon/rotate_all
3) issue two ioctls: first a KDFONTOP ioctl with op.op = KD_FONT_OP_SET,
op.width = 8 and op.height = 1, then a TIOCL_SETSEL ioctl
4) echo 3 > /sys/devices/virtual/graphics/fbcon/rotate_all

[CAUSE]
The root cause is that fbcon_modechanged() first sets the current rotate's
corresponding ops. Subsequently, during vc_resize(), it may trigger
clear_selection(), and in fbcon_putcs->ccw_putcs[rotate=3], this can result
in an out-of-bounds access to "src". This happens because par- >rotated.buf
is reallocated in fbcon_rotate_font():
1) When rotate=2, its size is (width + 7) / 8 * height
2) When rotate=3, its size is (height + 7) / 8 * width

And the call to fbcon_rotate_font() occurs after clear_selection(). In
other words, the fontbuffer is allocated using the size calculated from the
previous rotation 2, but before reallocating it with the new size,
con_putcs is already using the new rotation 3:

rotate_all_store
  fbcon_rotate_all
   fbcon_set_all_vcs
    fbcon_modechanged
     set_blitting_type
     ...
      par->bitops = &ccw_fbcon_bitops
     vc_resize
     ...
      clear_selection
       highlight
       ...
        do_update_region
    fbcon_putcs
    ...
     image.dy = vyres - ((xx + count) * vc->vc_font.width) [1]  // overflow!
     ccw_putcs_aligned
      // old buf size is still being used during the read!
      src = par->rotated.buf + (scr_readw(s--) & charmask) * cellsize
      fb_pad_aligned_buffer----[src KASAN!!!]    [2]
      info->fbops->fb_imageblit(info, image)
       sys_imageblit
        fb_imageblit
         fb_address_forward
          // offset: image->dy * bits_per_line + image->dx * bpp
          unsigned int bits = (unsigned int)adr->bits + offset
          adr->address += (bits & ~(BITS_PER_LONG - 1u)) / BITS_PER_BYTE    [3]
         fb_bitmap_imageblit
         ...
          fb_read_offset    // page fault!    [4]
     update_screen
      redraw_screen
      ...
       ccw_cursor
        soft_cursor
         memcpy(src, image->data, dsize)----[src KASAN again!!!]    [5]
      fbcon_switch
       fbcon_rotate_font
        font_data_rotate
    dst = kmalloc_array(charcount, d_cellsize, GFP_KERNEL)
        // the new size is allocated only here!
        par->rotated.buf = buf    [6]

[FIX]
A fairly obvious approach is to follow fbcon_switch(): in
fbcon_modechanged(), call rotate_font() before vc_resize() so that a
correctly sized buffer is allocated in time, as done in [6]. This fix is
necessary, but it is not sufficient on its own.

In [1] it causes an image.dy overflow (ccw_putcs: vyres = 768,
image.dy = 4294967040), because vc_cols has not been updated in time at
this point (it is likewise only updated after clear_selection()). This
allows (xx + count) * width to exceed vyres, causing image.dy to overflow.
Subsequently, address in [3] is incremented by an even larger amount, which
triggers a page fault at [4].

Therefore, a second fix is required in combination with the first: move
clear_selection() earlier, before set_blitting_type() in
fbcon_set_all_vcs(), to prevent the out-of-bounds access. fbcon_rotate()
has a similar problem, so add the same clear there. Since vc_is_sel() is
not exported, the fbdev side is currently forced to call clear_selection()
unconditionally, causing the global selection to be cleared prematurely.
And this will not cause any other significant impact.

Signed-off-by: Zizhi Wo <wozizhi@xxxxxxxxxx>
---
v2:
Fixed the issue by calling clear_selection() earlier, and updated the
related description in the commit message.

v1: https://lore.kernel.org/all/20250905024340.337521-1- wozizhi@xxxxxxxxxxxxxxx/
---
  drivers/video/fbdev/core/fbcon.c | 9 +++++++++
  1 file changed, 9 insertions(+)

diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/ core/fbcon.c
index 9f5c4c101581..3361479d139d 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2639,13 +2639,20 @@ static void fbcon_modechanged(struct fb_info *info)
      vc = vc_cons[par->currcon].d;
      if (vc->vc_mode != KD_TEXT ||
          fbcon_info_from_console(par->currcon) != info)
          return;
+    clear_selection();
+
      p = &fb_display[vc->vc_num];
      set_blitting_type(vc, info);
+    if (par->bitops->rotate_font && par->bitops->rotate_font(info, vc)) {

Do you mind adding some comments before this rotate_font(), so that people
may understand why you rotate the font here?

Same for the two added clear_selection() calls.... A short comment which explains
why the selection is cleared.

Thank you!
Helge


Thanks for the review. I missed the comments here. I'll add them in the
next version :)

Thanks,
Zizhi Wo