From: Christopher Faulet Date: Thu, 5 Nov 2026 08:14:59 +0000 (+0800) Subject: BUG/MINOR: tcpcheck: fix out-of-bounds reads on short MySQL error packets X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=3bbfc3bd77c2d6e55de35ac3d5d97e9dc592dad6;p=haproxy.git BUG/MINOR: tcpcheck: fix out-of-bounds reads on short MySQL error packets tcpcheck_mysql_expect_packet() only verifies that the packet header is present before reading inside it. This covers the 3 length bytes and the sequence id, but not the packet type byte read at offset+4 right after. Then the whole packet is checked against the buffer, which only guarantees that 4 bytes past offset were received. When the packet announces a length lower than 4, the error branch below is entered even though the bytes it reads were never received: it reads the error code at offset+5 and offset+6 and uses offset+7 as the start of the message body, while the body length is computed as b_data()-offset-7, which wraps around when b_data() is lower than offset+7. A 5-byte response, i.e. a packet announcing plen == 0 followed by a 0xff byte, is enough to trigger this. The wrapped length does not lead to a copy today because chunk_istcat() refuses to append more than b_room(), but the reads still happen and the error code and status reported are garbage. Thus, first make sure the packet type byte is available before reading it, and then reject error packets announcing less than 4 payload bytes, the minimum for the packet type, a 2-byte error code and at least one byte of description. As a consequence, such packets are now reported as a Layer7 invalid response instead of a Layer7 wrong status with a bogus error code, and a response carrying only the 4 header bytes is now considered incomplete and waits for more data instead of being parsed from bytes that were never received. Truncated responses keep waiting for more data as before. This bug is present since 2.2, so it should be backported to all maintained branches. Co-authored-by: QiuYitai <74493355+QiuYitai@users.noreply.github.com> --- diff --git a/src/tcpcheck.c b/src/tcpcheck.c index 9ca473aa2..3131407df 100644 --- a/src/tcpcheck.c +++ b/src/tcpcheck.c @@ -563,8 +563,8 @@ static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, TRACE_ENTER(CHK_EV_TCPCHK_EXP, check); - /* 3 Bytes for the packet length and 1 byte for the sequence id */ - if (b_data(&check->bi) < offset+4) { + /* 3 Bytes for the packet length, 1 byte for the sequence id and 1 byte for the type */ + if (b_data(&check->bi) < offset+5) { if (!last_read) goto wait_more_data; @@ -587,6 +587,16 @@ static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, } if (*b_peek(&check->bi, offset+4) == '\xff') { + /* The shortest error packet carries at least 4 payload bytes: + * the packet type (0xff), a 2-byte error code and the + * description (at least one byte). + */ + if (plen < 4) { + /* invalid length */ + status = HCHK_STATUS_L7RSP; + goto error; + } + /* MySQL Error packet always begin with field_count = 0xff */ status = HCHK_STATUS_L7STS; err = ((unsigned char) *b_peek(&check->bi, offset+5)) +