ANT-2026-3SEPW083 · ncurses
stack-buffer-overflow high
Severity Claude high · Security research firm high · Maintainer -
Discovered by an unreleased Anthropic model
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
- Create a pty pair and spawn the target curses program on the slave with TERM=xterm-1006.
- Let the program initialize curses and call mousemask(), which enables SGR-1006 mouse mode via the XM capability.
- From the pty master, write: \x1b[< followed by ~60-70 semicolons, then attacker-chosen digits, then 'M' (e.g., \x1b[<;;;;;...;1234567890M).
- When the program calls getch()/wgetch(), input dispatch recognizes CSI-< and calls decode_xterm_SGR1006() -> read_SGR().
- Each semicolon increments
nowpast MAX_PARAMS; the trailing digits write controlled ints into params[now] far past the 9-int array, overwriting saved RBP/return address. - 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
Triage and disclosure were performed by Ada Logics.
- Verdict
- true positive
- Severity
- high
Dates from discovery through public reveal.
- 2026-04-08 Reported to tracker
- 2026-04-18 Sent to maintainer
- 2026-04-18 Patch released
- 2026-05-13 Maintainer acknowledged
- 2026-08-17 Publicly revealed
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"
}