From b55de8b151d1466a3e41d1f854f22eeb6faa23dc Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Mon, 8 Jun 2026 14:29:27 -0300 Subject: [PATCH] avoid OOB reads in path_is_sane --- mongoose.c | 15 +++++++++------ src/util.c | 15 +++++++++------ test/unit_test.c | 14 +++++++++++++- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/mongoose.c b/mongoose.c index 731e8001..97e5b57f 100644 --- a/mongoose.c +++ b/mongoose.c @@ -24771,13 +24771,16 @@ int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) { bool mg_path_is_sane(const struct mg_str path) { const char *s = path.buf; size_t n = path.len; - if (path.buf[0] == '~') return false; // Starts with ~ - if (path.buf[0] == '.' && path.buf[1] == '.') return false; // Starts with .. - for (; s[0] != '\0' && n > 0; s++, n--) { - if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir? - if (s[1] == '.' && s[2] == '.') return false; // Starts with .. - } + if (n == 0 || path.buf[0] == '\0') return true; + if (s[0] == '~') return false; // Starts with ~ + if (s[0] == '.' && n > 1 && s[1] == '.') + return false; // Starts with .. + for (; n > 0 && s[0] != '\0'; s++, n--) { + if ((s[0] == '/' || s[0] == '\\') && n >= 2 && s[1] == '.' && n > 2 && + s[2] == '.') + return false; // Subdir starts with .. } + if (n > 0) return false; // embedded nul (terminator not counted in len) return true; } diff --git a/src/util.c b/src/util.c index 459a581f..6c3c7b86 100644 --- a/src/util.c +++ b/src/util.c @@ -143,13 +143,16 @@ int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) { bool mg_path_is_sane(const struct mg_str path) { const char *s = path.buf; size_t n = path.len; - if (path.buf[0] == '~') return false; // Starts with ~ - if (path.buf[0] == '.' && path.buf[1] == '.') return false; // Starts with .. - for (; s[0] != '\0' && n > 0; s++, n--) { - if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir? - if (s[1] == '.' && s[2] == '.') return false; // Starts with .. - } + if (n == 0 || path.buf[0] == '\0') return true; + if (s[0] == '~') return false; // Starts with ~ + if (s[0] == '.' && n > 1 && s[1] == '.') + return false; // Starts with .. + for (; n > 0 && s[0] != '\0'; s++, n--) { + if ((s[0] == '/' || s[0] == '\\') && n >= 2 && s[1] == '.' && n > 2 && + s[2] == '.') + return false; // Subdir starts with .. } + if (n > 0) return false; // embedded nul (terminator not counted in len) return true; } diff --git a/test/unit_test.c b/test/unit_test.c index 4bd7db1a..96fc2add 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -2555,7 +2555,18 @@ static void test_str(void) { ASSERT(strcmp(buf, "[164:2100:0:0:0:0:0:0]:3 7") == 0); } - ASSERT(mg_path_is_sane(mg_str(".")) == true); + { + char s[1] = "."; + ASSERT(mg_path_is_sane(mg_str_n(s, 1)) == true); + } + { + char s[2] = "/."; + ASSERT(mg_path_is_sane(mg_str_n(s, 2)) == true); + } + { + char s[3] = "a/."; + ASSERT(mg_path_is_sane(mg_str_n(s, 3)) == true); + } ASSERT(mg_path_is_sane(mg_str("")) == true); ASSERT(mg_path_is_sane(mg_str("a.b")) == true); ASSERT(mg_path_is_sane(mg_str("a..b")) == true); @@ -2571,6 +2582,7 @@ static void test_str(void) { ASSERT(mg_path_is_sane(mg_str("a/../b")) == false); ASSERT(mg_path_is_sane(mg_str_n("a/..", 2)) == true); ASSERT(mg_path_is_sane(mg_str_n("a/../b", 2)) == true); + ASSERT(mg_path_is_sane(mg_str_n("a\0/../b", 7)) == false); } static void fn1(struct mg_connection *c, int ev, void *ev_data) {