ANT-2026-3SEPW083 · ncurses

stack-buffer-overflow high

Severity Claude high · Security research firm high · Maintainer -

Discovered by an unreleased Anthropic model

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.

ANT-2026-3SEPW083: Stack buffer overflow in SGR mouse response parser

In ncurses' SGR-1006 mouse parser (lib_mouse.c), read_SGR() stores parsed parameters into result->params[MAX_PARAMS] (MAX_PARAMS=9) but advances the index now on every semicolon and digit group with no check against MAX_PARAMS; only the 80-byte kbuf limits input length, permitting now to reach ~76. A sequence like ESC [ < ;;;...;1234567890M therefore writes to params[60+] on the stack of decode_xterm_SGR1006(), roughly 200+ bytes past the 36-byte array and over the saved frame pointer and return address. An attacker controlling the terminal/pty master chooses the written integer values digit-by-digit. SGR-1006 mode is auto-enabled when the terminfo XM capability contains '1006', which is the case for xterm-1006/1002/1003, tmux, alacritty, and kitty entries shipped in terminfo.src, so any curses application calling mousemask() under these TERMs is reachable.

Target

Project: ncurses
Version: commit 87c2c84cbd2332d6d94b12a1dcaf12ad1a51a938 (present at current HEAD)
Location: ncurses/base/lib_mouse.c:1249

Technical Details

Built a minimal curses program (initscr/keypad/mousemask/getch) under TERM=mouse1006 (XM contains '1006', kmous=\E[<). Fed stdin '\E[<;;;;...(60x);1234567890M'. read_SGR() at lib_mouse.c:1249 increments 'now' once per semicolon with no MAX_PARAMS bound, then writes result->params[60]. UBSAN reports 'index 60 out of bounds for type int[9]' at lib_mouse.c:1249. The SGR_DATA struct (48 bytes) lives on decode_xterm_SGR1006's stack; params[60] is ~200 bytes past it, covering saved registers/return address.

Crash trace (truncated — full trace in attached crash.log):

../ncurses/./base/lib_mouse.c:1249:29: runtime error: index 60 out of bounds for type 'int[9]'
==12==WARNING: invalid path to external symbolizer!
==12==WARNING: Failed to use and restart external symbolizer!
    #0 0xaaaab9ae65e4  (/out/mdrv+0x1e65e4) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #1 0xaaaab9adccb4  (/out/mdrv+0x1dccb4) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #2 0xaaaab9ae04c0  (/out/mdrv+0x1e04c0) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #3 0xaaaab9adb8f0  (/out/mdrv+0x1db8f0) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #4 0xffffa14d2258  (/lib/aarch64-linux-gnu/libc.so.6+0x22258) (BuildId: 45918bc10b33fd96afc550c98de062dccdf44328)
    #5 0xffffa14d2338  (/lib/aarch64-linux-gnu/libc.so.6+0x22338) (BuildId: 45918bc10b33fd96afc550c98de062dccdf44328)
    #6 0xaaaab99f96ac  (/out/mdrv+0xf96ac) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../ncurses/./base/lib_mouse.c:1249:29 
../ncurses/./base/lib_mouse.c:1249:6: runtime error: index 60 out of bounds for type 'int[9]'
    #0 0xaaaab9ae680c  (/out/mdrv+0x1e680c) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #1 0xaaaab9adccb4  (/out/mdrv+0x1dccb4) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #2 0xaaaab9ae04c0  (/out/mdrv+0x1e04c0) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #3 0xaaaab9adb8f0  (/out/mdrv+0x1db8f0) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)
    #4 0xffffa14d2258  (/lib/aarch64-linux-gnu/libc.so.6+0x22258) (BuildId: 45918bc10b33fd96afc550c98de062dccdf44328)
    #5 0xffffa14d2338  (/lib/aarch64-linux-gnu/libc.so.6+0x22338) (BuildId: 45918bc10b33fd96afc550c98de062dccdf44328)
    #6 0xaaaab99f96ac  (/out/mdrv+0xf96ac) (BuildId: 3f13f46ce1c3e987ff5057949530b6e62732f84f)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../ncurses/./base/lib_mouse.c:1249:6

Reproduction

  1. Create a pty pair and spawn the target curses program on the slave with TERM=xterm-1006.
  2. Let the program initialize curses and call mousemask(), which enables SGR-1006 mouse mode via the XM capability.
  3. From the pty master, write: \x1b[< followed by ~60-70 semicolons, then attacker-chosen digits, then 'M' (e.g., \x1b[<;;;;;...;1234567890M).
  4. When the program calls getch()/wgetch(), input dispatch recognizes CSI-< and calls decode_xterm_SGR1006() -> read_SGR().
  5. Each semicolon increments now past MAX_PARAMS; the trailing digits write controlled ints into params[now] far past the 9-int array, overwriting saved RBP/return address.
  6. On function return, control transfers to attacker-chosen address / ROP chain.

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-3SEPW083.


Reference: ANT-2026-3SEPW083
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Ada Logics.

Verdict
true positive
Severity
high
TIMELINE

Dates from discovery through public reveal.

  1. 2026-04-08 Reported to tracker
  2. 2026-04-18 Sent to maintainer
  3. 2026-04-18 Patch released
  4. 2026-05-13 Maintainer acknowledged
  5. 2026-08-17 Publicly revealed
PROVENANCE

SHA-3-512 hash:

6c64a707f8b3af9b99108c07e688e3a615414a3ba738c64097b2dbaa5b1ab401d8d2c4e1bb5cf1045da255c67374f598d15de102a8a60cc1e168bed82840059d

Committed 2026-04-23 00:04 PT

Revealed 2026-08-17 10:47 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-3SEPW083",
  "bug_class": "Stack-buffer-overflow",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-04-09T05:34:53+00:00",
  "description": "In ncurses' SGR-1006 mouse parser (lib_mouse.c), read_SGR() stores parsed parameters into result->params[MAX_PARAMS] (MAX_PARAMS=9) but advances the index `now` on every semicolon and digit group with no check against MAX_PARAMS; only the 80-byte kbuf limits input length, permitting `now` to reach ~76. A sequence like ESC [ < ;;;...;1234567890M therefore writes to params[60+] on the stack of decode_xterm_SGR1006(), roughly 200+ bytes past the 36-byte array and over the saved frame pointer and return address. An attacker controlling the terminal/pty master chooses the written integer values digit-by-digit. SGR-1006 mode is auto-enabled when the terminfo XM capability contains '1006', which is the case for xterm-1006/1002/1003, tmux, alacritty, and kitty entries shipped in terminfo.src, so any curses application calling mousemask() under these TERMs is reachable.",
  "discovered_at": null,
  "location": "ncurses/base/lib_mouse.c:1249",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "ncurses",
  "reproduction": [
    "1. Create a pty pair and spawn the target curses program on the slave with TERM=xterm-1006.",
    "2. Let the program initialize curses and call mousemask(), which enables SGR-1006 mouse mode via the XM capability.",
    "3. From the pty master, write: \\x1b[< followed by ~60-70 semicolons, then attacker-chosen digits, then 'M' (e.g., \\x1b[<;;;;;...;1234567890M).",
    "4. When the program calls getch()/wgetch(), input dispatch recognizes CSI-< and calls decode_xterm_SGR1006() -> read_SGR().",
    "5. Each semicolon increments `now` past MAX_PARAMS; the trailing digits write controlled ints into params[now] far past the 9-int array, overwriting saved RBP/return address.",
    "6. On function return, control transfers to attacker-chosen address / ROP chain."
  ],
  "technical_details": "Built a minimal curses program (initscr/keypad/mousemask/getch) under TERM=mouse1006 (XM contains '1006', kmous=\\\\E[<). Fed stdin '\\\\E[<;;;;...(60x);1234567890M'. read_SGR() at lib_mouse.c:1249 increments 'now' once per semicolon with no MAX_PARAMS bound, then writes result->params[60]. UBSAN reports 'index 60 out of bounds for type int[9]' at lib_mouse.c:1249. The SGR_DATA struct (48 bytes) lives on decode_xterm_SGR1006's stack; params[60] is ~200 bytes past it, covering saved registers/return address.",
  "title": "Stack buffer overflow in SGR mouse response parser",
  "vendor_severity": "high"
}