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:
parent
a9d2e14004
commit
96389ada8f
7 changed files with 86 additions and 5 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 (;;)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue