From e60617300bd1e530569a3816bd30ad6ae147cb9d Mon Sep 17 00:00:00 2001 From: Eric Covener Date: Sat, 11 May 2024 21:33:09 +0200 Subject: [PATCH 1/8] apache2: let httpd handle CL/TE for non-http handlers Fix #2635 origin: https://github.com/apache/httpd/commit/a29723ce1af75eed0813c3717d3f6dee9b405ca8.patch bug-cve: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24795 --- apache2/mod_proxy_uwsgi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apache2/mod_proxy_uwsgi.c b/apache2/mod_proxy_uwsgi.c index ed9979279..909fc32ae 100644 --- a/apache2/mod_proxy_uwsgi.c +++ b/apache2/mod_proxy_uwsgi.c @@ -388,6 +388,12 @@ static int uwsgi_response(request_rec *r, proxy_conn_rec *backend, proxy_server_ return HTTP_BAD_GATEWAY; } + /* T-E wins over C-L */ + if (apr_table_get(r->headers_out, "Transfer-Encoding")) { + apr_table_unset(r->headers_out, "Content-Length"); + backend->close = 1; + } + if ((buf = apr_table_get(r->headers_out, "Content-Type"))) { ap_set_content_type(r, apr_pstrdup(r->pool, buf)); } From a26309021dd64c8b10aaae09e28194c87433541e Mon Sep 17 00:00:00 2001 From: "Hanan .T" Date: Sat, 11 May 2024 18:05:31 +0300 Subject: [PATCH 2/8] core: remove race-condition over termination of uWSGI process when using need-app and lazy-apps Fix #2640 --- core/uwsgi.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/uwsgi.c b/core/uwsgi.c index 4ce77e657..5e1abbc45 100644 --- a/core/uwsgi.c +++ b/core/uwsgi.c @@ -3835,13 +3835,6 @@ void uwsgi_init_all_apps() { if (uwsgi.need_app) { if (!uwsgi.lazy) uwsgi_log("*** no app loaded. GAME OVER ***\n"); - if (uwsgi.lazy_apps) { - if (uwsgi.master_process) { - if (kill(uwsgi.workers[0].pid, SIGINT)) { - uwsgi_error("kill()"); - } - } - } exit(UWSGI_FAILED_APP_CODE); } else { From 76f6ed33332d66d4726add819fb90f8a1127611c Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 14 May 2024 21:08:14 -0700 Subject: [PATCH 3/8] fix 32-bit compilation with GCC14 Wrong pointer type is used. --- core/regexp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/regexp.c b/core/regexp.c index 74bb77751..2b59f16fb 100644 --- a/core/regexp.c +++ b/core/regexp.c @@ -23,7 +23,7 @@ int uwsgi_regexp_build(char *re, uwsgi_pcre ** pattern) { #ifdef UWSGI_PCRE2 int errnbr; - long unsigned int erroff; + size_t erroff; *pattern = pcre2_compile((const unsigned char *) re, PCRE2_ZERO_TERMINATED, 0, &errnbr, &erroff, NULL); #else From 835b88ab11ec32d813f91cdc21db71b13f6a003b Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 27 May 2024 21:08:30 +0200 Subject: [PATCH 4/8] uwsgiconfig: get compiler version with -dumpfullversion Fix #2644 --- uwsgiconfig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uwsgiconfig.py b/uwsgiconfig.py index 448ebfb23..ab63a7e58 100644 --- a/uwsgiconfig.py +++ b/uwsgiconfig.py @@ -708,13 +708,13 @@ def __init__(self, filename, mute=False): if uwsgi_os == 'GNU': self.cflags.append('-D__HURD__') - gcc_version = spcall("%s -dumpversion" % GCC) + gcc_version = spcall("%s -dumpfullversion -dumpversion" % GCC) if not gcc_version and GCC.startswith('gcc'): if uwsgi_os == 'Darwin': GCC = 'llvm-' + GCC else: GCC = 'gcc' - gcc_version = spcall("%s -dumpversion" % GCC) + gcc_version = spcall("%s -dumpfullversion -dumpversion" % GCC) try: add_it = False From 29387ad6b2508eaebdea093d84f8e12e3e34cbd4 Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Mon, 29 Apr 2024 22:53:52 +0200 Subject: [PATCH 5/8] fix uwsgi_regexp_match() with pcre2 (Fix #2634) pcre2_match() with no match_data structure does not work --- core/regexp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/regexp.c b/core/regexp.c index 2b59f16fb..7e0c7de1e 100644 --- a/core/regexp.c +++ b/core/regexp.c @@ -69,7 +69,7 @@ int uwsgi_regexp_build(char *re, uwsgi_pcre ** pattern) { int uwsgi_regexp_match(uwsgi_pcre *pattern, const char *subject, int length) { #ifdef UWSGI_PCRE2 - return pcre2_match(pattern, (const unsigned char *)subject, length, 0, 0, NULL, NULL); + return uwsgi_regexp_match_ovec(pattern, subject, length, NULL, 0); #else return pcre_exec((const pcre *) pattern->p, (const pcre_extra *) pattern->extra, subject, length, 0, 0, NULL, 0); #endif From 2d302a8fa969c8a90a78485c9a70e0ccdc28aade Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Mon, 29 Apr 2024 23:08:01 +0200 Subject: [PATCH 6/8] add unittest for uwsgi_regexp_match() --- Makefile | 5 ++ buildconf/unittest.ini | 6 +++ unittest/Makefile | 28 +++++++++++ unittest/check_core.c | 108 ++++++++++++++++++++++++++++++++++++++++ unittest/check_regexp.c | 86 ++++++++++++++++++++++++++++++++ 5 files changed, 233 insertions(+) create mode 100644 buildconf/unittest.ini create mode 100644 unittest/Makefile create mode 100644 unittest/check_core.c create mode 100644 unittest/check_regexp.c diff --git a/Makefile b/Makefile index b06a45330..43189356e 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ all: clean: $(PYTHON) uwsgiconfig.py --clean + cd unittest && make clean check: $(PYTHON) uwsgiconfig.py --check @@ -12,5 +13,9 @@ check: plugin.%: $(PYTHON) uwsgiconfig.py --plugin plugins/$* $(PROFILE) +tests: + $(PYTHON) uwsgiconfig.py --build unittest + cd unittest && make && make test + %: $(PYTHON) uwsgiconfig.py --build $@ diff --git a/buildconf/unittest.ini b/buildconf/unittest.ini new file mode 100644 index 000000000..80ff3e709 --- /dev/null +++ b/buildconf/unittest.ini @@ -0,0 +1,6 @@ +[uwsgi] +inherit = base +bin_name = libuwsgi.a +embedded_plugins = null +as_shared_library = static +check = true diff --git a/unittest/Makefile b/unittest/Makefile new file mode 100644 index 000000000..f0c22acef --- /dev/null +++ b/unittest/Makefile @@ -0,0 +1,28 @@ + +CFLAGS = $(shell pkg-config --cflags check) +CFLAGS += -DUWSGI_PCRE2 +LDFLAGS = $(shell pkg-config --libs check) +LDFLAGS += -ldl -lz +LDFLAGS += $(shell xml2-config --libs) +LDFLAGS += $(shell pkg-config --libs openssl) +LDFLAGS += $(shell pcre2-config --libs8) +LDFLAGS += $(shell pkg-config --libs jansson) + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Linux) + LDFLAGS += -lcap +endif + + +objects = check_core check_regexp + +all: $(objects) + +$(objects): %: %.c ../libuwsgi.a + $(CC) $(CFLAGS) -o $@ $< ../libuwsgi.a $(LDFLAGS) + +test: + @for file in $(objects); do ./$$file; done + +clean: + rm -f $(objects) diff --git a/unittest/check_core.c b/unittest/check_core.c new file mode 100644 index 000000000..6aaee620d --- /dev/null +++ b/unittest/check_core.c @@ -0,0 +1,108 @@ +#include +#include "../uwsgi.h" + + +START_TEST(test_uwsgi_strncmp) +{ + int result; + result = uwsgi_strncmp("test", 4, "test", 4); + ck_assert(result == 0); + + result = uwsgi_strncmp("test", 4, "tes", 3); + ck_assert(result == 1); + + result = uwsgi_strncmp("tes", 3, "test", 4); + ck_assert(result == 1); + + result = uwsgi_strncmp("aaa", 3, "bbb", 3); + ck_assert_msg(result < 0, "result: %d", result); + + result = uwsgi_strncmp("bbb", 3, "aaa", 3); + ck_assert_msg(result > 0, "result: %d", result); +} +END_TEST + +Suite *check_core_strings(void) +{ + Suite *s = suite_create("uwsgi strings"); + TCase *tc = tcase_create("strings"); + + suite_add_tcase(s, tc); + tcase_add_test(tc, test_uwsgi_strncmp); + return s; +} + +START_TEST(test_uwsgi_opt_set_int) +{ + int result; + uwsgi_opt_set_int("", "true", &result); + ck_assert(result == 0); + + uwsgi_opt_set_int("", "false", &result); + ck_assert(result == 0); + + uwsgi_opt_set_int("", "0", &result); + ck_assert(result == 0); + + uwsgi_opt_set_int("", "60", &result); + ck_assert(result == 60); + + // When used with "optional_argument", value will be passed as NULL + uwsgi_opt_set_int("", NULL, &result); + ck_assert(result == 1); +} +END_TEST + +Suite *check_core_opt_parsing(void) +{ + Suite *s = suite_create("uwsgi opt parsing"); + TCase *tc = tcase_create("opt_parsing"); + + suite_add_tcase(s, tc); + tcase_add_test(tc, test_uwsgi_opt_set_int); + return s; +} + +START_TEST(test_uwsgi_cron_task_needs_execution_handles_weekday_7_as_sunday) +{ + int result; + struct tm *t; + time_t now; + + now = time(NULL); + t = localtime(&now); + t->tm_wday= 0; + + result = uwsgi_cron_task_needs_execution(t, -1, -1, -1, -1, 0); + ck_assert(result == 1); + + result = uwsgi_cron_task_needs_execution(t, -1, -1, -1, -1, 7); + ck_assert(result == 1); + + result = uwsgi_cron_task_needs_execution(t, -1, -1, -1, -1, 1); + ck_assert(result == 0); +} +END_TEST + +Suite *check_core_cron(void) +{ + Suite *s = suite_create("uwsgi cron"); + TCase *tc = tcase_create("cron"); + + suite_add_tcase(s, tc); + tcase_add_test(tc, test_uwsgi_cron_task_needs_execution_handles_weekday_7_as_sunday); + return s; +} + +int main(void) +{ + int nf; + SRunner *r = srunner_create(check_core_strings()); + srunner_add_suite(r, check_core_opt_parsing()); + srunner_add_suite(r, check_core_cron()); + srunner_run_all(r, CK_NORMAL); + nf = srunner_ntests_failed(r); + srunner_free(r); + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/unittest/check_regexp.c b/unittest/check_regexp.c new file mode 100644 index 000000000..b2025bfb2 --- /dev/null +++ b/unittest/check_regexp.c @@ -0,0 +1,86 @@ +#include +#include "../uwsgi.h" + + +START_TEST(test_uwsgi_regexp_match) +{ + int result; + uwsgi_pcre *pattern_all; + uwsgi_pcre *pattern; + + result = uwsgi_regexp_build(".*", &pattern_all); + ck_assert(result == 0); + + result = uwsgi_regexp_match(pattern_all, "/fooba", 6); + ck_assert(result >= 0); + + result = uwsgi_regexp_build("/foobar/.*", &pattern); + ck_assert(result == 0); + + result = uwsgi_regexp_match(pattern, "/fooba", 6); + ck_assert(result < 0); + + result = uwsgi_regexp_match(pattern, "/foobar/baz", 11); + ck_assert(result >= 0); + + pcre2_code_free(pattern_all); + pcre2_code_free(pattern); +} +END_TEST + +START_TEST(test_uwsgi_regexp_match_ovec) +{ + int result; + uwsgi_pcre *pattern; + int *ovec = calloc((2+1)*2, sizeof(int)); + char buf[20], sub[20]; + + result = uwsgi_regexp_build("^/foo/(.*)\\.jpg\\?([0-9]{2})", &pattern); + ck_assert(result == 0); + result = uwsgi_regexp_ovector(pattern); + ck_assert(result == 2); + + result = uwsgi_regexp_match_ovec(pattern, "/fooba", 6, ovec, 2); + ck_assert(result < 0); + + strcpy(buf, "/foo/bar.jpg?422"); + result = uwsgi_regexp_match_ovec(pattern, buf, strlen(buf), ovec, 2); + ck_assert(result >= 0); + strncpy(sub, buf+ovec[0], ovec[1]-ovec[0]); + sub[ovec[1]-ovec[0]] = '\0'; + ck_assert_str_eq(sub, "/foo/bar.jpg?42"); + strncpy(sub, buf+ovec[2], ovec[3]-ovec[2]); + sub[ovec[3]-ovec[2]] = '\0'; + ck_assert_str_eq(sub, "bar"); + strncpy(sub, buf+ovec[4], ovec[5]-ovec[4]); + sub[ovec[5]-ovec[4]] = '\0'; + ck_assert_str_eq(sub, "42"); + + strcpy(sub, uwsgi_regexp_apply_ovec(buf, strlen(buf), "key=$1.$2.jpg", 13, ovec, 2)); + ck_assert_str_eq(sub, "key=bar.42.jpg"); + + pcre2_code_free(pattern); + free(ovec); +} +END_TEST + +Suite *check_regexp(void) +{ + Suite *s = suite_create("uwsgi regexp"); + TCase *tc = tcase_create("regexp"); + + suite_add_tcase(s, tc); + tcase_add_test(tc, test_uwsgi_regexp_match); + tcase_add_test(tc, test_uwsgi_regexp_match_ovec); + return s; +} + +int main(void) +{ + int nf; + SRunner *r = srunner_create(check_regexp()); + srunner_run_all(r, CK_NORMAL); + nf = srunner_ntests_failed(r); + srunner_free(r); + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} From e23fb3cee235d6f4ce19cc24e6d064e181bb9a33 Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Mon, 29 Apr 2024 23:09:39 +0200 Subject: [PATCH 7/8] make unittest a specific test job also rename unittests as such, making the distinction with tests and checks. --- .github/workflows/test.yml | 32 ++++++++++++++++++-------------- Makefile | 4 ++-- unittest/Makefile | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c396344d..19f06baa0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,13 +7,26 @@ on: branches: [ master, uwsgi-2.0 ] jobs: - python: + unittest: + runs-on: ubuntu-20.04 + steps: + - name: Install dependencies + run: | + sudo apt update -qq + sudo apt install --no-install-recommends -qqyf \ + libpcre2-dev libjansson-dev libcap2-dev \ + check + - uses: actions/checkout@v4 + - name: Run unit tests + run: make unittests + + python: runs-on: ubuntu-20.04 strategy: matrix: python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - test-suite: [unittest, python, deadlocks] + test-suite: [python, deadlocks] steps: - name: Add deadnakes ppa run: sudo add-apt-repository ppa:deadsnakes/ppa -y @@ -22,34 +35,27 @@ jobs: sudo apt update -qq sudo apt install --no-install-recommends -qqyf python${{ matrix.python-version }}-dev \ libpcre2-dev libjansson-dev libcap2-dev \ - curl check + curl - name: Install distutils if: contains(fromJson('["3.6","3.7","3.8","3.9","3.10","3.11","3.12"]'), matrix.python-version) run: | sudo apt install --no-install-recommends -qqyf python${{ matrix.python-version }}-distutils \ - uses: actions/checkout@v4 - - name: Run unit tests - if: matrix.test-suite == 'unittest' - run: make tests - name: Build uWSGI binary - if: matrix.test-suite != 'unittest' run: make - name: Build python${{ matrix.python-version }} plugin - if: matrix.test-suite != 'unittest' run: | PYTHON_VERSION=${{ matrix.python-version }} PYTHON_VERSION=python${PYTHON_VERSION//.} /usr/bin/python${{ matrix.python-version }} -V /usr/bin/python${{ matrix.python-version }} uwsgiconfig.py --plugin plugins/python base $PYTHON_VERSION - name: run smoke tests - if: matrix.test-suite != 'unittest' run: | PYTHON_VERSION=${{ matrix.python-version }} PYTHON_VERSION=python${PYTHON_VERSION//.} ./tests/gh-${{ matrix.test-suite }}.sh ${PYTHON_VERSION} rack: - runs-on: ubuntu-20.04 strategy: matrix: @@ -59,11 +65,9 @@ jobs: run: | sudo apt update -qq sudo apt install --no-install-recommends -qqyf python3-dev \ - libpcre3-dev libjansson-dev libcap2-dev ruby2.7-dev \ - curl check + libpcre2-dev libjansson-dev libcap2-dev ruby2.7-dev \ + curl - uses: actions/checkout@v4 - - name: Run unit tests - run: make tests - name: Build uWSGI binary run: make - name: Build rack plugin diff --git a/Makefile b/Makefile index 43189356e..e7e3a6354 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ check: plugin.%: $(PYTHON) uwsgiconfig.py --plugin plugins/$* $(PROFILE) -tests: +unittests: $(PYTHON) uwsgiconfig.py --build unittest - cd unittest && make && make test + cd unittest && make test %: $(PYTHON) uwsgiconfig.py --build $@ diff --git a/unittest/Makefile b/unittest/Makefile index f0c22acef..c28b8d70c 100644 --- a/unittest/Makefile +++ b/unittest/Makefile @@ -21,7 +21,7 @@ all: $(objects) $(objects): %: %.c ../libuwsgi.a $(CC) $(CFLAGS) -o $@ $< ../libuwsgi.a $(LDFLAGS) -test: +test: all @for file in $(objects); do ./$$file; done clean: From 7e8cb427ccd7cc0c72568ca4c97a7580503c4044 Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Tue, 30 Apr 2024 11:23:42 +0200 Subject: [PATCH 8/8] add integration test for static-expires-uri --- .github/workflows/test.yml | 12 ++++++ Makefile | 3 ++ t/runner | 82 ++++++++++++++++++++++++++++++++++++++ t/static/config.ini | 4 ++ 4 files changed, 101 insertions(+) create mode 100755 t/runner create mode 100644 t/static/config.ini diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19f06baa0..88df146b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,18 @@ jobs: - name: Run unit tests run: make unittests + test: + runs-on: ubuntu-20.04 + steps: + - name: Install dependencies + run: | + sudo apt update -qq + sudo apt install --no-install-recommends -qqyf \ + libpcre2-dev libjansson-dev libcap2-dev + - uses: actions/checkout@v4 + - name: Run integration tests + run: make all tests + python: runs-on: ubuntu-20.04 strategy: diff --git a/Makefile b/Makefile index e7e3a6354..96bfe0011 100644 --- a/Makefile +++ b/Makefile @@ -17,5 +17,8 @@ unittests: $(PYTHON) uwsgiconfig.py --build unittest cd unittest && make test +tests: + $(PYTHON) t/runner + %: $(PYTHON) uwsgiconfig.py --build $@ diff --git a/t/runner b/t/runner new file mode 100755 index 000000000..b9d9ce529 --- /dev/null +++ b/t/runner @@ -0,0 +1,82 @@ +#!/usr/bin/python3 + + +import os +import requests +import signal +import socket +import subprocess +import time +import unittest + + +TESTS_DIR = os.path.dirname(__file__) +UWSGI_BINARY = os.getenv("UWSGI_BINARY", os.path.join(TESTS_DIR, "..", "uwsgi")) +UWSGI_ADDR = "127.0.0.1" +UWSGI_PORT = 8000 +UWSGI_HTTP = f"{UWSGI_ADDR}:{UWSGI_PORT}" + + +class BaseTest: + """ + Container class to avoid base test being run + """ + + class UwsgiServerTest(unittest.TestCase): + """ + Test case with a server instance available on a socket for requests + """ + + @classmethod + def uwsgi_ready(cls): + try: + s = socket.socket() + s.connect( + ( + UWSGI_ADDR, + UWSGI_PORT, + ) + ) + except socket.error: + return False + else: + return True + finally: + s.close() + + @classmethod + def setUpClass(cls): + # launch server + cls.testserver = subprocess.Popen( + [UWSGI_BINARY, "--http-socket", UWSGI_HTTP] + cls.ARGS + ) + + # ensure server is ready + retries = 10 + while not cls.uwsgi_ready() and retries > 0: + time.sleep(0.1) + retries = retries - 1 + if retries == 0: + raise RuntimeError("uwsgi test server is not available") + + @classmethod + def tearDownClass(cls): + cls.testserver.send_signal(signal.SIGTERM) + cls.testserver.wait() + + +class StaticTest(BaseTest.UwsgiServerTest): + + ARGS = [ + "--plugin", + "python3", # provide a request plugin if no embedded request plugin + os.path.join(TESTS_DIR, "static", "config.ini"), + ] + + def test_static_expires(self): + with requests.get(f"http://{UWSGI_HTTP}/foobar/config.ini") as r: + self.assertTrue("Expires" in r.headers) + + +if __name__ == "__main__": + unittest.main() diff --git a/t/static/config.ini b/t/static/config.ini new file mode 100644 index 000000000..e0e23c55e --- /dev/null +++ b/t/static/config.ini @@ -0,0 +1,4 @@ +[uwsgi] +need-app = False +static-map = /foobar=t/static +static-expires-uri = ^/foobar/ 315360000