Fix crash in overlay line drawing on uninitialized linebuf view
screen_draw_overlay_line accessed self->linebuf->line->cpu_cells without ever calling linebuf_init_line on the shared view. Render paths that initialize a stack-local Line via render_line_for_virtual_y left the view's cpu_cells as NULL (the value set by alloc_line via PyType_GenericAlloc), and the multicell-trim loop then dereferenced NULL + xstart * sizeof(CPUCell), producing a SIGSEGV at a small address (e.g. 0x1e for xstart=2). The crash was reachable any time an IME pre-edit overlay was rendered with the cursor not in column 0 on a screen whose linebuf->line had not been re-pointed by some unrelated prior call. Fix by initializing the view at the overlay row on entry. Add a test_draw_overlay_line method on Screen so the behavior can be exercised directly from a regression test.
This commit is contained in:
parent
9fd83e7cfb
commit
875ca70a55
2 changed files with 38 additions and 0 deletions
|
|
@ -4283,6 +4283,11 @@ screen_update_overlay_text(Screen *self, const char *utf8_text) {
|
||||||
static void
|
static void
|
||||||
screen_draw_overlay_line(Screen *self) {
|
screen_draw_overlay_line(Screen *self) {
|
||||||
if (!self->overlay_line.overlay_text) return;
|
if (!self->overlay_line.overlay_text) return;
|
||||||
|
// self->linebuf->line is a shared view that callers may not have pointed
|
||||||
|
// at the overlay row (e.g. render_line_for_virtual_y inits a stack-local
|
||||||
|
// Line instead). Without this, line->cpu_cells can be NULL or stale,
|
||||||
|
// crashing the cell loops below.
|
||||||
|
linebuf_init_line(self->linebuf, self->overlay_line.ynum);
|
||||||
// Right-align the overlay to ensure that the pre-edit text just entered is visible when the cursor is near the end of the line.
|
// Right-align the overlay to ensure that the pre-edit text just entered is visible when the cursor is near the end of the line.
|
||||||
index_type xstart = self->overlay_line.text_len <= self->columns ? self->columns - self->overlay_line.text_len : 0;
|
index_type xstart = self->overlay_line.text_len <= self->columns ? self->columns - self->overlay_line.text_len : 0;
|
||||||
if (self->overlay_line.xstart < xstart) xstart = self->overlay_line.xstart;
|
if (self->overlay_line.xstart < xstart) xstart = self->overlay_line.xstart;
|
||||||
|
|
@ -6066,6 +6071,26 @@ test_create_write_buffer(Screen *screen UNUSED, PyObject *args UNUSED) {
|
||||||
return PyMemoryView_FromMemory((char*)buf, s, PyBUF_WRITE);
|
return PyMemoryView_FromMemory((char*)buf, s, PyBUF_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
test_draw_overlay_line(Screen *self, PyObject *args) {
|
||||||
|
PyObject *text;
|
||||||
|
unsigned int xstart, ynum;
|
||||||
|
if (!PyArg_ParseTuple(args, "UII", &text, &xstart, &ynum)) return NULL;
|
||||||
|
if (ynum >= self->lines || xstart >= self->columns) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "ynum or xstart out of range");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_INCREF(text);
|
||||||
|
Py_XDECREF(self->overlay_line.overlay_text);
|
||||||
|
self->overlay_line.overlay_text = text;
|
||||||
|
self->overlay_line.text_len = (index_type)PyUnicode_GET_LENGTH(text);
|
||||||
|
self->overlay_line.xstart = xstart;
|
||||||
|
self->overlay_line.ynum = ynum;
|
||||||
|
self->overlay_line.is_active = true;
|
||||||
|
screen_draw_overlay_line(self);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
test_commit_write_buffer(Screen *screen, PyObject *args) {
|
test_commit_write_buffer(Screen *screen, PyObject *args) {
|
||||||
RAII_PY_BUFFER(srcbuf); RAII_PY_BUFFER(destbuf);
|
RAII_PY_BUFFER(srcbuf); RAII_PY_BUFFER(destbuf);
|
||||||
|
|
@ -6153,6 +6178,7 @@ static PyMethodDef methods[] = {
|
||||||
METHODB(test_create_write_buffer, METH_NOARGS),
|
METHODB(test_create_write_buffer, METH_NOARGS),
|
||||||
METHODB(test_commit_write_buffer, METH_VARARGS),
|
METHODB(test_commit_write_buffer, METH_VARARGS),
|
||||||
METHODB(test_parse_written_data, METH_VARARGS),
|
METHODB(test_parse_written_data, METH_VARARGS),
|
||||||
|
METHODB(test_draw_overlay_line, METH_VARARGS),
|
||||||
MND(line_edge_colors, METH_NOARGS)
|
MND(line_edge_colors, METH_NOARGS)
|
||||||
MND(line, METH_O)
|
MND(line, METH_O)
|
||||||
MND(dump_lines_with_attrs, METH_VARARGS)
|
MND(dump_lines_with_attrs, METH_VARARGS)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,18 @@ class TestMulticell(BaseTest):
|
||||||
def test_multicell(self):
|
def test_multicell(self):
|
||||||
test_multicell(self)
|
test_multicell(self)
|
||||||
|
|
||||||
|
def test_overlay_line_does_not_crash_on_uninit_linebuf_view(self):
|
||||||
|
# Regression: screen_draw_overlay_line accessed self->linebuf->line->cpu_cells
|
||||||
|
# without ever calling linebuf_init_line, so on render paths that
|
||||||
|
# initialize a stack-local Line (render_line_for_virtual_y) the shared
|
||||||
|
# view's cpu_cells stayed NULL and the multicell-trim loop dereferenced
|
||||||
|
# NULL + xstart * sizeof(CPUCell). Reproduces by placing a wide cell at
|
||||||
|
# xstart>0 and triggering the overlay draw directly.
|
||||||
|
for xstart in (1, 2, 3):
|
||||||
|
s = self.create_screen(cols=10, lines=5)
|
||||||
|
s.draw('好好') # two 2-cell wide chars covering columns 0..3
|
||||||
|
s.test_draw_overlay_line('xy', xstart, 0) # would SIGSEGV without fix
|
||||||
|
|
||||||
|
|
||||||
def test_multicell(self: TestMulticell) -> None:
|
def test_multicell(self: TestMulticell) -> None:
|
||||||
from kitty.tab_bar import as_rgb
|
from kitty.tab_bar import as_rgb
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue