Foot Version
foot 1.27.0
TERM environment variable
foot
Compositor Name and Version
sway 1.11
Distribution
arch
Terminal multiplexer
No response
Shell, TUI, application
No response
Server/standalone mode
- Standalone
- Server
Foot config
default
Description of Bug and Steps to Reproduce
The %xx decode loop in uri_parse() (uri.c) reads next[1] and next[2]
without checking that two bytes remain:
const char *next = memchr(encoded, '%', encoded_len);
...
if (hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15) {
*p++ = hex2nibble(next[1]) << 4 | hex2nibble(next[2]);
encoded_len -= 3; // underflows if encoded_len < 3
encoded = next + 3;
}
A URI ending in '%' makes next[1]/next[2] read past the end of the
input.
The OSC 7 / OSC 8 / url-mode callers pass strlen-terminated strings, so
a trailing '%' short-circuits safely there. The clipboard /
drag-and-drop text/uri-list path does not: in selection.c,
fdm_receive_decoder_uri() xrealloc's ctx->buf.data to exactly the data
size (no NUL terminator, no slack), and fdm_receive_finish_uri() calls
decode_one_uri(ctx, ctx->buf.data, ctx->buf.idx) -> uri_parse(). For a
file:// URI, query_start/fragment_start are NULL so path_len reaches
the end of the buffer and a trailing '%' is the last byte.
If the two out-of-bounds bytes are valid hex, encoded_len (==1) -= 3
underflows to a huge size_t and the decode loop then memcpy/strncpy
runs out of bounds into 'decoded' (allocated only path_len + 1).
Triggering requires a Wayland client that offers a malformed
text/uri-list (last entry not terminated by CRLF, ending in '%') plus
a user paste/drop. RFC 2483 specifies that every URI is followed by
CRLF, so well-behaved clipboard owners never produce the trigger
input. The bug is still worth fixing as a defensive-coding hardening,
not because there is a credible exploit path in normal use.
Reproduce: drive the clipboard URI pipeline with a 10-byte buffer
sized exactly to the payload (no NUL slack):
payload: "file:///a%" (10 bytes, no trailing CRLF)
buffer: xrealloc'd to exactly 10 bytes by fdm_receive_decoder_uri()
call: fdm_receive_finish_uri() -> decode_one_uri() -> uri_parse()
Fix: require at least 3 bytes remaining before reading next[1]/next[2],
otherwise treat '%' literally; alternatively, NUL-terminate the
clipboard URI buffer before parsing.
Relevant logs, stacktraces, etc.
Debug log (selection.c + uri.c)
[*] driving REAL begin/decoder/finish uri pipeline with 10-byte
non-terminated payload ending in '%'
==ERROR== AddressSanitizer: heap-buffer-overflow
READ of size 1
#0 uri_parse (uri.c:198, the next[1] access)
#1 decode_one_uri (selection.c)
0 bytes after a 10-byte region
allocated by:
#1 xrealloc
#2 fdm_receive_decoder_uri (selection.c)