From 5fc8dc4dfdba089c792c1e9e0b9991134434cd78 Mon Sep 17 00:00:00 2001 From: Aizal Khan Date: Wed, 1 Jul 2026 12:37:42 +0530 Subject: [PATCH] fix out-of-bounds read in FuzzySetMatch/FuzzyMatchParse octet scan AddressSanitizer, matching a pattern against a shorter address: ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 #0 strlen #1 sscanf #2 FuzzySetMatch addr_lib.c Noticed this reading the IP range matcher. FuzzySetMatch() splits an address into octets with a fixed 4-iteration loop: for (i = 0; i < 4; i++) { sscanf(sp1, "%63[^.]", buffer1); ... sscanf(sp2, "%63[^.]", buffer2); sp2 += strlen(buffer2) + 1; } The +1 steps over the '.' separator. But on the last octet there is no separator, so it steps over the terminating NUL instead. When s2 has fewer octets than s1 (say pattern "1.2.3.4" against address "1"), sp2 walks past the end of the string and the next sscanf reads out of bounds. buffer2 is not reset either, so a failed scan keeps stale content. s2 reaches here from untrusted data through the isipinsubnet() and iprange() policy functions, whose address argument is an arbitrary string. Only advance past a separator that is actually there, reset buffer2, and stop with no match once the address runs out of octets. The IPv6 match loop and the IPv4 range loop in FuzzyMatchParse() had the same walk-off, fixed the same way. Added a regression test that trips ASan on the old code. Changelog: Title Signed-off-by: Aizal Khan --- libcfnet/addr_lib.c | 46 +++++++++++++++++++++++++++++++++----- tests/unit/addr_lib_test.c | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/libcfnet/addr_lib.c b/libcfnet/addr_lib.c index c3c5c33871..02ded0bac3 100644 --- a/libcfnet/addr_lib.c +++ b/libcfnet/addr_lib.c @@ -174,12 +174,27 @@ int FuzzySetMatch(const char *s1, const char *s2) break; } - sp1 += strlen(buffer1) + 1; + sp1 += strlen(buffer1); + if (*sp1 == '.') + { + sp1++; + } + buffer2[0] = '\0'; sscanf(sp2, "%63[^.]", buffer2); buffer2[63] = '\0'; - sp2 += strlen(buffer2) + 1; + if (strlen(buffer2) == 0) + { + /* s2 has fewer octets than the pattern: no match */ + return -1; + } + + sp2 += strlen(buffer2); + if (*sp2 == '.') + { + sp2++; + } if (strstr(buffer1, "-")) { @@ -268,15 +283,25 @@ int FuzzySetMatch(const char *s1, const char *s2) for (i = 0; i < 8; i++) { + buffer1[0] = '\0'; sscanf(sp1, "%63[^:]", buffer1); buffer1[63] = '\0'; - sp1 += strlen(buffer1) + 1; + sp1 += strlen(buffer1); + if (*sp1 == ':') + { + sp1++; + } + buffer2[0] = '\0'; sscanf(sp2, "%63[^:]", buffer2); buffer2[63] = '\0'; - sp2 += strlen(buffer2) + 1; + sp2 += strlen(buffer2); + if (*sp2 == ':') + { + sp2++; + } if (strstr(buffer1, "-")) { @@ -487,7 +512,18 @@ bool FuzzyMatchParse(const char *s) { buffer1[0] = '\0'; sscanf(sp1, "%63[^.]", buffer1); - sp1 += strlen(buffer1) + 1; + buffer1[63] = '\0'; + + if (strlen(buffer1) == 0) + { + break; + } + + sp1 += strlen(buffer1); + if (*sp1 == '.') + { + sp1++; + } if (strstr(buffer1, "-")) { diff --git a/tests/unit/addr_lib_test.c b/tests/unit/addr_lib_test.c index f6785c4014..9600eecdea 100644 --- a/tests/unit/addr_lib_test.c +++ b/tests/unit/addr_lib_test.c @@ -143,13 +143,51 @@ static void test_ParseHostPort() hostname = NULL; port = NULL; } +static void test_FuzzySetMatch() +{ + /* Sanity: exact and subnet matches still work. */ + assert_int_equal(FuzzySetMatch("128.39.74.10/23", "128.39.75.56"), 0); + assert_int_not_equal(FuzzySetMatch("128.39.74.10/24", "128.39.75.56"), 0); + assert_int_equal(FuzzySetMatch("1.2.3.4", "1.2.3.4"), 0); + assert_int_not_equal(FuzzySetMatch("1.2.3.4", "1.2.3.5"), 0); + + /* An address with fewer octets than the pattern used to walk the scan + * pointer past the end of the string. Heap-allocate the address so that + * the over-read is caught under ASan. It must simply not match. */ + char *s = xstrdup("1"); + assert_int_not_equal(FuzzySetMatch("1.2.3.4", s), 0); + free(s); + + s = xstrdup("1.2"); + assert_int_not_equal(FuzzySetMatch("1.2.3.4", s), 0); + free(s); + + /* Same for an IPv6 range pattern against a short address. */ + s = xstrdup("2001"); + assert_int_not_equal(FuzzySetMatch("2001:0-ffff:0:0:0:0:0:1", s), 0); + free(s); +} + +static void test_FuzzyMatchParse() +{ + assert_true(FuzzyMatchParse("1.2.3.4")); + assert_true(FuzzyMatchParse("1-10.2.3.4")); + + /* A range with fewer octets used to read past the end of the string. */ + char *s = xstrdup("1-2.3"); + FuzzyMatchParse(s); + free(s); +} + int main() { PRINT_TEST_BANNER(); const UnitTest tests[] = { - unit_test(test_ParseHostPort) + unit_test(test_ParseHostPort), + unit_test(test_FuzzySetMatch), + unit_test(test_FuzzyMatchParse) }; return run_tests(tests);