From 835f653df8b39c9f9790414137eb271bc9678928 Mon Sep 17 00:00:00 2001 From: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com> Date: Tue, 12 May 2026 00:50:59 -0700 Subject: [PATCH] Fix truncated UDP and JSON bounds checks --- src/json.c | 11 +++++------ src/udprelay.c | 7 +++++++ tests/test_json.c | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/json.c b/src/json.c index 00a367f16..830acee46 100644 --- a/src/json.c +++ b/src/json.c @@ -304,7 +304,7 @@ json_value * json_parse_ex (json_settings * settings, case 't': string_add ('\t'); break; case 'u': - if (end - state.ptr < 4 || + if (end - state.ptr <= 4 || (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || @@ -321,7 +321,7 @@ json_value * json_parse_ex (json_settings * settings, if ((uchar & 0xF800) == 0xD800) { json_uchar uchar2; - if (end - state.ptr < 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' || + if (end - state.ptr <= 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' || (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || @@ -600,7 +600,7 @@ json_value * json_parse_ex (json_settings * settings, case 't': - if ((end - state.ptr) < 3 || *(++ state.ptr) != 'r' || + if ((end - state.ptr) <= 3 || *(++ state.ptr) != 'r' || *(++ state.ptr) != 'u' || *(++ state.ptr) != 'e') { goto e_unknown_value; @@ -616,7 +616,7 @@ json_value * json_parse_ex (json_settings * settings, case 'f': - if ((end - state.ptr) < 4 || *(++ state.ptr) != 'a' || + if ((end - state.ptr) <= 4 || *(++ state.ptr) != 'a' || *(++ state.ptr) != 'l' || *(++ state.ptr) != 's' || *(++ state.ptr) != 'e') { @@ -631,7 +631,7 @@ json_value * json_parse_ex (json_settings * settings, case 'n': - if ((end - state.ptr) < 3 || *(++ state.ptr) != 'u' || + if ((end - state.ptr) <= 3 || *(++ state.ptr) != 'u' || *(++ state.ptr) != 'l' || *(++ state.ptr) != 'l') { goto e_unknown_value; @@ -1032,4 +1032,3 @@ void json_value_free (json_value * value) settings.mem_free = default_free; json_value_free_ex (&settings, value); } - diff --git a/src/udprelay.c b/src/udprelay.c index 5de38830a..66a629742 100644 --- a/src/udprelay.c +++ b/src/udprelay.c @@ -245,6 +245,10 @@ static int parse_udprelay_header(const char *buf, const size_t buf_len, char *host, char *port, struct sockaddr_storage *storage) { + if (buf_len < 1) { + return 0; + } + const uint8_t atyp = *(uint8_t *)buf; int offset = 1; @@ -267,6 +271,9 @@ parse_udprelay_header(const char *buf, const size_t buf_len, } } else if ((atyp & ADDRTYPE_MASK) == 3) { // Domain name + if (buf_len < offset + 1) { + return 0; + } uint8_t name_len = *(uint8_t *)(buf + offset); if (name_len + 4 <= buf_len) { if (storage != NULL) { diff --git a/tests/test_json.c b/tests/test_json.c index 9606284d1..7dc065493 100644 --- a/tests/test_json.c +++ b/tests/test_json.c @@ -94,6 +94,26 @@ test_parse_invalid(void) assert(json_parse("not json", 8) == NULL); } +static void +test_parse_truncated_escape_sequences(void) +{ + /* Four bytes remain after 'u', but the fourth hex read would hit EOF. */ + const char *truncated_unicode_escape = "\"\\uB2D"; + assert(json_parse(truncated_unicode_escape, strlen(truncated_unicode_escape)) == NULL); + + /* Six bytes remain after the high surrogate, but the sixth read would hit EOF. */ + const char *truncated_surrogate_pair = "\"\\udfff\\ufff"; + assert(json_parse(truncated_surrogate_pair, strlen(truncated_surrogate_pair)) == NULL); +} + +static void +test_parse_truncated_literals(void) +{ + assert(json_parse("tru", 3) == NULL); + assert(json_parse("fals", 4) == NULL); + assert(json_parse("nul", 3) == NULL); +} + static void test_parse_empty_object(void) { @@ -124,6 +144,8 @@ main(void) test_parse_nested(); test_parse_types(); test_parse_invalid(); + test_parse_truncated_escape_sequences(); + test_parse_truncated_literals(); test_parse_empty_object(); test_parse_empty_array(); return 0;