diff --git a/.gitignore b/.gitignore index a87725f..d932f71 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ # Build directories build/ +build-*/ _build/ cmake-build-*/ @@ -19,6 +20,10 @@ test_stress test_features test_security test_client +test_welcome +test_starvation +test_reentrancy +test_http_edge fuzz_http mnet-server @@ -36,7 +41,6 @@ example/example_combined # User-compiled binaries myapp -test_welcome # OS .DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index ac4a2e9..11fc089 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,27 @@ if(MSVC) # names" — sscanf, strcpy, strdup, etc. These are false positives for # portable C code; suppress them so /WX does not escalate them to errors. add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) + # MSVC's buffer-security hardening is on by default: /GS (stack cookies), + # /DYNAMICBASE (ASLR) and /NXCOMPAT (DEP) are all enabled unless explicitly + # turned off, so no extra flags are needed for parity with the POSIX side. else() add_compile_options(-Wall -Wextra -Wpedantic -Werror) + + # Hardening. -fstack-protector-strong works at any optimization level. + add_compile_options(-fstack-protector-strong) + + # _FORTIFY_SOURCE=2 only makes sense with optimization: without it glibc + # emits a #warning that -Werror would escalate, and CMake's default + # (empty) build type compiles without -O. Restrict it to the optimized + # build types. + if(CMAKE_BUILD_TYPE MATCHES "^(Release|RelWithDebInfo|MinSizeRel)$") + add_compile_definitions(_FORTIFY_SOURCE=2) + endif() + + # relro/now are ELF-only. + if(UNIX AND NOT APPLE) + add_link_options(-Wl,-z,relro,-z,now) + endif() endif() if(NOT WIN32) @@ -136,6 +155,16 @@ target_link_libraries(test_http_edge PRIVATE mnet_static) target_include_directories(test_http_edge PRIVATE include src test) add_test(NAME mnet_http_edge_tests COMMAND test_http_edge) +# Tests are assert()-based, and CMake's Release configs define NDEBUG, which +# would compile every assertion out: the suite would then pass without +# checking anything. Undefine it for the test targets so assertions stay +# active in every build type, matching the Make and Meson builds. +foreach(test_target IN ITEMS test_mnet test_http test_parser test_stress + test_features test_security test_client test_welcome test_starvation + test_reentrancy test_http_edge) + target_compile_options(${test_target} PRIVATE -UNDEBUG) +endforeach() + # Install install(TARGETS mnet_static mnet_shared ARCHIVE DESTINATION lib diff --git a/Makefile b/Makefile index 2da9e53..3769706 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,16 @@ ifeq ($(shell uname -s),Darwin) CFLAGS += -D_DARWIN_C_SOURCE endif +# Hardening. -fstack-protector-strong works at any optimization level and on +# GCC/Clang; -Wl,-z,relro,-z,now is ELF-only, so it stays on Linux. +# _FORTIFY_SOURCE=2 needs optimization, which this Makefile does not set by +# default: add it yourself when building optimized, e.g. +# make CFLAGS="-O2 -D_FORTIFY_SOURCE=2 ..." +CFLAGS += -fstack-protector-strong +ifeq ($(shell uname -s),Linux) + LDFLAGS += -Wl,-z,relro,-z,now +endif + # The worker pool needs pthreads on POSIX. ifeq ($(OS),Windows_NT) LDFLAGS += -lws2_32 diff --git a/meson.build b/meson.build index 2a26fa5..26d65a6 100644 --- a/meson.build +++ b/meson.build @@ -10,8 +10,26 @@ if cc.get_id() == 'msvc' add_project_arguments(['/W4', '/WX'], language: 'c') # Suppress false-positive C4996 deprecation warnings for standard C functions. add_project_arguments(['/D_CRT_SECURE_NO_WARNINGS', '/D_CRT_NONSTDC_NO_DEPRECATE'], language: 'c') + # MSVC's buffer-security hardening is on by default: /GS (stack cookies), + # /DYNAMICBASE (ASLR) and /NXCOMPAT (DEP) are all enabled unless explicitly + # turned off, so no extra flags are needed for parity with the POSIX side. else add_project_arguments(warning_flags, language: 'c') + + # Hardening. -fstack-protector-strong works at any optimization level. + add_project_arguments('-fstack-protector-strong', language: 'c') + + # _FORTIFY_SOURCE=2 only makes sense with optimization: without it glibc + # emits a #warning that -Werror would escalate. Meson's debug buildtype + # is -O0, so restrict it to optimized builds. + if get_option('optimization') != '0' + add_project_arguments('-D_FORTIFY_SOURCE=2', language: 'c') + endif + + # relro/now are ELF-only. + if host_machine.system() == 'linux' + add_project_link_arguments('-Wl,-z,relro,-z,now', language: 'c') + endif endif # glibc's _FORTIFY_SOURCE is only meaningful with optimization, and some @@ -164,6 +182,22 @@ test_http_edge = executable('test_http_edge', test('mnet_http_edge_tests', test_http_edge) +test_client = executable('test_client', + 'test/test_client.c', + link_with: mnet_static, + include_directories: [mnet_inc, include_directories('src'), include_directories('test')], +) + +test('mnet_client_tests', test_client) + +test_welcome = executable('test_welcome', + 'test/test_welcome.c', + link_with: mnet_static, + include_directories: [mnet_inc, include_directories('src'), include_directories('test')], +) + +test('mnet_welcome_tests', test_welcome) + # Install headers (libraries are installed by default) install_headers('include/mnet/mnet.h', 'include/mnet/mnet_app.h', 'include/mnet/mnet_request.h', 'include/mnet/mnet_response.h', 'include/mnet/mnet_router.h', 'include/mnet/mnet_socket.h', diff --git a/test/test_mnet.c b/test/test_mnet.c index 9794722..a16df81 100644 --- a/test/test_mnet.c +++ b/test/test_mnet.c @@ -130,27 +130,26 @@ static void test_mnet_json_escape(void) { char out[256]; - size_t n = mnet_json_escape(out, sizeof(out), "hello"); + (void)mnet_json_escape(out, sizeof(out), "hello"); assert(strcmp(out, "hello") == 0); - n = mnet_json_escape(out, sizeof(out), "he\"llo"); + (void)mnet_json_escape(out, sizeof(out), "he\"llo"); assert(strcmp(out, "he\\\"llo") == 0); - n = mnet_json_escape(out, sizeof(out), "a\\b"); + (void)mnet_json_escape(out, sizeof(out), "a\\b"); assert(strcmp(out, "a\\\\b") == 0); - n = mnet_json_escape(out, sizeof(out), "a\nb"); + (void)mnet_json_escape(out, sizeof(out), "a\nb"); assert(strcmp(out, "a\\nb") == 0); - n = mnet_json_escape(out, sizeof(out), "a\rb"); + (void)mnet_json_escape(out, sizeof(out), "a\rb"); assert(strcmp(out, "a\\rb") == 0); - n = mnet_json_escape(out, sizeof(out), "a\tb"); + (void)mnet_json_escape(out, sizeof(out), "a\tb"); assert(strcmp(out, "a\\tb") == 0); - n = mnet_json_escape(out, sizeof(out), ""); + assert(mnet_json_escape(out, sizeof(out), "") == 0); assert(strcmp(out, "") == 0); - assert(n == 0); printf(" PASS test_mnet_json_escape\n"); } @@ -574,25 +573,21 @@ static void test_mnet_url_decode_malformed(void) /* The legacy wrapper must not leave a truncated value behind. */ memset(out, 'X', sizeof(out)); - size_t n = mnet_url_decode(out, sizeof(out), "a%zzb"); - assert(n == 0); + assert(mnet_url_decode(out, sizeof(out), "a%zzb") == 0); assert(out[0] == '\0'); memset(out, 'X', sizeof(out)); - n = mnet_url_decode(out, sizeof(out), "a%00b"); - assert(n == 0); + assert(mnet_url_decode(out, sizeof(out), "a%00b") == 0); assert(out[0] == '\0'); /* exact fit succeeds */ char buf4[4]; - n = mnet_url_decode(buf4, sizeof(buf4), "abc"); - assert(n == 3); + assert(mnet_url_decode(buf4, sizeof(buf4), "abc") == 3); assert(strcmp(buf4, "abc") == 0); /* overflow refuses */ char buf3[3]; - n = mnet_url_decode(buf3, sizeof(buf3), "abcd"); - assert(n == 0); + assert(mnet_url_decode(buf3, sizeof(buf3), "abcd") == 0); assert(buf3[0] == '\0'); printf(" PASS test_mnet_url_decode_malformed\n");