From 6d87eb2878899ec1ccf6b612d950b90367b5ea45 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 13 Mar 2026 16:42:16 +0100 Subject: [PATCH] cmake: add `CURL_GCC_ANALYZER` option, enable in CI, fix/silence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable in one existing Linux, macOS and Windows job. Cost: - Linux: +1.3 minutes. - macOS: +1.5 minutes. - Windows: +2.5 minutes. Fix or silence issues found: - conncache: silence NULL deref warning. ``` lib/conncache.c:564:18: warning: dereference of NULL '*data.multi' [CWE-476] [-Wanalyzer-null-dereference] ``` Ref: ede6a8e08762321d95864ad384b8ff5ac44ac459 #19378 - http2: check pointer for NULL. ``` lib/http2.c:388:7: error: dereference of NULL ‘data’ [CWE-476] [-Wanalyzer-null-dereference] ``` - http2: silence potential NULL deref in `cf_h2_recv`. ``` lib/http2.c: In function 'cf_h2_recv': lib/curl_trc.h:62:15: warning: dereference of NULL 'data' [CWE-476] [-Wanalyzer-null-dereference] ``` - openldap: silence deref before NULL check. Seen in GHA/Linux. ``` lib/openldap.c: In function ‘oldap_state_mechs_resp’: lib/curl_trc.h:140:7: warning: check of ‘data’ for NULL after already dereferencing it [-Wanalyzer-deref-before-check] ``` - sendf: silence NULL deref false positive in `Curl_creader_set_fread`. It looks impossible to happen. ``` lib/sendf.c:1133:7: warning: dereference of NULL 'r' [CWE-476] [-Wanalyzer-null-dereference] ``` - ws: silence deref before NULL check. ``` lib/ws.c: In function 'ws_send_raw_blocking': lib/curl_trc.h:205:7: warning: check of 'data' for NULL after already dereferencing it [-Wanalyzer-deref-before-check] ``` - var: fix potential NULL deref ``` src/var.c:216:29: warning: dereference of NULL 'envp' [CWE-476] [-Wanalyzer-null-dereference] ``` - cli_hx_upload.c: fix NULL check after dereference. ``` tests/libtest/cli_hx_upload.c:170:7: warning: check of '*t.method' for NULL after already dereferencing it [-Wanalyzer-deref-before-check] ``` - unit1607, unit1609: fix theoretical NULL ptr dereference. ``` tests/unit/unit1607.c:211:12: warning: dereference of NULL 'addr' [CWE-476] [-Wanalyzer-null-dereference] tests/unit/unit1609.c:193:12: warning: dereference of NULL 'addr' [CWE-476] [-Wanalyzer-null-dereference] ``` - globally disable checks triggering false positives only: ``` docs/examples/externalsocket.c:135:8: warning: 'connect' on possibly invalid file descriptor 'sockfd' [-Wanalyzer-fd-use-without-check] lib/bufq.c:465:16: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop] (gcc-15 Windows) lib/doh.c:1035:34: warning: stack-based buffer over-read [CWE-126] [-Wanalyzer-out-of-bounds] (gcc-15 macOS) lib/ftp.c:4022:20: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop] (gcc-15 macOS) lib/http2.c:689:28: warning: buffer over-read [CWE-126] [-Wanalyzer-out-of-bounds] (gcc-15 macOS) lib/socketpair.c:195:5: warning: leak of file descriptor 'curl_dbg_socket(2, 1, 0, 192, "D:/a/curl/curl/lib/socketpair.c")' [CWE-775] [-Wanalyzer-fd-leak] src/tool_doswin.c:810:7: warning: leak of file descriptor '*tdata.socket_l' [CWE-775] [-Wanalyzer-fd-leak] src/tool_doswin.c:816:9: warning: leak of file descriptor '*tdata.socket_l' [CWE-775] [-Wanalyzer-fd-leak] src/tool_main.c:96:1: warning: leak of file descriptor 'fd[0]' [CWE-775] [-Wanalyzer-fd-leak] src/tool_main.c:96:1: warning: leak of file descriptor 'fd[1]' [CWE-775] [-Wanalyzer-fd-leak] src/tool_urlglob.c:48:17: warning: leak of 'malloc(8)' [CWE-401] [-Wanalyzer-malloc-leak] src/tool_writeout.c:870:3: warning: leak of FILE 'stream2' [CWE-775] [-Wanalyzer-file-leak] tests/libtest/lib518.c:90:1: warning: leak of FILE [CWE-775] [-Wanalyzer-file-leak] tests/libtest/lib537.c:87:1: warning: leak of FILE [CWE-775] [-Wanalyzer-file-leak] tests/server/tftpd.c:1147:10: warning: 'bind' on possibly invalid file descriptor 'sock' [-Wanalyzer-fd-use-without-check] tests/server/tftpd.c:1155:10: warning: 'bind' on possibly invalid file descriptor 'sock' [-Wanalyzer-fd-use-without-check] tests/server/tftpd.c:1259:10: warning: 'connect' on possibly invalid file descriptor '4294967295' [-Wanalyzer-fd-use-without-check] ``` Also: - cmake: update clang-tidy typecheck comment. Ref: https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html Closes #20921 --- .github/workflows/linux.yml | 4 ++-- .github/workflows/macos.yml | 6 +++--- .github/workflows/windows.yml | 4 ++-- CMakeLists.txt | 14 +++++++++++++- docs/INSTALL-CMAKE.md | 1 + docs/examples/CMakeLists.txt | 3 +++ lib/CMakeLists.txt | 12 ++++++++++++ lib/conncache.c | 2 +- lib/http2.c | 11 ++++++++++- lib/openldap.c | 10 ++++++++-- lib/sendf.c | 2 +- lib/ws.c | 3 +++ src/CMakeLists.txt | 4 ++++ src/var.c | 7 ++++--- tests/CMakeLists.txt | 3 +++ tests/libtest/cli_hx_upload.c | 2 +- tests/unit/unit1607.c | 3 +++ tests/unit/unit1609.c | 3 +++ 18 files changed, 77 insertions(+), 17 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0d9acf86bc..eae5aa5f60 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -174,11 +174,11 @@ jobs: install_steps: pytest configure: --with-openssl --enable-debug --disable-unity - - name: 'openssl libssh2 sync-resolver valgrind 1' + - name: 'openssl libssh2 sync-resolver valgrind 1 +analyzer' image: ubuntu-24.04-arm install_packages: libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind tflags: '--min=920 1 to 950' - generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF + generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_GCC_ANALYZER=ON - name: 'openssl libssh2 sync-resolver valgrind 2' image: ubuntu-24.04-arm diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 24271b6877..6bc4898f37 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -314,10 +314,10 @@ jobs: compiler: clang install: gnutls nettle krb5 generate: -DENABLE_DEBUG=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=OFF -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5 -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON - - name: 'aws-lc' - compiler: gcc-13 + - name: 'aws-lc +analyzer' + compiler: gcc-15 install: aws-lc - generate: -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/aws-lc -DUSE_ECH=ON -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON + generate: -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/aws-lc -DUSE_ECH=ON -DCURL_DISABLE_LDAP=ON -DUSE_SSLS_EXPORT=ON -DCURL_GCC_ANALYZER=ON - name: 'Rustls' compiler: clang install: rustls-ffi diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 43f877a228..fd7ca881a9 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -474,14 +474,14 @@ jobs: strategy: matrix: include: - - name: 'schannel' # mingw-w64 12.0 + - name: 'schannel +analyzer' # mingw-w64 12.0 sys: 'mingw64' dir: 'w64devkit' env: 'x86_64' ver: '15.1.0' url: 'https://github.com/skeeto/w64devkit/releases/download/v2.2.0/w64devkit-x64-2.2.0.7z.exe' SHA256: e02de30b97196329662007d64bc4509fbd7f5e14339d344075c7f1223dead4a2 - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF' + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF -DCURL_GCC_ANALYZER=ON' type: 'Release' - name: 'schannel' # mingw-w64 10.0 sys: 'mingw64' diff --git a/CMakeLists.txt b/CMakeLists.txt index aaa1df85d8..308f04e84c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,7 +273,7 @@ if(CURL_CLANG_TIDY) set(PICKY_COMPILER OFF) # Do a best effort and skip passing non-clang warning options to clang-tidy. # This lets through warning options enabled via CURL_WERROR=ON, affecting lib and src. endif() - set(CURL_DISABLE_TYPECHECK ON) # to improve performance and avoid potential interference. + set(CURL_DISABLE_TYPECHECK ON) # to improve performance (1.4x), avoid potential interference and bugprone-macro-parentheses. set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY}") list(APPEND CMAKE_C_CLANG_TIDY "--config-file=${PROJECT_SOURCE_DIR}/.clang-tidy.yml") if(CURL_WERROR) @@ -285,6 +285,18 @@ if(CURL_CLANG_TIDY) endif() endif() +option(CURL_GCC_ANALYZER "Enable GCC --analyzer option" OFF) +if(CURL_GCC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0) + set(CURL_DISABLE_TYPECHECK ON) # to improve performance (1.1x). + # https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html + set(CURL_ANALYZER_CFLAGS "-fanalyzer") + # disable checks causing false positives only + list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-fd-leak" "-Wno-analyzer-fd-use-without-check" "-Wno-analyzer-file-leak") + list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-infinite-loop") + list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-malloc-leak") + list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-out-of-bounds") +endif() + option(CURL_CODE_COVERAGE "Enable code coverage build options" OFF) if(CURL_CODE_COVERAGE) if(CMAKE_C_COMPILER_ID STREQUAL "GNU") diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index db73b0023a..75cfceb872 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -242,6 +242,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) `wolfssl`, `gnutls`, `mbedtls`, `openssl`, `schannel`, `rustls` - `CURL_DROP_UNUSED`: Drop unused code and data from built binaries. Default: `OFF` - `CURL_ENABLE_EXPORT_TARGET`: Enable CMake export target. Default: `ON` +- `CURL_GCC_ANALYZER`: Enable GCC `--analyzer` option. Default: `OFF` - `CURL_HIDDEN_SYMBOLS`: Hide libcurl internal symbols (=hide all symbols that are not officially external). Default: `ON` - `CURL_LIBCURL_SOVERSION`: Enable libcurl SOVERSION. Default: `ON` for supported platforms - `CURL_LIBCURL_VERSIONED_SYMBOLS`: Enable libcurl versioned symbols. Default: `OFF` diff --git a/docs/examples/CMakeLists.txt b/docs/examples/CMakeLists.txt index f1a8f03bb5..f1178ad35e 100644 --- a/docs/examples/CMakeLists.txt +++ b/docs/examples/CMakeLists.txt @@ -83,6 +83,9 @@ foreach(_target IN LISTS COMPLICATED_MAY_BUILD check_PROGRAMS _all) # keep 'COM endif() target_link_libraries(${_target_name} ${LIB_SELECTED} ${CURL_NETWORK_AND_TIME_LIBS} ${_more_libs}) target_compile_definitions(${_target_name} PRIVATE "CURL_NO_OLDIES" "$<$:WIN32_LEAN_AND_MEAN>") + if(CURL_ANALYZER_CFLAGS) + set_property(TARGET ${_target_name} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS}) + endif() set_target_properties(${_target_name} PROPERTIES OUTPUT_NAME "${_target}" PROJECT_LABEL "Example ${_target}" UNITY_BUILD OFF) endforeach() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index f2bf47c67d..4b4f1c3788 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -129,6 +129,10 @@ if(SHARE_LIB_OBJECT AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) if(CURL_CLANG_TIDY) set_target_properties(${LIB_OBJECT} PROPERTIES UNITY_BUILD OFF) endif() + if(CURL_ANALYZER_CFLAGS) + set_target_properties(${LIB_OBJECT} PROPERTIES UNITY_BUILD OFF) + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS}) + endif() if(CURL_CODE_COVERAGE) set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS ${CURL_COVERAGE_MACROS}) set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS ${CURL_COVERAGE_CFLAGS}) @@ -178,6 +182,10 @@ if(BUILD_STATIC_LIBS) set_target_properties(${LIB_STATIC} PROPERTIES UNITY_BUILD OFF) endif() endif() + if(CURL_ANALYZER_CFLAGS) + set_target_properties(${LIB_STATIC} PROPERTIES UNITY_BUILD OFF) + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS}) + endif() if(CURL_CODE_COVERAGE) set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS ${CURL_COVERAGE_MACROS}) set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_OPTIONS ${CURL_COVERAGE_CFLAGS}) @@ -243,6 +251,10 @@ if(BUILD_SHARED_LIBS) if(CURL_CLANG_TIDY) set_target_properties(${LIB_SHARED} PROPERTIES UNITY_BUILD OFF) endif() + if(CURL_ANALYZER_CFLAGS) + set_target_properties(${LIB_SHARED} PROPERTIES UNITY_BUILD OFF) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS}) + endif() if(CURL_CODE_COVERAGE) set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_DEFINITIONS ${CURL_COVERAGE_MACROS}) set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_OPTIONS ${CURL_COVERAGE_CFLAGS}) diff --git a/lib/conncache.c b/lib/conncache.c index a865959709..48d9873ea5 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -558,7 +558,7 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, struct cpool *cpool = cpool_get_instance(data); bool kept = TRUE; - if(!data) + if(!data || !data->multi) return kept; if(!data->multi->maxconnects) { diff --git a/lib/http2.c b/lib/http2.c index a260275085..6869a0fe2f 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -375,6 +375,10 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, (void)cf; DEBUGASSERT(data); + + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + stream = H2_STREAM_CTX(ctx, data); if(stream) { *pstream = stream; @@ -1940,10 +1944,15 @@ static CURLcode cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, size_t *pnread) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + struct h2_stream_ctx *stream; CURLcode result, r2; struct cf_call_data save; + if(!data) + return CURLE_HTTP2; + + stream = H2_STREAM_CTX(ctx, data); + *pnread = 0; if(!stream) { /* Abnormal call sequence: either this transfer has never opened a stream diff --git a/lib/openldap.c b/lib/openldap.c index 7d038e0bf2..5d6637543f 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -693,13 +693,19 @@ out: static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { - struct connectdata *conn = data->conn; - struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + struct connectdata *conn; + struct ldapconninfo *li; int rc; BerElement *ber = NULL; CURLcode result = CURLE_OK; struct berval bv, *bvals; + if(!data) + return CURLE_FAILED_INIT; + + conn = data->conn; + li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + if(!li) return CURLE_FAILED_INIT; switch(ldap_msgtype(msg)) { diff --git a/lib/sendf.c b/lib/sendf.c index 92e77b482a..ab67da26ca 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -1128,7 +1128,7 @@ CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) struct cr_in_ctx *ctx; result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT); - if(result) + if(result || !r) goto out; ctx = r->ctx; ctx->total_len = len; diff --git a/lib/ws.c b/lib/ws.c index ee399ef747..e14bee46f7 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -1689,6 +1689,9 @@ static CURLcode ws_send_raw_blocking(struct Curl_easy *data, CURLcode result = CURLE_OK; size_t nwritten; + if(!data) + return result; + (void)ws; while(buflen) { result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f61833c448..2260ae3923 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -124,6 +124,10 @@ endif() if(CURL_CLANG_TIDY) set_target_properties(${EXE_NAME} PROPERTIES UNITY_BUILD OFF) endif() +if(CURL_ANALYZER_CFLAGS) + set_target_properties(${EXE_NAME} PROPERTIES UNITY_BUILD OFF) + set_property(TARGET ${EXE_NAME} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS}) +endif() if(ENABLE_UNICODE AND MINGW) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) diff --git a/src/var.c b/src/var.c index 7fb51d656e..9065e0c0a9 100644 --- a/src/var.c +++ b/src/var.c @@ -213,6 +213,8 @@ ParameterError varexpand(const char *line, struct dynbuf *out, bool *replaced) curlx_dyn_init(out, MAX_EXPAND_CONTENT); do { envp = strstr(line, "{{"); + if(!envp) + break; if((envp > line) && envp[-1] == '\\') { /* preceding backslash, we want this verbatim */ @@ -227,7 +229,7 @@ ParameterError varexpand(const char *line, struct dynbuf *out, bool *replaced) return PARAM_NO_MEM; line = &envp[2]; } - else if(envp) { + else { char name[MAX_VAR_LEN]; size_t nlen; size_t i; @@ -320,8 +322,7 @@ ParameterError varexpand(const char *line, struct dynbuf *out, bool *replaced) } line = &clp[2]; } - - } while(envp); + } while(1); if(added && *line) { /* add the "suffix" as well */ result = curlx_dyn_add(out, line); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d751a69f2c..9835bcec72 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,6 +41,9 @@ if(CURL_CLANG_TIDY) add_custom_target(tests-clang-tidy) add_dependencies(tt tests-clang-tidy) endif() +if(CURL_ANALYZER_CFLAGS) + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS}) +endif() add_custom_target(testdeps) add_dependencies(testdeps "tt") diff --git a/tests/libtest/cli_hx_upload.c b/tests/libtest/cli_hx_upload.c index a0575c24e4..d0434a2eab 100644 --- a/tests/libtest/cli_hx_upload.c +++ b/tests/libtest/cli_hx_upload.c @@ -158,7 +158,7 @@ static int setup_hx_upload(CURL *curl, const char *url, struct transfer_u *t, if(use_earlydata) curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_EARLYDATA); - if(!strcmp("MIME", t->method)) { + if(t->method && !strcmp("MIME", t->method)) { curl_mimepart *part; t->mime = curl_mime_init(curl); part = curl_mime_addpart(t->mime); diff --git a/tests/unit/unit1607.c b/tests/unit/unit1607.c index 3265df484a..d4fefca4f9 100644 --- a/tests/unit/unit1607.c +++ b/tests/unit/unit1607.c @@ -208,6 +208,9 @@ static CURLcode test_unit1607(const char *arg) break; } + if(!addr) + break; + addr = addr->ai_next; } diff --git a/tests/unit/unit1609.c b/tests/unit/unit1609.c index a862154176..da099d72b2 100644 --- a/tests/unit/unit1609.c +++ b/tests/unit/unit1609.c @@ -190,6 +190,9 @@ static CURLcode test_unit1609(const char *arg) break; } + if(!addr) + break; + addr = addr->ai_next; }