From f4fce42e77553d0cf5a0327b9dde8f53c587847b Mon Sep 17 00:00:00 2001 From: Sayed Kaif Date: Thu, 30 Jul 2026 00:46:08 +0530 Subject: [PATCH 1/2] fail mmap::open when ::mmap returns MAP_FAILED --- httplib.h | 9 +++++++++ test/test.cc | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/httplib.h b/httplib.h index 1c98cc80..3cd371cb 100644 --- a/httplib.h +++ b/httplib.h @@ -5568,6 +5568,15 @@ inline bool mmap::open(const char *path) { is_open_empty_file = true; return false; } + + // A failed mapping must not be left in `addr_`: `is_open()` only compares it + // against nullptr, so the MAP_FAILED sentinel would pass and `data()` would + // hand the caller (const char *)-1. + if (addr_ == MAP_FAILED) { + addr_ = nullptr; + close(); + return false; + } #endif return true; diff --git a/test/test.cc b/test/test.cc index b71171f7..04e130a1 100644 --- a/test/test.cc +++ b/test/test.cc @@ -9163,6 +9163,23 @@ TEST(MmapTest, OpenWhileFileHeldForWriting) { } #endif +#ifndef _WIN32 +// A failed ::mmap must not be reported as an open mapping. is_open() only +// compares addr_ against nullptr, so the MAP_FAILED sentinel used to pass it +// and data() handed the caller (const char *)-1. A directory opens and stats +// fine but has no mapping, so ::mmap fails for it. +TEST(MmapTest, FailedMappingIsNotOpen) { + const char *path = "./mmap_failed_mapping_test_dir"; + ASSERT_EQ(0, ::mkdir(path, 0755)); + auto dir_cleanup = detail::scope_exit([&] { ::rmdir(path); }); + + detail::mmap m(path); + EXPECT_FALSE(m.is_open()); + EXPECT_NE(static_cast(m.data()), + static_cast(MAP_FAILED)); +} +#endif + TEST(KeepAliveTest, ReadTimeout) { Server svr; From 15a23abd7e65aaa11e639fcb681464f1ded5a453 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 1 Aug 2026 22:21:26 -0400 Subject: [PATCH 2/2] Clarify the MAP_FAILED guard comment and assert the cleared size Explain why `addr_` is reset before `close()`: `munmap()` must not be called with the sentinel. The test also checks `size()`, since the hazard is a stale size paired with a sentinel `data()`. --- httplib.h | 5 ++--- test/test.cc | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/httplib.h b/httplib.h index e699b122..e4044c57 100644 --- a/httplib.h +++ b/httplib.h @@ -5577,10 +5577,9 @@ inline bool mmap::open(const char *path) { return false; } - // A failed mapping must not be left in `addr_`: `is_open()` only compares it - // against nullptr, so the MAP_FAILED sentinel would pass and `data()` would - // hand the caller (const char *)-1. if (addr_ == MAP_FAILED) { + // Clear the sentinel before `close()`, since `is_open()` only checks + // `addr_` against nullptr and `munmap()` must not be called with it. addr_ = nullptr; close(); return false; diff --git a/test/test.cc b/test/test.cc index adc4ddbb..588a1b62 100644 --- a/test/test.cc +++ b/test/test.cc @@ -9347,10 +9347,9 @@ TEST(MmapTest, OpenWhileFileHeldForWriting) { #endif #ifndef _WIN32 -// A failed ::mmap must not be reported as an open mapping. is_open() only -// compares addr_ against nullptr, so the MAP_FAILED sentinel used to pass it -// and data() handed the caller (const char *)-1. A directory opens and stats -// fine but has no mapping, so ::mmap fails for it. +// A failed ::mmap() must not be reported as an open mapping, otherwise data() +// hands the caller the MAP_FAILED sentinel. A directory is the easiest way to +// reach it, since ::open() and fstat() succeed for one but ::mmap() doesn't. TEST(MmapTest, FailedMappingIsNotOpen) { const char *path = "./mmap_failed_mapping_test_dir"; ASSERT_EQ(0, ::mkdir(path, 0755)); @@ -9358,6 +9357,7 @@ TEST(MmapTest, FailedMappingIsNotOpen) { detail::mmap m(path); EXPECT_FALSE(m.is_open()); + EXPECT_EQ(0U, m.size()); EXPECT_NE(static_cast(m.data()), static_cast(MAP_FAILED)); }