Public bug reported:
libtermkey's driver-csi.c has two out-of-bounds memory accesses reachable from
termkey_getkey() on well-formed CSI input. Both are one-line fixes. Both were
fixed in Neovim's vendored copy of this code, in May 2025 and June 2026, but
never made it back to upstream libtermkey, and Ubuntu ships the unfixed code in
every release.
Full analysis, evidence and the two changes as separate patches are in the
Debian
report I filed against the same source package:
https://bugs.debian.org/1142891
This report is the Ubuntu-specific part; there is no need to read both.
1) Release
$ lsb_release -rd
Description: Ubuntu 24.04.4 LTS
Release: 24.04
2) Package versions
$ apt-cache policy libtermkey1
libtermkey1:
Installed: 0.22-1
Candidate: 0.22-1
$ apt-cache policy neovim
neovim:
Installed: 0.9.5-6ubuntu2
Candidate: 0.9.5-6ubuntu2
3) What I expected to happen
Feeding an application a valid CSI escape sequence should produce either a key
event or nothing. A terminal input parser should not have undefined behaviour on
input a terminal can legitimately produce.
4) What happened instead
An unmodified archive install of neovim segfaults on five bytes of
input:
$ ldd /usr/bin/nvim | grep termkey
libtermkey.so.1 => /lib/x86_64-linux-gnu/libtermkey.so.1
$ printf '\033[1 A' | script -qec "/usr/bin/nvim -u NONE -i NONE" /dev/null
$ echo $?
139
139 is SIGSEGV. A bare ESC through the identical harness exits normally, so the
crash is specific to the sequence and not to the test method.
That sequence is ESC [ 1 SP A -- valid ECMA-48, xterm's SR (Shift right). It is
not malformed garbage.
AFFECTED RELEASES
libtermkey is unpatched in every Ubuntu release (all in universe; the only
Debian
patch is a makefile change that does not touch driver-csi.c):
xenial 0.18-1
bionic 0.20-3
focal 0.22-1
jammy 0.22-1
noble 0.22-1
plucky 0.22-2
questing 0.22-2
resolute 0.22-2build1
stonking 0.22-2build1
I built and ran the reproducers against upstream 0.18, 0.19, 0.20, 0.21 and
0.22;
all five have both defects, so every version above is affected. 0.18 is simply
the oldest I tested, not necessarily where the bugs were introduced.
EXPOSURE
Both current LTS releases ship a neovim that links the affected library:
jammy neovim 0.6.1-3 depends on libtermkey1
noble neovim 0.9.5-6ubuntu2 depends on libtermkey1 <- crash shown
above
Newer releases are not exposed via neovim -- it dropped the libtermkey
dependency
and now uses its own vendored (already fixed) copy:
questing neovim 0.10.4-8build2 no libtermkey dependency
resolute neovim 0.11.6-1 no libtermkey dependency
Other packages depending on libtermkey1, and still exposed everywhere: vis,
libtickit3t64, libterm-termkey-perl.
To be clear about what I verified: I demonstrated the crash on noble. jammy's
neovim links the same affected library so it should behave the same way, but I
did not run it there.
This is why the bug is worth filing here as well as in Debian: a Debian fix
reaches neither of the two released LTSes.
THE TWO DEFECTS
1. Out-of-bounds READ in handle_csi_ss3_full().
peekkey_csi() masks when it selects a handler but passes the unmasked cmd to it,
and cmd carries the CSI initial byte in bits 8-15 and the intermediate byte in
bits 16-23. handle_csi_ss3_full() then indexes a 64-element array with the
unmasked value:
key->type = csi_ss3s[cmd - 0x40].type;
For ESC [ 1 SP A that is index 2097153, about 32 MB past the array.
UBSan:
driver-csi.c:34:15: runtime error: index 2097153 out of bounds for
type 'struct keyinfo[64]'
An intermediate byte is not required -- an initial byte alone is enough, e.g.
ESC [ ? A (index 16129). That matters because CSI sequences beginning '?' are
ordinary terminal replies, so an application that queries the terminal can be
crashed by the answer.
Fixed by the first hunk of the attached patch -- the guard Neovim added as
neovim/neovim#40296.
2. Out-of-bounds WRITE in parse_csi().
peekkey_csi() passes a 16-element array and parse_csi() bounds it one slot too
late ("if(argi > 16)"), so args[16] is written -- eight bytes past the end of
the
caller's stack array, with a value taken from the input. Triggered by any CSI
sequence with 17 or more parameters. ASan:
ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 8 #0 parse_csi driver-csi.c:368
[96, 224) 'arg' (line 533) <== Memory access at offset 224 overflows this
variable
This one does not crash the packaged library -- the write lands in stack padding
-- and valgrind cannot see it, since it stays inside one stack frame. Only ASan
observes it. It does bite in the field: neovim/neovim#24356 reports
"*** stack smashing detected ***" at startup under the RLogin terminal, which
answers the "CSI c" query with 17 parameters.
Fixed by the second hunk of the attached patch -- the change Neovim made as
neovim/neovim#33868.
IMPACT
Denial of service for both. I have deliberately not ticked "this bug is a
security vulnerability" and I am not requesting a CVE; I would rather understate
this than overstate it:
- Defect 1 is a read. The values land in key->type / key->code.sym and are not
returned to the attacker, so it is not a useful information disclosure.
- Defect 2 is a write, but it is a single 8-byte slot immediately past the
array,
it cannot walk further, and on a hardened build the stack canary sits between
it and the return address. The realistic outcome is a controlled abort. I was
not able to make it do anything more.
On reachability: libtermkey parses application input, so bytes the terminal
merely displays do not reach it. The paths that do are the terminal's replies to
queries the application makes (how both defects were originally hit), pasted
text, and injection into the tty input queue. The likely victim is a user with
an
unusual terminal rather than a targeted one -- which is an argument for treating
this as a robustness bug rather than a security one. Please re-classify if you
disagree.
FIX
The attached patch carries both changes as two hunks and applies cleanly to 0.22
as shipped, in one step. Upstream's own test suite passes identically with and
without it: 16 test files, 430 assertions, 0 failures either way. Both
reproducers exit cleanly once it is applied, including under ASan.
The Debian bug has the two changes as separate patches if that is preferred for
packaging. They are the same two hunks; I combined them here because this form
takes a single attachment.
The changes are a bounds check and a comparison operator. Both have been running
in Neovim's tree since 2026-06 and 2025-05 respectively.
** Affects: libtermkey (Ubuntu)
Importance: Undecided
Status: New
** Affects: libtermkey (Debian)
Importance: Unknown
Status: Unknown
** Tags: patch
** Patch added: "This should fix both bugs."
https://bugs.launchpad.net/bugs/2161916/+attachment/5987087/+files/fix-both.patch
** Bug watch added: Debian Bug tracker #1142891
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1142891
** Also affects: libtermkey (Debian) via
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1142891
Importance: Unknown
Status: Unknown
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2161916
Title:
libtermkey: two out-of-bounds accesses in driver-csi.c (nvim segfaults
on 5 bytes of terminal input)
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/libtermkey/+bug/2161916/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs