@@ -72,17 +72,26 @@ jobs:
7272 debug : debug
7373 coverage : nocoverage
7474 shell : bash
75- # This test gives false positives on newer versions of clang
76- # and ubuntu-18.04 is not supported anymore on github
77- # - test-group: extra
78- # os: ubuntu-18.04
79- # os-type: ubuntu
80- # build-type: msan
81- # compiler-family: clang
82- # c-compiler: clang-6.0
83- # cc-compiler: clang++-6.0
84- # debug: debug
85- # coverage: nocoverage
75+ # TASK-087: restored msan lane. The previous entry (retired
76+ # Ubuntu / Clang toolchain) was dropped when GitHub removed that
77+ # runner image and never replaced, so MemorySanitizer went
78+ # silently absent. This lane runs clang-18 against an
79+ # instrumented libc++ + instrumented
80+ # libmicrohttpd (both built and cached in-CI), with gnutls and
81+ # curl kept out of the picture. See the "instrumented libc++"
82+ # and "Run tests" steps below for how the false-positive-free
83+ # dependency set is assembled and why coverage is scoped to the
84+ # backend-free unit tests.
85+ - test-group : extra
86+ os : ubuntu-latest
87+ os-type : ubuntu
88+ build-type : msan
89+ compiler-family : clang
90+ c-compiler : clang-18
91+ cc-compiler : clang++-18
92+ debug : debug
93+ coverage : nocoverage
94+ shell : bash
8695 - test-group : extra
8796 os : ubuntu-latest
8897 os-type : ubuntu
@@ -586,7 +595,10 @@ jobs:
586595 # TASK-076: also skip on the tls-no-cli lane — it does its own
587596 # install of libgnutls28-dev WITHOUT gnutls-bin in the next step
588597 # so the per-test LT_SKIP_IF(!has_gnutls_cli(), ...) gate fires.
589- if : ${{ matrix.os-type == 'ubuntu' && matrix.build-type != 'flag-invariance-off' && matrix.build-type != 'tls-no-cli' }}
598+ # TASK-087: also skip on the msan lane — installing gnutls would pull
599+ # an uninstrumented gnutls/nettle/gmp tree into LDADD; with no header
600+ # present configure auto-detects HAVE_GNUTLS=no and -lgnutls drops out.
601+ if : ${{ matrix.os-type == 'ubuntu' && matrix.build-type != 'flag-invariance-off' && matrix.build-type != 'tls-no-cli' && matrix.build-type != 'msan' }}
590602
591603 - name : Setup gnutls dependency WITHOUT gnutls-cli (TASK-076 tls-no-cli lane)
592604 run : |
@@ -601,6 +613,34 @@ jobs:
601613 fi
602614 if : ${{ matrix.build-type == 'tls-no-cli' }}
603615
616+ # TASK-087: MemorySanitizer reports use-of-uninitialized-value for any
617+ # bytes written by uninstrumented code and later read by instrumented
618+ # code. libc++ is on nearly every code path, so it must be instrumented
619+ # too. Build LLVM libc++/libc++abi with MemoryWithOrigins once and cache
620+ # the install prefix (keyed by os + compiler); rebuild only on a miss.
621+ - name : Fetch instrumented libc++ from cache (msan lane)
622+ id : cache-libcxx-msan
623+ uses : actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
624+ with :
625+ path : ${{ github.workspace }}/libcxx-msan
626+ key : ${{ matrix.os }}-${{ matrix.c-compiler }}-libcxx-msan-origins-v1
627+ if : ${{ matrix.build-type == 'msan' }}
628+
629+ - name : Build instrumented libc++ (msan lane, if not cached)
630+ run : |
631+ sudo apt-get install -y ninja-build cmake ;
632+ git clone --depth 1 --branch llvmorg-18.1.8 https://github.com/llvm/llvm-project.git llvm-project ;
633+ cmake -G Ninja -S llvm-project/runtimes -B build-libcxx-msan \
634+ -DCMAKE_C_COMPILER=clang-18 \
635+ -DCMAKE_CXX_COMPILER=clang++-18 \
636+ -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
637+ -DLLVM_USE_SANITIZER=MemoryWithOrigins \
638+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
639+ -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/libcxx-msan" ;
640+ ninja -C build-libcxx-msan cxx cxxabi ;
641+ ninja -C build-libcxx-msan install-cxx install-cxxabi ;
642+ if : ${{ matrix.build-type == 'msan' && steps.cache-libcxx-msan.outputs.cache-hit != 'true' }}
643+
604644 - name : Fetch libmicrohttpd from cache
605645 id : cache-libmicrohttpd
606646 uses : actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
@@ -612,7 +652,10 @@ jobs:
612652 # The flag-invariance-on lane uses the stock build because
613653 # libmicrohttpd 1.0.3 ships TLS/Basic/Digest auth on by default and
614654 # does NOT ship the websocket extension at all.
615- if : ${{ matrix.os-type != 'windows' && matrix.build-type != 'no-dauth' && matrix.build-type != 'flag-invariance-off' && matrix.compiler-family != 'arm-cross' }}
655+ # TASK-087: the msan lane rebuilds libmicrohttpd instrumented (so MHD
656+ # output is not read back as uninitialized), so it skips this stock
657+ # cache/build too.
658+ if : ${{ matrix.os-type != 'windows' && matrix.build-type != 'no-dauth' && matrix.build-type != 'flag-invariance-off' && matrix.build-type != 'msan' && matrix.compiler-family != 'arm-cross' }}
616659
617660 - name : Build libmicrohttpd dependency (if not cached)
618661 run : |
@@ -625,7 +668,8 @@ jobs:
625668 # TASK-037: as above -- flag-invariance-off has its own dedicated
626669 # libmicrohttpd build step below; flag-invariance-on uses the stock
627670 # build because libmicrohttpd 1.0.3 ships no websocket extension.
628- if : ${{ matrix.os-type != 'windows' && matrix.build-type != 'no-dauth' && matrix.build-type != 'flag-invariance-off' && matrix.compiler-family != 'arm-cross' && steps.cache-libmicrohttpd.outputs.cache-hit != 'true' }}
671+ # TASK-087: msan has its own dedicated instrumented build step below.
672+ if : ${{ matrix.os-type != 'windows' && matrix.build-type != 'no-dauth' && matrix.build-type != 'flag-invariance-off' && matrix.build-type != 'msan' && matrix.compiler-family != 'arm-cross' && steps.cache-libmicrohttpd.outputs.cache-hit != 'true' }}
629673
630674
631675 - name : Build libmicrohttpd without digest auth (no-dauth test)
@@ -656,6 +700,30 @@ jobs:
656700 make ;
657701 if : ${{ matrix.build-type == 'flag-invariance-off' && steps.cache-libmicrohttpd-off.outputs.cache-hit != 'true' }}
658702
703+ - name : Fetch libmicrohttpd from cache (msan lane)
704+ id : cache-libmicrohttpd-msan
705+ uses : actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
706+ with :
707+ path : libmicrohttpd-1.0.3
708+ key : ${{ matrix.os }}-${{ matrix.c-compiler }}-libmicrohttpd-1.0.3-msan-v1
709+ if : ${{ matrix.build-type == 'msan' }}
710+
711+ - name : Build instrumented libmicrohttpd (TASK-087 msan lane)
712+ run : |
713+ curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-1.0.3.tar.gz -o libmicrohttpd-1.0.3.tar.gz ;
714+ echo "7816b57aae199cf5c3645e8770e1be5f0a4dfafbcb24b3772173dc4ee634126a libmicrohttpd-1.0.3.tar.gz" | sha256sum -c ;
715+ tar -xzf libmicrohttpd-1.0.3.tar.gz ;
716+ cd libmicrohttpd-1.0.3 ;
717+ # Instrument MHD with MemorySanitizer so its output buffers, which
718+ # libhttpserver reads on nearly every code path, are not flagged as
719+ # uninitialized. --disable-https keeps gnutls (uninstrumented) out.
720+ export CC=clang-18 ;
721+ export CFLAGS='-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -g -O1' ;
722+ export LDFLAGS='-fsanitize=memory' ;
723+ ./configure --disable-examples --disable-https ;
724+ make ;
725+ if : ${{ matrix.build-type == 'msan' && steps.cache-libmicrohttpd-msan.outputs.cache-hit != 'true' }}
726+
659727 - name : Install libmicrohttpd
660728 run : cd libmicrohttpd-1.0.3 ; sudo make install ;
661729 if : ${{ matrix.os-type != 'windows' && matrix.compiler-family != 'arm-cross' }}
@@ -785,7 +853,16 @@ jobs:
785853 run : |
786854 # Set memory check flags. They need to stay in step as env variables don't propagate across steps.
787855 if [ "$BUILD_TYPE" = "asan" ]; then export CFLAGS='-fsanitize=address'; export CXXFLAGS='-fsanitize=address'; export LDFLAGS='-fsanitize=address'; fi
788- if [ "$BUILD_TYPE" = "msan" ]; then export CFLAGS='-fsanitize=memory'; export CXXFLAGS='-fsanitize=memory'; export LDFLAGS='-fsanitize=memory'; fi
856+ # TASK-087: msan needs the instrumented libc++ built above, plus
857+ # origins tracking and frame pointers for readable reports. Point
858+ # the C++ TUs at the instrumented libc++ headers/libs and drop the
859+ # default (uninstrumented) libstdc++ via -nostdinc++/-stdlib=libc++.
860+ if [ "$BUILD_TYPE" = "msan" ]; then
861+ LIBCXX_MSAN="${{ github.workspace }}/libcxx-msan" ;
862+ export CFLAGS="-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -g -O1" ;
863+ export CXXFLAGS="-stdlib=libc++ -nostdinc++ -isystem ${LIBCXX_MSAN}/include/c++/v1 -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -g -O1" ;
864+ export LDFLAGS="-stdlib=libc++ -fsanitize=memory -L${LIBCXX_MSAN}/lib -Wl,-rpath,${LIBCXX_MSAN}/lib -lc++abi" ;
865+ fi
789866 if [ "$BUILD_TYPE" = "lsan" ]; then export CFLAGS='-fsanitize=leak'; export CXXFLAGS='-fsanitize=leak'; export LDFLAGS='-fsanitize=leak'; fi
790867 if [ "$BUILD_TYPE" = "tsan" ]; then export CFLAGS='-fsanitize=thread'; export CXXFLAGS='-fsanitize=thread'; export LDFLAGS='-fsanitize=thread'; fi
791868 if [ "$BUILD_TYPE" = "ubsan" ]; then export CFLAGS='-fsanitize=undefined'; export CXXFLAGS='-fsanitize=undefined'; export LDFLAGS='-fsanitize=undefined'; fi
@@ -949,8 +1026,32 @@ jobs:
9491026 # (see test/tsan.supp). All libhttpserver-internal races stay fatal.
9501027 if [ "$BUILD_TYPE" = "tsan" ]; then
9511028 export TSAN_OPTIONS="suppressions=$(pwd)/../test/tsan.supp" ;
1029+ make check ;
1030+ elif [ "$BUILD_TYPE" = "msan" ]; then
1031+ # TASK-087: msan coverage is deliberately scoped to the
1032+ # backend-free UNIT tests. curl (linked into every integ
1033+ # round-trip via test/integ/curl_helpers.hpp) cannot be
1034+ # practically instrumented, so its uninstrumented output would
1035+ # surface as false use-of-uninitialized-value reports. libc++ and
1036+ # libmicrohttpd ARE instrumented (see the steps above), so the
1037+ # unit subset — pure libhttpserver logic plus instrumented-MHD
1038+ # type inspection — runs clean without wildcard suppressions.
1039+ export LD_LIBRARY_PATH="${{ github.workspace }}/libcxx-msan/lib:${LD_LIBRARY_PATH:-}" ;
1040+ export MSAN_OPTIONS="abort_on_error=1" ;
1041+ # Auto-wire narrow suppressions only if triage produced the file.
1042+ # test/msan.supp is intentionally ABSENT until a real finding
1043+ # forces a narrow use-of-uninitialized-value:<symbol> entry with
1044+ # an upstream-issue comment (never a wildcard over libhttpserver).
1045+ if [ -f ../test/msan.supp ]; then
1046+ export MSAN_OPTIONS="${MSAN_OPTIONS}:suppressions=$(pwd)/../test/msan.supp" ;
1047+ fi
1048+ # Derive the unit/ subset from the test Makefile so newly-added
1049+ # unit tests are covered automatically.
1050+ MSAN_TESTS="$(grep -oE '^[A-Za-z0-9_]+_SOURCES[[:space:]]*=[[:space:]]*unit/' ../test/Makefile.am | sed -E 's/_SOURCES.*//' | tr '\n' ' ')" ;
1051+ make check TESTS="$MSAN_TESTS" ;
1052+ else
1053+ make check ;
9521054 fi
953- make check;
9541055 # TASK-037: flag-invariance-{on,off} are compile/link-only gates, so
9551056 # skip the full `make check` (which spins up real servers and would
9561057 # crash on the OFF lane for the BAUTH/DAUTH/WS unit tests anyway).
0 commit comments