From 2eaa7beba1f35b31019a992de35be8a54b9ac028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 May 2026 10:59:26 +0200 Subject: [PATCH] uri-parse: fix out-of-bounds read with malformed %-encoded content If the input URI ends with a trailing '%' (or a trailing '%N'), we read outside the provided buffer. On NULL terminated input, this happened to work out since we'd correctly detect an invalid %-sequence as soon as we read the NULL terminator. On input that is not NULL terminated, we're out of luck. This patch fixes this by also checking we have enough input left to even _try_ to read the %-digits. Also add unit tests for this particular case. Closes #2353 --- CHANGELOG.md | 7 +++++++ uri.c | 31 ++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5792c240..a4c22bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,13 @@ ### Deprecated ### Removed ### Fixed + +* Out-of-bounds read when parsing URIs with malformed %-encoded + content ([#2353][2353]). + +[2353]: https://codeberg.org/dnkl/foot/issues/2353 + + ### Security ### Contributors diff --git a/uri.c b/uri.c index 3f3beb53..ddbfacdd 100644 --- a/uri.c +++ b/uri.c @@ -195,7 +195,9 @@ uri_parse(const char *uri, size_t len, encoded_len -= prefix_len; decoded_len += prefix_len; - if (hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15) { + if (encoded_len >= 3 && + hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15) + { *p++ = hex2nibble(next[1]) << 4 | hex2nibble(next[2]); decoded_len++; encoded_len -= 3; @@ -416,3 +418,30 @@ UNITTEST xassert(query == NULL); xassert(fragment == NULL); } + +UNITTEST +{ + /* Malformed URI, trailing '%' */ + const char uri[] = "file:///%ABNOT-PART-OF-INPUT"; + char *path; + uri_parse(uri, 9, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL); + xassert(streq(path, "/%")); free(path); +} + +UNITTEST +{ + /* Malformed URI, trailing '%2' */ + const char uri[] = "file:///%2ANOT-PART-OF-INPUT"; + char *path; + uri_parse(uri, 10, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL); + xassert(streq(path, "/%2")); free(path); +} + +UNITTEST +{ + /* Malformed URI, trailing '%ag' */ + const char uri[] = "file:///%ag"; + char *path; + uri_parse(uri, sizeof(uri) - 1, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL); + xassert(streq(path, "/%ag")); free(path); +}