From 26b971b4a8f075adc6f5283600212d3e1666af22 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Tue, 27 Oct 2026 10:36:28 -0300 Subject: [PATCH] harden root_dir --- mongoose.c | 4 +++- mongoose.h | 2 ++ src/dash.h | 2 ++ src/http.c | 4 +++- test/unit_test.c | 6 ++++++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mongoose.c b/mongoose.c index e06b827f..2c83fd48 100644 --- a/mongoose.c +++ b/mongoose.c @@ -4484,13 +4484,15 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, static bool uri2path(const char *r, struct mg_str uri, char *buf, size_t len) { struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u; size_t n; + bool matched = false; while (mg_span(s, &part, &s, ',')) { if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); if (v.len == 0) v = k, k = mg_str("/"), u = k, d = v; if (uri.len < k.len) continue; if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue; - u = k, d = v; + u = k, d = v, matched = true; } + if (!matched) return false; n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf); if (len == 0 || n + 2 >= len) return false; // Path overflow if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash diff --git a/mongoose.h b/mongoose.h index 6ac3ed93..61bbb632 100644 --- a/mongoose.h +++ b/mongoose.h @@ -4109,6 +4109,8 @@ struct mg_dash { struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix // Resolves the built-in file manager's root dir for user u, e.g. per-user // upload directories. NULL: file manager disabled + // - The returned directory must be nonempty and must not contain ',' or '=', + // because it is used as an mg_http_serve_opts::root_dir mapping target. bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len); }; diff --git a/src/dash.h b/src/dash.h index aef9fbf8..574f9263 100644 --- a/src/dash.h +++ b/src/dash.h @@ -96,6 +96,8 @@ struct mg_dash { struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix // Resolves the built-in file manager's root dir for user u, e.g. per-user // upload directories. NULL: file manager disabled + // - The returned directory must be nonempty and must not contain ',' or '=', + // because it is used as an mg_http_serve_opts::root_dir mapping target. bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len); }; diff --git a/src/http.c b/src/http.c index b0e067b7..aae7da06 100644 --- a/src/http.c +++ b/src/http.c @@ -734,13 +734,15 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, static bool uri2path(const char *r, struct mg_str uri, char *buf, size_t len) { struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u; size_t n; + bool matched = false; while (mg_span(s, &part, &s, ',')) { if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); if (v.len == 0) v = k, k = mg_str("/"), u = k, d = v; if (uri.len < k.len) continue; if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue; - u = k, d = v; + u = k, d = v, matched = true; } + if (!matched) return false; n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf); if (len == 0 || n + 2 >= len) return false; // Path overflow if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash diff --git a/test/unit_test.c b/test/unit_test.c index 22434439..fc22f899 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1475,6 +1475,11 @@ static void h4(struct mg_connection *c, int ev, void *ev_data) { opts.root_dir = "/a=./data"; opts.page404 = "./data/404.html"; // existing 404 page mg_http_serve_dir(c, hm, &opts); + } else if (mg_match(hm->uri, mg_str("/unmapped"), NULL)) { + struct mg_http_serve_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.root_dir = "/a=./data"; + mg_http_serve_dir(c, hm, &opts); } else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) { struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); @@ -1502,6 +1507,7 @@ static void test_http_404(void) { ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); + ASSERT(fetch(&mgr, buf, url, "GET /unmapped HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0);