Merge tag 'fbdev-for-7.2-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/linux-fbdev

Pull fbdev fixes from Helge Deller:
 "A few patches for the core fbdev layer which stabilize or fix
  potential issues with text font rendering after screen rotation or
  after user initiated font changes and locking fixes for sysfb during
  modifications of the graphics mode database"

* tag 'fbdev-for-7.2-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/linux-fbdev:
  fbdev: bitblit: bound-check glyph index in bit_cursor()
  fbdev: Fix out-of-bounds access when rotating console after font resize
  fbdev: core: Fix pointer desynchronization in fb_io_read()
  fbdev: serialize mode sysfs access with lock_fb_info()
  fbdev: clear fb_info->mode before deleting a videomode
  fbdev: bound mode sysfs output to the sysfs buffer
This commit is contained in:
Linus Torvalds
2026-08-08 07:47:52 -07:00
5 changed files with 91 additions and 15 deletions

View File

@@ -273,9 +273,14 @@ static void bit_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
if (!vc->vc_font.data)
return;
c = scr_readw((u16 *) vc->vc_pos);
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height));
c &= charmask;
/* Clamp to font size, same as bit_putcs_aligned() */
if (c >= vc->vc_font.charcount)
c = 0;
src = vc->vc_font.data + (c * (w * vc->vc_font.height));
if (par->cursor_state.image.data != (const char *)src ||
par->cursor_reset) {

View File

@@ -61,6 +61,14 @@ ssize_t fb_io_read(struct fb_info *info, char __user *buf, size_t count, loff_t
buf += c;
cnt += c;
count -= c;
/*
* If there was a partial copy, the user buffer is faulty.
* Break out to avoid over-advancing the src pointer and
* reading out of bounds in the next iteration.
*/
if (trailing)
break;
}
kfree(buffer);

View File

@@ -2641,9 +2641,31 @@ static void fbcon_modechanged(struct fb_info *info)
fbcon_info_from_console(par->currcon) != info)
return;
/*
* Clear the selection before switching bitops. Without this, the
* clear_selection() inside vc_resize() below repaints the highlighted
* cells through the new bitops while the console geometry(vc_rows/vc_cols)
* has not been updated to match, so the repaint is computed from a
* half-switched geometry and overflows the framebuffer address.
* Pre-clearing makes that repaint a no-op.
*/
clear_selection();
p = &fb_display[vc->vc_num];
set_blitting_type(vc, info);
/*
* Rebuild par->rotated.buf for the new rotation now that bitops have
* switched. The new putcs/cursor ops read this buffer; if it is still
* sized for the old rotation, fbcon_putcs() and the cursor path reached
* via update_screen() below overflow it. Mirrors fbcon_switch(); fall
* back to unrotated rendering on allocation failure.
*/
if (par->bitops->rotate_font && par->bitops->rotate_font(info, vc)) {
par->rotate = FB_ROTATE_UR;
set_blitting_type(vc, info);
}
if (con_is_visible(vc)) {
var_to_display(p, &info->var, info);
cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
@@ -2675,6 +2697,9 @@ static void fbcon_set_all_vcs(struct fb_info *info)
if (!par || par->currcon < 0)
return;
/* See the comment in fbcon_modechanged(). */
clear_selection();
for (i = first_fb_vc; i <= last_fb_vc; i++) {
vc = vc_cons[i].d;
if (!vc || vc->vc_mode != KD_TEXT ||

View File

@@ -246,8 +246,11 @@ fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
ret = fb_mode_is_equal(&mode1, &mode2);
if (!ret) {
ret = fbcon_mode_deleted(info, &mode1);
if (!ret)
if (!ret) {
if (info->mode && fb_mode_is_equal(info->mode, &mode1))
info->mode = NULL;
fb_delete_videomode(&mode1, &info->modelist);
}
}
return ret ? -EINVAL : 0;

View File

@@ -12,27 +12,35 @@
#include "fb_internal.h"
#include "fbcon.h"
static int activate_locked(struct fb_info *fb_info,
struct fb_var_screeninfo *var)
{
var->activate |= FB_ACTIVATE_FORCE;
return fb_set_var_from_user(fb_info, var);
}
static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var)
{
int err;
var->activate |= FB_ACTIVATE_FORCE;
console_lock();
lock_fb_info(fb_info);
err = fb_set_var_from_user(fb_info, var);
err = activate_locked(fb_info, var);
unlock_fb_info(fb_info);
console_unlock();
if (err)
return err;
return 0;
return err;
}
static int mode_string(char *buf, unsigned int offset,
static int mode_string(char *buf, size_t size, unsigned int offset,
const struct fb_videomode *mode)
{
char m = 'U';
char v = 'p';
if (offset >= size)
return 0;
if (mode->flag & FB_MODE_IS_DETAILED)
m = 'D';
if (mode->flag & FB_MODE_IS_VESA)
@@ -45,7 +53,7 @@ static int mode_string(char *buf, unsigned int offset,
if (mode->vmode & FB_VMODE_DOUBLE)
v = 'd';
return snprintf(&buf[offset], PAGE_SIZE - offset, "%c:%dx%d%c-%d\n",
return scnprintf(&buf[offset], size - offset, "%c:%dx%d%c-%d\n",
m, mode->xres, mode->yres, v, mode->refresh);
}
@@ -62,19 +70,32 @@ static ssize_t store_mode(struct device *device, struct device_attribute *attr,
memset(&var, 0, sizeof(var));
console_lock();
lock_fb_info(fb_info);
list_for_each_entry(modelist, &fb_info->modelist, list) {
mode = &modelist->mode;
i = mode_string(mstr, 0, mode);
i = mode_string(mstr, sizeof(mstr), 0, mode);
if (strncmp(mstr, buf, max(count, i)) == 0) {
var = fb_info->var;
fb_videomode_to_var(&var, mode);
if ((err = activate(fb_info, &var)))
err = activate_locked(fb_info, &var);
if (err) {
unlock_fb_info(fb_info);
console_unlock();
return err;
}
fb_info->mode = mode;
unlock_fb_info(fb_info);
console_unlock();
return count;
}
}
unlock_fb_info(fb_info);
console_unlock();
return -EINVAL;
}
@@ -82,11 +103,20 @@ static ssize_t show_mode(struct device *device, struct device_attribute *attr,
char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
struct fb_videomode mode;
bool have_mode = false;
if (!fb_info->mode)
lock_fb_info(fb_info);
if (fb_info->mode) {
mode = *fb_info->mode;
have_mode = true;
}
unlock_fb_info(fb_info);
if (!have_mode)
return 0;
return mode_string(buf, 0, fb_info->mode);
return mode_string(buf, PAGE_SIZE, 0, &mode);
}
static ssize_t store_modes(struct device *device,
@@ -134,10 +164,15 @@ static ssize_t show_modes(struct device *device, struct device_attribute *attr,
const struct fb_videomode *mode;
i = 0;
lock_fb_info(fb_info);
list_for_each_entry(modelist, &fb_info->modelist, list) {
mode = &modelist->mode;
i += mode_string(buf, i, mode);
i += mode_string(buf, PAGE_SIZE, i, mode);
if (i >= PAGE_SIZE - 1)
break;
}
unlock_fb_info(fb_info);
return i;
}