A text cursor in the tty; LDLS sets net_device.priv_len

The virtual terminals never drew a cursor. Each now paints a steady
block (the cell with its colours swapped), erased before and redrawn
after every output byte, redrawn by a repaint, and hidden by
ESC [ ? 25 l as curses programs ask.

r8169 under LDLS on the TUF A15 got through probe and died in ndo_open
on __fortify_panic. netdev_priv() is `dev->priv` now, a flexible array
counted by net_device.priv_len, and rtl_open's memset of its ring
bookkeeping is bounds-checked against it. The allocator left priv_len
zero, so the driver saw an area of no bytes. It is set from BTF when the
kernel has the member. __fortify_panic also says which check, how many
bytes it had and wanted, and where from.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX
This commit is contained in:
Nexoniarz 2026-10-25 18:25:30 +01:00
commit 96389ada8f
7 changed files with 86 additions and 5 deletions

View file

@ -472,6 +472,9 @@ kpi_layout_from_btf(struct btf *b)
GET(nd, "dev_addr", kpi_l.nd_dev_addr); GET(nd, "dev_addr", kpi_l.nd_dev_addr);
GET(nd, "perm_addr", kpi_l.nd_perm_addr); GET(nd, "perm_addr", kpi_l.nd_perm_addr);
GET(nd, "addr_len", kpi_l.nd_addr_len); GET(nd, "addr_len", kpi_l.nd_addr_len);
// Optional: older kernels do not have it, and 0 is how that is said.
if (btf_member_off(b, nd, "priv_len", &off, &size) == 0)
kpi_l.nd_priv_len = off;
GET(nd, "mc", kpi_l.nd_mc); GET(nd, "mc", kpi_l.nd_mc);
GET(nd, "uc", kpi_l.nd_uc); GET(nd, "uc", kpi_l.nd_uc);
GET(nd, "_tx", kpi_l.nd_tx); GET(nd, "_tx", kpi_l.nd_tx);

View file

@ -112,6 +112,16 @@ struct vt {
int esc_parm[VT_MAXPARM]; int esc_parm[VT_MAXPARM];
int esc_nparm; int esc_nparm;
int esc_private; int esc_private;
int esc_qmark; // the marker was '?': a DEC private mode
// The text cursor, a steady block. Drawn by painting its cell inverted over
// the grid, so the grid never holds it: curs_drawn says whether the screen
// currently does, and where, because erasing it means repainting that cell
// from the grid -- after the byte that moves the cursor has been consumed.
// DECTCEM (ESC [ ? 25 l) hides it; curses programs do before drawing.
int curs_hidden;
int curs_drawn;
int curs_x, curs_y;
// UTF-8 decoder. // UTF-8 decoder.
uint32 u8_cp; uint32 u8_cp;

View file

@ -235,11 +235,46 @@ vt_current(void)
// Output // Output
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// The text cursor. See struct vt. Drawn as the cell with its colours swapped
// -- relative to what the cell would show, so a cursor on reverse-video text
// is the plain text and every cursor on screen is visible against its cell.
static void
cursor_erase(struct vt *v)
{
if (!v->curs_drawn)
return;
v->curs_drawn = 0;
if (v->curs_x < v->cols && v->curs_y < v->rows)
show(v, v->curs_x, v->curs_y);
}
static void
cursor_draw(struct vt *v)
{
if (v->curs_hidden || !is_active(v))
return;
// A glyph in the last column leaves the cursor *on* it with the wrap
// pending, and that is where it is drawn.
int col = v->cx >= v->cols ? v->cols - 1 : v->cx;
int row = v->cy >= v->rows ? v->rows - 1 : v->cy;
if (col < 0 || row < 0)
return;
struct vtcell *k = cell_at(v, col, row);
if (k->attr & VT_ATTR_REVERSE)
gfx_draw_cell(col, row, k->ch, vt_rgb(k->fg), vt_rgb(k->bg));
else
gfx_draw_cell(col, row, k->ch, vt_rgb(k->bg), vt_rgb(k->fg));
v->curs_x = col;
v->curs_y = row;
v->curs_drawn = 1;
}
void void
vt_repaint(struct vt *v) vt_repaint(struct vt *v)
{ {
if (!is_active(v)) if (!is_active(v))
return; return;
v->curs_drawn = 0; // the repaint below paints over it
// The margins first: the screen is rarely a whole number of cells, and the // The margins first: the screen is rarely a whole number of cells, and the
// strip right of the last column and below the last row belongs to nobody. // strip right of the last column and below the last row belongs to nobody.
@ -263,6 +298,7 @@ vt_repaint(struct vt *v)
else else
gfx_draw_cell(c, r, k->ch, vt_rgb(k->fg), vt_rgb(k->bg)); gfx_draw_cell(c, r, k->ch, vt_rgb(k->fg), vt_rgb(k->bg));
} }
cursor_draw(v);
} }
void void
@ -906,7 +942,7 @@ vt_feed_1(struct vt *v, int c)
switch (v->esc_state) { switch (v->esc_state) {
case VE_ESC: case VE_ESC:
if (c == '[') { v->esc_state = VE_CSI; if (c == '[') { v->esc_state = VE_CSI;
v->esc_nparm = 0; v->esc_private = 0; v->esc_nparm = 0; v->esc_private = 0; v->esc_qmark = 0;
for (int i = 0; i < VT_MAXPARM; i++) v->esc_parm[i] = 0; for (int i = 0; i < VT_MAXPARM; i++) v->esc_parm[i] = 0;
return; } return; }
if (c == ']') { v->esc_state = VE_OSC; return; } if (c == ']') { v->esc_state = VE_OSC; return; }
@ -962,7 +998,11 @@ vt_feed_1(struct vt *v, int c)
// this terminal does not speak, but must still consume to the final byte. // this terminal does not speak, but must still consume to the final byte.
// Ending at the marker instead printed the rest -- tmux asking for // Ending at the marker instead printed the rest -- tmux asking for
// extended keys as ESC [ > 4 ; 1 m left "4;1m" on the screen. // extended keys as ESC [ > 4 ; 1 m left "4;1m" on the screen.
if (c >= '<' && c <= '?') { v->esc_private = 1; return; } if (c >= '<' && c <= '?') {
v->esc_private = 1;
if (c == '?') v->esc_qmark = 1;
return;
}
if (c >= 0x20 && c <= 0x2F) { v->esc_private = 1; return; } if (c >= 0x20 && c <= 0x2F) { v->esc_private = 1; return; }
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
if (v->esc_nparm == 0) v->esc_nparm = 1; if (v->esc_nparm == 0) v->esc_nparm = 1;
@ -975,8 +1015,15 @@ vt_feed_1(struct vt *v, int c)
return; return;
} }
v->esc_state = VE_NORMAL; v->esc_state = VE_NORMAL;
if (!v->esc_private) if (!v->esc_private) {
vt_csi(v, c); vt_csi(v, c);
} else if (v->esc_qmark && (c == 'h' || c == 'l')) {
// DECSET/DECRST. Only the cursor's visibility is ours; the rest of
// ?1049 and its kin are still ignored, as before.
for (int i = 0; i < v->esc_nparm && i < VT_MAXPARM; i++)
if (v->esc_parm[i] == 25)
v->curs_hidden = (c == 'l');
}
return; return;
} }
@ -1194,5 +1241,7 @@ vt_cycle(int dir)
void void
vt_feed(struct vt *v, int c) vt_feed(struct vt *v, int c)
{ {
cursor_erase(v);
vt_feed_1(v, c); vt_feed_1(v, c);
cursor_draw(v);
} }

