diff --git a/0001-CMakeLists.txt.patch b/0001-CMakeLists.txt.patch new file mode 100644 index 0000000000000000000000000000000000000000..028c51955508e194bf2a80a9e79740326f707a91 --- /dev/null +++ b/0001-CMakeLists.txt.patch @@ -0,0 +1,9 @@ +--- civetweb-1.16/CMakeLists.txt.orig 2025-07-16 07:44:16.982762892 -0400 ++++ civetweb-1.16/CMakeLists.txt 2025-07-16 07:44:32.894505768 -0400 +@@ -1,5 +1,5 @@ + # Use at least CMake 3.3 +-cmake_minimum_required (VERSION 3.3.0) ++cmake_minimum_required (VERSION 3.3.0...4.0) + cmake_policy(VERSION 3.2.2) + cmake_policy(SET CMP0054 NEW) + cmake_policy(SET CMP0057 NEW) diff --git a/0002-src-civetweb.c.patch b/0002-src-civetweb.c.patch new file mode 100644 index 0000000000000000000000000000000000000000..fc3a0243a233b63e032b1275bad616b776c68c36 --- /dev/null +++ b/0002-src-civetweb.c.patch @@ -0,0 +1,83 @@ +From 76e222bcb77ba8452e5da4e82ae6cecd499c25e0 Mon Sep 17 00:00:00 2001 +From: krispybyte +Date: Sat, 21 Jun 2025 23:33:50 +0300 +Subject: [PATCH 1/2] Fix heap overflow in directory URI slash redirection + +--- + src/civetweb.c | 23 ++++++++++++++++++----- + 1 file changed, 18 insertions(+), 5 deletions(-) + +diff --git a/src/civetweb.c b/src/civetweb.c +index bbc9aa8be..e969c939f 100644 +--- a/src/civetweb.c ++++ b/src/civetweb.c +@@ -15579,7 +15579,6 @@ handle_request(struct mg_connection *conn) + /* 12. Directory uris should end with a slash */ + if (file.stat.is_directory && ((uri_len = (int)strlen(ri->local_uri)) > 0) + && (ri->local_uri[uri_len - 1] != '/')) { +- + /* Path + server root */ + size_t buflen = UTF8_PATH_MAX * 2 + 2; + char *new_path; +@@ -15592,12 +15591,26 @@ handle_request(struct mg_connection *conn) + mg_send_http_error(conn, 500, "out or memory"); + } else { + mg_get_request_link(conn, new_path, buflen - 1); +- strcat(new_path, "/"); ++ ++ size_t len = strlen(new_path); ++ if (len + 1 < buflen) { ++ new_path[len] = '/'; ++ new_path[len + 1] = '\0'; ++ len += 1; ++ } ++ + if (ri->query_string) { +- /* Append ? and query string */ +- strcat(new_path, "?"); +- strcat(new_path, ri->query_string); ++ if (len + 1 < buflen) { ++ new_path[len] = '?'; ++ new_path[len + 1] = '\0'; ++ len += 1; ++ } ++ ++ /* Append with size of space left for query string + null terminator */ ++ size_t max_append = buflen - len - 1; ++ strncat(new_path, ri->query_string, max_append); + } ++ + mg_send_http_redirect(conn, new_path, 301); + mg_free(new_path); + } + +From d5321963b1d0bc953101de91f8588bf83db73bf5 Mon Sep 17 00:00:00 2001 +From: krispybyte +Date: Sun, 22 Jun 2025 00:23:06 +0300 +Subject: [PATCH 2/2] Fit code style + +--- + src/civetweb.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/civetweb.c b/src/civetweb.c +index e969c939f..6af91f874 100644 +--- a/src/civetweb.c ++++ b/src/civetweb.c +@@ -15596,14 +15596,14 @@ handle_request(struct mg_connection *conn) + if (len + 1 < buflen) { + new_path[len] = '/'; + new_path[len + 1] = '\0'; +- len += 1; ++ len++; + } + + if (ri->query_string) { + if (len + 1 < buflen) { + new_path[len] = '?'; + new_path[len + 1] = '\0'; +- len += 1; ++ len++; + } + + /* Append with size of space left for query string + null terminator */ diff --git a/backport-CVE-2025-9648.patch b/backport-CVE-2025-9648.patch new file mode 100644 index 0000000000000000000000000000000000000000..bffa4e562b981471e62ce729566c096b74c9f3b7 --- /dev/null +++ b/backport-CVE-2025-9648.patch @@ -0,0 +1,249 @@ +From 782e18903515f43bafbf2e668994e82bdfa51133 Mon Sep 17 00:00:00 2001 +From: bel2125 +Date: Tue, 2 Sep 2025 14:08:41 +0200 +Subject: [PATCH] Make parsing of URL encoded forms more robust + +Reject requests that obviously violate the URL encoding. +Fixes #1348 +--- + src/civetweb.c | 7 ++++++- + src/handle_form.inl | 46 +++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 44 insertions(+), 9 deletions(-) + +diff --git a/src/civetweb.c b/src/civetweb.c +index 6d3db86..3e3b880 100644 +--- a/src/civetweb.c ++++ b/src/civetweb.c +@@ -1,4 +1,4 @@ +-/* Copyright (c) 2013-2021 the Civetweb developers ++/* Copyright (c) 2013-2025 the Civetweb developers + * Copyright (c) 2004-2013 Sergey Lyubka + * + * Permission is hereby granted, free of charge, to any person obtaining a copy +@@ -7052,6 +7052,7 @@ mg_url_decode(const char *src, + int is_form_url_encoded) + { + int i, j, a, b; ++ + #define HEXTOI(x) (isdigit(x) ? (x - '0') : (x - 'W')) + + for (i = j = 0; (i < src_len) && (j < (dst_len - 1)); i++, j++) { +@@ -7064,11 +7065,15 @@ mg_url_decode(const char *src, + i += 2; + } else if (is_form_url_encoded && (src[i] == '+')) { + dst[j] = ' '; ++ } else if ((unsigned char)src[i] <= ' ') { ++ return -1; /* invalid character */ + } else { + dst[j] = src[i]; + } + } + ++#undef HEXTOI ++ + dst[j] = '\0'; /* Null-terminate the destination */ + + return (i >= src_len) ? j : -1; +diff --git a/src/handle_form.inl b/src/handle_form.inl +index be477a0..0ebaf56 100644 +--- a/src/handle_form.inl ++++ b/src/handle_form.inl +@@ -1,4 +1,4 @@ +-/* Copyright (c) 2016-2021 the Civetweb developers ++/* Copyright (c) 2016-2025 the Civetweb developers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal +@@ -39,7 +39,7 @@ url_encoded_field_found(const struct mg_connection *conn, + mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); + + if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) { +- return MG_FORM_FIELD_STORAGE_SKIP; ++ return MG_FORM_FIELD_STORAGE_ABORT; + } + + if (filename) { +@@ -53,7 +53,7 @@ url_encoded_field_found(const struct mg_connection *conn, + || (filename_dec_len < 0)) { + /* Log error message and skip this field. */ + mg_cry_internal(conn, "%s: Cannot decode filename", __func__); +- return MG_FORM_FIELD_STORAGE_SKIP; ++ return MG_FORM_FIELD_STORAGE_ABORT; + } + remove_dot_segments(filename_dec); + +@@ -95,6 +95,7 @@ url_encoded_field_get( + struct mg_form_data_handler *fdh) + { + char key_dec[1024]; ++ int key_dec_len; + + char *value_dec = (char *)mg_malloc_ctx(*value_len + 1, conn->phys_ctx); + int value_dec_len, ret; +@@ -108,7 +109,8 @@ url_encoded_field_get( + return MG_FORM_FIELD_STORAGE_ABORT; + } + +- mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); ++ key_dec_len = mg_url_decode( ++ key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); + + if (*value_len >= 2 && value[*value_len - 2] == '%') + *value_len -= 2; +@@ -117,6 +119,11 @@ url_encoded_field_get( + value_dec_len = mg_url_decode( + value, (int)*value_len, value_dec, ((int)*value_len) + 1, 1); + ++ if ((key_dec_len < 0) || (value_dec_len < 0)) { ++ mg_free(value_dec); ++ return MG_FORM_FIELD_STORAGE_ABORT; ++ } ++ + ret = fdh->field_get(key_dec, + value_dec, + (size_t)value_dec_len, +@@ -136,9 +143,13 @@ unencoded_field_get(const struct mg_connection *conn, + struct mg_form_data_handler *fdh) + { + char key_dec[1024]; ++ int key_dec_len; + (void)conn; + +- mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); ++ key_dec_len = mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); ++ if (key_dec_len < 0) { ++ return MG_FORM_FIELD_STORAGE_ABORT; ++ } + + return fdh->field_get(key_dec, value, value_len, fdh->user_data); + } +@@ -188,6 +199,7 @@ mg_handle_form_request(struct mg_connection *conn, + int buf_fill = 0; + int r; + int field_count = 0; ++ int abort_read = 0; + struct mg_file fstore = STRUCT_FILE_INITIALIZER; + int64_t file_size = 0; /* init here, to a avoid a false positive + "uninitialized variable used" warning */ +@@ -278,6 +290,7 @@ mg_handle_form_request(struct mg_connection *conn, + conn, data, (size_t)keylen, val, (size_t *)&vallen, fdh); + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + if (r == MG_FORM_FIELD_HANDLE_NEXT) { +@@ -320,6 +333,7 @@ mg_handle_form_request(struct mg_connection *conn, + r = field_stored(conn, path, file_size, fdh); + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + +@@ -358,6 +372,7 @@ mg_handle_form_request(struct mg_connection *conn, + if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT) + == MG_FORM_FIELD_STORAGE_ABORT) { + /* Stop parsing the request */ ++ abort_read = 1; + break; + } + +@@ -386,7 +401,7 @@ mg_handle_form_request(struct mg_connection *conn, + * Here we use "POST", and read the data from the request body. + * The data read on the fly, so it is not required to buffer the + * entire request in memory before processing it. */ +- for (;;) { ++ while (!abort_read) { + const char *val; + const char *next; + ptrdiff_t keylen, vallen; +@@ -440,6 +455,7 @@ mg_handle_form_request(struct mg_connection *conn, + if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT) + == MG_FORM_FIELD_STORAGE_ABORT) { + /* Stop parsing the request */ ++ abort_read = 1; + break; + } + +@@ -468,6 +484,15 @@ mg_handle_form_request(struct mg_connection *conn, + } else { + vallen = (ptrdiff_t)strlen(val); + end_of_key_value_pair_found = all_data_read; ++ if ((buf + buf_fill) > (val + vallen)) { ++ /* Avoid DoS attacks by having a zero byte in the middle of ++ * a request that is supposed to be URL encoded. Since this ++ * request is certainly invalid, according to the protocol ++ * specification, stop processing it. Fixes #1348 */ ++ abort_read = 1; ++ break; ++ } ++ + } + + if (field_storage == MG_FORM_FIELD_STORAGE_GET) { +@@ -489,6 +514,7 @@ mg_handle_form_request(struct mg_connection *conn, + get_block++; + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + if (r == MG_FORM_FIELD_HANDLE_NEXT) { +@@ -557,7 +583,6 @@ mg_handle_form_request(struct mg_connection *conn, + val = buf; + } + } +- + } while (!end_of_key_value_pair_found); + + #if !defined(NO_FILESYSTEMS) +@@ -568,6 +593,7 @@ mg_handle_form_request(struct mg_connection *conn, + r = field_stored(conn, path, file_size, fdh); + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + } else { +@@ -581,7 +607,7 @@ mg_handle_form_request(struct mg_connection *conn, + } + #endif /* NO_FILESYSTEMS */ + +- if (all_data_read && (buf_fill == 0)) { ++ if ((all_data_read && (buf_fill == 0)) || abort_read) { + /* nothing more to process */ + break; + } +@@ -937,6 +963,7 @@ mg_handle_form_request(struct mg_connection *conn, + get_block++; + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + if (r == MG_FORM_FIELD_HANDLE_NEXT) { +@@ -1011,6 +1038,7 @@ mg_handle_form_request(struct mg_connection *conn, + fdh); + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + if (r == MG_FORM_FIELD_HANDLE_NEXT) { +@@ -1039,6 +1067,7 @@ mg_handle_form_request(struct mg_connection *conn, + r = field_stored(conn, path, file_size, fdh); + if (r == MG_FORM_FIELD_HANDLE_ABORT) { + /* Stop request handling */ ++ abort_read = 1; + break; + } + } else { +@@ -1057,6 +1086,7 @@ mg_handle_form_request(struct mg_connection *conn, + if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT) + == MG_FORM_FIELD_STORAGE_ABORT) { + /* Stop parsing the request */ ++ abort_read = 1; + break; + } + diff --git a/civetweb-1.16.tar.gz b/civetweb-1.16.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..a5b029e40eafa02252b8659d01fd3b05f469a0ff Binary files /dev/null and b/civetweb-1.16.tar.gz differ diff --git a/civetweb.spec b/civetweb.spec new file mode 100644 index 0000000000000000000000000000000000000000..224bc4a28e83cc3e03355052877a765ced6f82a1 --- /dev/null +++ b/civetweb.spec @@ -0,0 +1,66 @@ +Name: civetweb +Summary: Embedded C/C++ web server +Version: 1.16 +Release: 1 +License: MIT +Url: https://github.com/civetweb/civetweb +Source: https://github.com/%{name}/%{name}/archive/v%{version}/%{name}-%{version}.tar.gz +Patch1: 0001-CMakeLists.txt.patch +Patch2: 0002-src-civetweb.c.patch +Patch3: backport-CVE-2025-9648.patch + +BuildRequires: cmake make gcc-c++ + +%description +Civetweb is an easy to use, powerful, C (C/C++) embeddable web server +with optional CGI, SSL and Lua support. + +CivetWeb can be used by developers as a library, to add web server +functionality to an existing application. It can also be used by end +users as a stand-alone web server running on a Windows or Linux PC. +It is available as single executable, no installation is required. + +%package devel +Summary: Civetweb Client Library C and C++ header files +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description devel +Civetweb shared libs and associated header files + +%prep +%autosetup -p1 + +%build +%{cmake} . \ + -G "Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_CONFIG=rpmbuild \ + -DCIVETWEB_ENABLE_CXX:BOOL=ON \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + -DCIVETWEB_BUILD_TESTING:BOOL=OFF + +export GCC_COLORS= +export VERBOSE=1 +%cmake_build %{?_smp_mflags} + +%install +%cmake_install +mkdir -p %{buildroot}%{_docdir}/civetweb + +%files +%license LICENSE.md +%doc README.md RELEASE_NOTES.md SECURITY.md +%{_bindir}/civetweb +%{_libdir}/libcivetweb.so.* +%{_libdir}/libcivetweb-cpp.so.* + +%files devel +%{_includedir}/*.h +%{_libdir}/libcivetweb.so +%{_libdir}/libcivetweb-cpp.so +%{_libdir}/cmake/civetweb/* +%{_datadir}/pkgconfig/* + +%changelog +* Tue Sep 30 2025 liweigang - 1.16-1 +- init package and fix CVE-2025-9648 diff --git a/civetweb.yaml b/civetweb.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f39d13a468a78baf251ebf4d9c0057a98f885ce1 --- /dev/null +++ b/civetweb.yaml @@ -0,0 +1,4 @@ +version_control: github +src_repo: civetweb/civetweb +tag_prefix: ^v +separator: .