From 96389ada8fcc9e558db2c58c63a6be3f19126ef5 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 25 Oct 2026 18:25:30 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX --- cmd/ldlsd/main.c | 3 +++ include/dev/vt.h | 10 +++++++++ kernel/dev/vt.c | 55 ++++++++++++++++++++++++++++++++++++++++++++--- ldls/kpi/kpi.c | 2 +- ldls/kpi/kpi.h | 1 + ldls/kpi/net.c | 9 ++++++++ ldls/kpi/string.c | 11 +++++++++- 7 files changed, 86 insertions(+), 5 deletions(-) diff --git a/cmd/ldlsd/main.c b/cmd/ldlsd/main.c index 6f6a8e9..78cacd5 100644 --- a/cmd/ldlsd/main.c +++ b/cmd/ldlsd/main.c @@ -472,6 +472,9 @@ kpi_layout_from_btf(struct btf *b) GET(nd, "dev_addr", kpi_l.nd_dev_addr); GET(nd, "perm_addr", kpi_l.nd_perm_addr); 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, "uc", kpi_l.nd_uc); GET(nd, "_tx", kpi_l.nd_tx); diff --git a/include/dev/vt.h b/include/dev/vt.h index f7b5bab..6e7d712 100644 --- a/include/dev/vt.h +++ b/include/dev/vt.h @@ -112,6 +112,16 @@ struct vt { int esc_parm[VT_MAXPARM]; int esc_nparm; 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. uint32 u8_cp; diff --git a/kernel/dev/vt.c b/kernel/dev/vt.c index 7fd077d..65131fd 100644 --- a/kernel/dev/vt.c +++ b/kernel/dev/vt.c @@ -235,11 +235,46 @@ vt_current(void) // 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 vt_repaint(struct vt *v) { if (!is_active(v)) 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 // strip right of the last column and below the last row belongs to nobody. @@ -263,6 +298,7 @@ vt_repaint(struct vt *v) else gfx_draw_cell(c, r, k->ch, vt_rgb(k->fg), vt_rgb(k->bg)); } + cursor_draw(v); } void @@ -906,7 +942,7 @@ vt_feed_1(struct vt *v, int c) switch (v->esc_state) { case VE_ESC: 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; 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. // Ending at the marker instead printed the rest -- tmux asking for // 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 >= '0' && c <= '9') { if (v->esc_nparm == 0) v->esc_nparm = 1; @@ -975,8 +1015,15 @@ vt_feed_1(struct vt *v, int c) return; } v->esc_state = VE_NORMAL; - if (!v->esc_private) + if (!v->esc_private) { 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; } @@ -1194,5 +1241,7 @@ vt_cycle(int dir) void vt_feed(struct vt *v, int c) { + cursor_erase(v); vt_feed_1(v, c); + cursor_draw(v); } diff --git a/ldls/kpi/kpi.c b/ldls/kpi/kpi.c index f1d9d0c..58c1680 100644 --- a/ldls/kpi/kpi.c +++ b/ldls/kpi/kpi.c @@ -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 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_fortify_panic(void); +extern void kpi_fortify_panic(unsigned int, unsigned long, unsigned long); const struct kpi_sym kpi_table[] = { // PCI diff --git a/ldls/kpi/kpi.h b/ldls/kpi/kpi.h index 0aaafe9..076a858 100644 --- a/ldls/kpi/kpi.h +++ b/ldls/kpi/kpi.h @@ -83,6 +83,7 @@ extern struct kpi_ctx kpi; struct kpi_layout { unsigned int nd_netdev_ops; 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_tx, nd_num_tx, nd_real_num_tx; unsigned int nq_size, nq_dev; diff --git a/ldls/kpi/net.c b/ldls/kpi/net.c index ae8b14c..7418f61 100644 --- a/ldls/kpi/net.c +++ b/ldls/kpi/net.c @@ -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"); } + // 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. // // A Linux list_head is empty when it points at itself; zeroed means a null diff --git a/ldls/kpi/string.c b/ldls/kpi/string.c index 09a81b9..973f7ad 100644 --- a/ldls/kpi/string.c +++ b/ldls/kpi/string.c @@ -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, // and the honest thing is to say so rather than return. 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 " "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 (;;) ; }