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

Reply via email to