View file

@ -591,7 +591,7 @@ extern unsigned long kpi_strnlen(const char *s, unsigned long max);
extern long kpi_sized_strscpy(char *d, const char *s, unsigned long n); extern long kpi_sized_strscpy(char *d, const char *s, unsigned long n);
extern void kpi_memcpy_fromio(void *d, const void *s, unsigned long n); extern void kpi_memcpy_fromio(void *d, const void *s, unsigned long n);
extern void kpi_memcpy_toio(void *d, const void *s, unsigned long n); extern void kpi_memcpy_toio(void *d, const void *s, unsigned long n);
extern void kpi_fortify_panic(void); extern void kpi_fortify_panic(unsigned int, unsigned long, unsigned long);
const struct kpi_sym kpi_table[] = { const struct kpi_sym kpi_table[] = {
// PCI // PCI

View file

@ -83,6 +83,7 @@ extern struct kpi_ctx kpi;
struct kpi_layout { struct kpi_layout {
unsigned int nd_netdev_ops; unsigned int nd_netdev_ops;
unsigned int nd_dev_addr, nd_perm_addr, nd_addr_len; unsigned int nd_dev_addr, nd_perm_addr, nd_addr_len;
unsigned int nd_priv_len; // 0 when this kernel has none (before 6.17)
unsigned int nd_mc, nd_uc; unsigned int nd_mc, nd_uc;
unsigned int nd_tx, nd_num_tx, nd_real_num_tx; unsigned int nd_tx, nd_num_tx, nd_real_num_tx;
unsigned int nq_size, nq_dev; unsigned int nq_size, nq_dev;

View file

@ -79,6 +79,15 @@ kpi_alloc_etherdev_mqs(int priv, unsigned int txqs, unsigned int rxqs)
kpi_log("kpi: no net_device.dev_addr offset -- the driver will fault\n"); kpi_log("kpi: no net_device.dev_addr offset -- the driver will fault\n");
} }
// How big the private area is, which a driver's own bounds checks read:
// netdev_priv() is `dev->priv`, a flexible array counted by `priv_len`, so
// fortify sees the area as exactly that long. Zero, as left by mmap, made
// r8169's memset of its descriptor-ring bookkeeping -- tp->tx_skb, 4K at
// offset 0xa48 -- look like writing past the end of nothing, and it called
// __fortify_panic in rtl_open.
if (kpi_l.nd_priv_len)
*(unsigned int *)((unsigned char *)p + kpi_l.nd_priv_len) = (unsigned int)priv;
// The address lists have to be *empty*, not zeroed. // The address lists have to be *empty*, not zeroed.
// //
// A Linux list_head is empty when it points at itself; zeroed means a null // A Linux list_head is empty when it points at itself; zeroed means a null

View file

@ -136,10 +136,19 @@ kpi_memcpy_toio(void *dst, const void *s, unsigned long n)
// they detect an overflow. Reaching it means the driver found a bug in itself, // they detect an overflow. Reaching it means the driver found a bug in itself,
// and the honest thing is to say so rather than return. // and the honest thing is to say so rather than return.
void void
kpi_fortify_panic(void) kpi_fortify_panic(unsigned int reason, unsigned long avail, unsigned long size)
{ {
kpi_log("kpi: the driver called __fortify_panic -- it detected an " kpi_log("kpi: the driver called __fortify_panic -- it detected an "
"overflow in itself\n"); "overflow in itself\n");
// Which check, how much it thought it had, how much it wanted, and where
// from -- the address is in the driver's own text, so subtract the load
// address logged for it and look the offset up with nm. This took a
// hardware round trip to find the last time it said nothing.
kpi_log(" reason "); kpi_lognum(reason);
kpi_log(", available "); kpi_lognum((long long)avail);
kpi_log(", wanted "); kpi_lognum((long long)size);
kpi_log(", from 0x"); kpi_loghex((unsigned long)__builtin_return_address(0), 16);
kpi_log("\n");
for (;;) for (;;)
; ;
} }