mailinfo: fix out-of-bounds memory reads in unquote_quoted_pair()

When processing a header like a "From" line, mailinfo uses unquote_quoted_pair() to handle double-quotes and rfc822 parenthesized comments. It takes a NUL-terminated string on input, and loops over the "in" pointer until it sees the NUL. When it finds the start of an interesting block, it delegates to helper functions which also increment "in", and return the updated pointer. But there's a bug here: the helpers find the NUL with a post-increment in the loop condition, like: while ((c = *in++) != 0) So when they do see a NUL (rather than the correct termination of the quote or comment section), they return "in" as one _past_ the NUL terminator. And thus the outer loop in unquote_quoted_pair() does not realize we hit the NUL, and keeps reading past the end of the buffer. We should instead make sure to return "in" positioned at the NUL, so that the caller knows to stop their loop, too. A hacky way to do this is to return "in - 1" after leaving the inner loop. But a slightly cleaner solution is to avoid incrementing "in" until we are sure it contained a non-NUL byte (i.e., doing it inside the loop body). The two tests here show off the problem. Since we check the output, they'll _usually_ report a failure in a normal build, but it depends on what garbage bytes are found after the heap buffer. Building with SANITIZE=address reliably notices the problem. The outcome (both the exit code and the exact bytes) are just what Git happens to produce for these cases today, and shouldn't be taken as an endorsement. It might be reasonable to abort on an unterminated string, for example. The priority for this patch is fixing the out-of-bounds memory access. Reported-by: Carlos Andrés Ramírez Cataño <antaigroupltda@gmail.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Dec 12, 2023 at 17:12 UTC d1bd3a8c3424e818f4117a39fe418909e24cea5f
2 files changed +26 -4
mailinfo.c
+4 -4
@@ -56,12 +56,12 @@ static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
56
57 static const char *unquote_comment(struct strbuf *outbuf, const char *in)
58 {
59 - int c;
59 int take_next_literally = 0;
60
61 strbuf_addch(outbuf, '(');
62
64 - while ((c = *in++) != 0) {
63 + while (*in) {
64 + int c = *in++;
65 if (take_next_literally == 1) {
66 take_next_literally = 0;
67 } else {
@@ -86,10 +86,10 @@ static const char *unquote_comment(struct strbuf *outbuf, const char *in)
86
87 static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
88 {
89 - int c;
89 int take_next_literally = 0;
90
92 - while ((c = *in++) != 0) {
91 + while (*in) {
92 + int c = *in++;
93 if (take_next_literally == 1) {
94 take_next_literally = 0;
95 } else {
t/t5100-mailinfo.sh
+22
@@ -268,4 +268,26 @@ test_expect_success 'mailinfo warn CR in base64 encoded email' '
268 test_must_be_empty quoted-cr/0002.err
269 '
270
271 +test_expect_success 'from line with unterminated quoted string' '
272 + echo "From: bob \"unterminated string smith <bob@example.com>" >in &&
273 + git mailinfo /dev/null /dev/null <in >actual &&
274 + cat >expect <<-\EOF &&
275 + Author: bob unterminated string smith
276 + Email: bob@example.com
277 +
278 + EOF
279 + test_cmp expect actual
280 +'
281 +
282 +test_expect_success 'from line with unterminated comment' '
283 + echo "From: bob (unterminated comment smith <bob@example.com>" >in &&
284 + git mailinfo /dev/null /dev/null <in >actual &&
285 + cat >expect <<-\EOF &&
286 + Author: bob (unterminated comment smith
287 + Email: bob@example.com
288 +
289 + EOF
290 + test_cmp expect actual
291 +'
292 +
293 test_done