From ccfa6b10330aa0bf0312eab1a3c7bd0debcb66ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Uhliarik?= Date: Thu, 5 Jun 2025 04:42:06 +0200 Subject: [PATCH] Resolves: RHEL-89691 - varnish: request smuggling attacks (CVE-2025-47905) --- varnish-7.6.1-CVE-2025-47905.patch | 352 +++++++++++++++++++++++++++++ varnish.spec | 11 +- 2 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 varnish-7.6.1-CVE-2025-47905.patch diff --git a/varnish-7.6.1-CVE-2025-47905.patch b/varnish-7.6.1-CVE-2025-47905.patch new file mode 100644 index 0000000..fcc54a8 --- /dev/null +++ b/varnish-7.6.1-CVE-2025-47905.patch @@ -0,0 +1,352 @@ +diff --git a/bin/varnishd/http1/cache_http1_vfp.c b/bin/varnishd/http1/cache_http1_vfp.c +index 20f349d..fbc3dcb 100644 +--- a/bin/varnishd/http1/cache_http1_vfp.c ++++ b/bin/varnishd/http1/cache_http1_vfp.c +@@ -90,75 +90,118 @@ v1f_read(const struct vfp_ctx *vc, struct http_conn *htc, void *d, ssize_t len) + + + /*-------------------------------------------------------------------- +- * Read a chunked HTTP object. ++ * read (CR)?LF at the end of a chunk ++ */ ++static enum vfp_status ++v1f_chunk_end(struct vfp_ctx *vc, struct http_conn *htc) ++{ ++ char c; ++ ++ if (v1f_read(vc, htc, &c, 1) <= 0) ++ return (VFP_Error(vc, "chunked read err")); ++ if (c == '\r' && v1f_read(vc, htc, &c, 1) <= 0) ++ return (VFP_Error(vc, "chunked read err")); ++ if (c != '\n') ++ return (VFP_Error(vc, "chunked tail no NL")); ++ return (VFP_OK); ++} ++ ++ ++/*-------------------------------------------------------------------- ++ * Parse a chunk header and, for VFP_OK, return size in a pointer + * + * XXX: Reading one byte at a time is pretty pessimal. + */ + +-static enum vfp_status v_matchproto_(vfp_pull_f) +-v1f_chunked_pull(struct vfp_ctx *vc, struct vfp_entry *vfe, void *ptr, +- ssize_t *lp) ++static enum vfp_status ++v1f_chunked_hdr(struct vfp_ctx *vc, struct http_conn *htc, ssize_t *szp) + { +- struct http_conn *htc; + char buf[20]; /* XXX: 20 is arbitrary */ +- char *q; + unsigned u; + uintmax_t cll; +- ssize_t cl, l, lr; ++ ssize_t cl, lr; ++ char *q; + + CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC); +- CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC); +- CAST_OBJ_NOTNULL(htc, vfe->priv1, HTTP_CONN_MAGIC); +- AN(ptr); +- AN(lp); ++ CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC); ++ AN(szp); ++ assert(*szp == -1); + +- l = *lp; +- *lp = 0; +- if (vfe->priv2 == -1) { +- /* Skip leading whitespace */ +- do { +- lr = v1f_read(vc, htc, buf, 1); +- if (lr <= 0) +- return (VFP_Error(vc, "chunked read err")); +- } while (vct_islws(buf[0])); +- +- if (!vct_ishex(buf[0])) +- return (VFP_Error(vc, "chunked header non-hex")); +- +- /* Collect hex digits, skipping leading zeros */ +- for (u = 1; u < sizeof buf; u++) { +- do { +- lr = v1f_read(vc, htc, buf + u, 1); +- if (lr <= 0) +- return (VFP_Error(vc, "chunked read err")); +- } while (u == 1 && buf[0] == '0' && buf[u] == '0'); +- if (!vct_ishex(buf[u])) +- break; +- } ++ /* Skip leading whitespace */ ++ do { ++ lr = v1f_read(vc, htc, buf, 1); ++ if (lr <= 0) ++ return (VFP_Error(vc, "chunked read err")); ++ } while (vct_isows(buf[0])); + +- if (u >= sizeof buf) +- return (VFP_Error(vc, "chunked header too long")); ++ if (!vct_ishex(buf[0])) ++ return (VFP_Error(vc, "chunked header non-hex")); + +- /* Skip trailing white space */ +- while (vct_islws(buf[u]) && buf[u] != '\n') { ++ /* Collect hex digits, skipping leading zeros */ ++ for (u = 1; u < sizeof buf; u++) { ++ do { + lr = v1f_read(vc, htc, buf + u, 1); + if (lr <= 0) + return (VFP_Error(vc, "chunked read err")); +- } ++ } while (u == 1 && buf[0] == '0' && buf[u] == '0'); ++ if (!vct_ishex(buf[u])) ++ break; ++ } + +- if (buf[u] != '\n') +- return (VFP_Error(vc, "chunked header no NL")); ++ if (u >= sizeof buf) ++ return (VFP_Error(vc, "chunked header too long")); + +- buf[u] = '\0'; ++ /* Skip trailing white space */ ++ while (vct_isows(buf[u])) { ++ lr = v1f_read(vc, htc, buf + u, 1); ++ if (lr <= 0) ++ return (VFP_Error(vc, "chunked read err")); ++ } ++ ++ if (buf[u] == '\r' && v1f_read(vc, htc, buf + u, 1) <= 0) ++ return (VFP_Error(vc, "chunked read err")); ++ if (buf[u] != '\n') ++ return (VFP_Error(vc, "chunked header no NL")); ++ ++ buf[u] = '\0'; ++ ++ cll = strtoumax(buf, &q, 16); ++ if (q == NULL || *q != '\0') ++ return (VFP_Error(vc, "chunked header number syntax")); ++ cl = (ssize_t)cll; ++ if (cl < 0 || (uintmax_t)cl != cll) ++ return (VFP_Error(vc, "bogusly large chunk size")); ++ ++ *szp = cl; ++ return (VFP_OK); ++} ++ ++ ++/*-------------------------------------------------------------------- ++ * Read a chunked HTTP object. ++ * ++ */ ++ ++static enum vfp_status v_matchproto_(vfp_pull_f) ++v1f_chunked_pull(struct vfp_ctx *vc, struct vfp_entry *vfe, void *ptr, ++ ssize_t *lp) ++{ ++ static enum vfp_status vfps; ++ struct http_conn *htc; ++ ssize_t l, lr; + +- cll = strtoumax(buf, &q, 16); +- if (q == NULL || *q != '\0') +- return (VFP_Error(vc, "chunked header number syntax")); +- cl = (ssize_t)cll; +- if (cl < 0 || (uintmax_t)cl != cll) +- return (VFP_Error(vc, "bogusly large chunk size")); ++ CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC); ++ CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC); ++ CAST_OBJ_NOTNULL(htc, vfe->priv1, HTTP_CONN_MAGIC); ++ AN(ptr); ++ AN(lp); + +- vfe->priv2 = cl; ++ l = *lp; ++ *lp = 0; ++ if (vfe->priv2 == -1) { ++ vfps = v1f_chunked_hdr(vc, htc, &vfe->priv2); ++ if (vfps != VFP_OK) ++ return (vfps); + } + if (vfe->priv2 > 0) { + if (vfe->priv2 < l) +@@ -168,18 +211,15 @@ v1f_chunked_pull(struct vfp_ctx *vc, struct vfp_entry *vfe, void *ptr, + return (VFP_Error(vc, "chunked insufficient bytes")); + *lp = lr; + vfe->priv2 -= lr; +- if (vfe->priv2 == 0) +- vfe->priv2 = -1; +- return (VFP_OK); ++ if (vfe->priv2 != 0) ++ return (VFP_OK); ++ ++ vfe->priv2 = -1; ++ return (v1f_chunk_end(vc, htc)); + } + AZ(vfe->priv2); +- if (v1f_read(vc, htc, buf, 1) <= 0) +- return (VFP_Error(vc, "chunked read err")); +- if (buf[0] == '\r' && v1f_read(vc, htc, buf, 1) <= 0) +- return (VFP_Error(vc, "chunked read err")); +- if (buf[0] != '\n') +- return (VFP_Error(vc, "chunked tail no NL")); +- return (VFP_END); ++ vfps = v1f_chunk_end(vc, htc); ++ return (vfps == VFP_OK ? VFP_END : vfps); + } + + static const struct vfp v1f_chunked = { +diff --git a/bin/varnishtest/tests/f00016.vtc b/bin/varnishtest/tests/f00016.vtc +new file mode 100644 +index 0000000..a38b8b1 +--- /dev/null ++++ b/bin/varnishtest/tests/f00016.vtc +@@ -0,0 +1,69 @@ ++varnishtest "Do not tolerate anything else than CRLF as chunked ending" ++ ++server s0 { ++ rxreq ++ expect_close ++} -dispatch ++ ++varnish v1 -vcl+backend {} -start ++ ++logexpect l1 -v v1 { ++ expect * 1001 FetchError "chunked tail no NL" ++ expect * 1004 FetchError "chunked tail no NL" ++ expect * 1007 FetchError "chunked header non-hex" ++ expect * 1010 FetchError "chunked header non-hex" ++} -start ++ ++client c1 { ++ non_fatal ++ txreq -req POST -hdr "Transfer-encoding: chunked" ++ send "1\r\n" ++ send "This is more than one byte of data\r\n" ++ send "0\r\n" ++ send "\r\n" ++ fatal ++ rxresp ++ expect resp.status == 503 ++ expect_close ++} -run ++ ++client c2 { ++ non_fatal ++ txreq -req POST -hdr "Transfer-encoding: chunked" ++ send "1\r\n" ++ send "Z 2\r\n" ++ send "3d\r\n" ++ send "0\r\n\r\nPOST /evil HTTP/1.1\r\nHost: whatever\r\nContent-Length: 5\r\n\r\n" ++ send "0\r\n" ++ send "\r\n" ++ fatal ++ rxresp ++ expect resp.status == 503 ++ expect_close ++} -run ++ ++client c3 { ++ non_fatal ++ txreq -req POST -hdr "Transfer-encoding: chunked" ++ send "d\r\n" ++ send "Spurious CRLF\r\n\r\n" ++ send "0\r\n" ++ send "\r\n" ++ fatal ++ rxresp ++ expect resp.status == 503 ++ expect_close ++} -run ++ ++client c4 { ++ non_fatal ++ txreq -req POST -hdr "Transfer-encoding: chunked" ++ send "\n0\r\n" ++ send "\r\n" ++ fatal ++ rxresp ++ expect resp.status == 503 ++ expect_close ++} -run ++ ++logexpect l1 -wait +diff --git a/bin/varnishtest/tests/r01184.vtc b/bin/varnishtest/tests/r01184.vtc +index 0988e65..94ecd3c 100644 +--- a/bin/varnishtest/tests/r01184.vtc ++++ b/bin/varnishtest/tests/r01184.vtc +@@ -62,6 +62,7 @@ server s1 { + sendhex " 10 45 f3 a9 83 b8 18 1c 7b c2 30 55 04 17 13 c4" + sendhex " 0f 07 5f 7a 38 f4 8e 50 b3 37 d4 3a 32 4a 34 07" + sendhex " FF FF FF FF FF FF FF FF 72 ea 06 5f b3 1c fa dd" ++ send "\n" + expect_close + } -start + +@@ -93,6 +94,7 @@ server s1 { + sendhex " 10 45 f3 a9 83 b8 18 1c 7b c2 30 55 04 17 13 c4" + sendhex " 0f 07 5f 7a 38 f4 8e 50 b3 37 d4 3a 32 4a 34 07" + sendhex " FF FF FF FF FF FF FF FF 72 ea 06 5f b3 1c fa dd" ++ send "\n" + expect_close + } -start + +diff --git a/bin/varnishtest/tests/r01506.vtc b/bin/varnishtest/tests/r01506.vtc +index 96b7b54..f7f89a7 100644 +--- a/bin/varnishtest/tests/r01506.vtc ++++ b/bin/varnishtest/tests/r01506.vtc +@@ -7,15 +7,15 @@ server s0 { + txresp -nolen \ + -hdr "Transfer-Encoding: chunked" \ + -hdr "Connection: close" +- send "11\r\n0_23456789abcdef\n" +- send "11\r\n1_23456789abcdef\n" +- send "11\r\n2_23456789abcdef\n" +- send "11\r\n3_23456789abcdef\n" ++ send "11\r\n0_23456789abcdef\n\n" ++ send "11\r\n1_23456789abcdef\n\n" ++ send "11\r\n2_23456789abcdef\n\n" ++ send "11\r\n3_23456789abcdef\n\n" + barrier b1 sync +- send "11\r\n4_23456789abcdef\n" +- send "11\r\n5_23456789abcdef\n" +- send "11\r\n6_23456789abcdef\n" +- send "11\r\n7_23456789abcdef\n" ++ send "11\r\n4_23456789abcdef\n\n" ++ send "11\r\n5_23456789abcdef\n\n" ++ send "11\r\n6_23456789abcdef\n\n" ++ send "11\r\n7_23456789abcdef\n\n" + chunkedlen 0 + + } -dispatch +diff --git a/bin/varnishtest/tests/r01729.vtc b/bin/varnishtest/tests/r01729.vtc +index 883a60c..f6a01e9 100644 +--- a/bin/varnishtest/tests/r01729.vtc ++++ b/bin/varnishtest/tests/r01729.vtc +@@ -11,7 +11,7 @@ server s1 { + send "\r\n" + send "14\r\n" + send "0123456789" +- send "0123456789" ++ send "0123456789\n" + send "0\r\n" + send "\r\n" + +@@ -29,7 +29,7 @@ client c1 { + send "\r\n" + send "14\r\n" + send "0123456789" +- send "0123456789" ++ send "0123456789\n" + send "0\r\n" + send "\r\n" + +@@ -45,7 +45,7 @@ client c1 { + send "\r\n" + send "14\r\n" + send "0123456789" +- send "0123456789" ++ send "0123456789\n" + send "0\r\n" + send "\r\n" + diff --git a/varnish.spec b/varnish.spec index aa5753d..58d92c1 100644 --- a/varnish.spec +++ b/varnish.spec @@ -37,12 +37,16 @@ Summary: High-performance HTTP accelerator Name: varnish Version: 7.6.1 -Release: 2%{?dist} +Release: 3%{?dist} License: BSD-2-Clause AND (BSD-2-Clause-FreeBSD AND BSD-3-Clause AND LicenseRef-Fedora-Public-Domain AND Zlib) URL: https://www.varnish-cache.org/ Source0: http://varnish-cache.org/_downloads/%{name}-%{version}.tgz Source1: https://github.com/varnishcache/pkg-varnish-cache/archive/%{commit1}.tar.gz#/pkg-varnish-cache-%{shortcommit1}.tar.gz +# Patches: +# https://bugzilla.redhat.com/show_bug.cgi?id=2364235 +Patch100: varnish-7.6.1-CVE-2025-47905.patch + %if 0%{?fedora} > 29 || 0%{?rhel} > 7 Provides: varnish%{_isa} = %{version}-%{release} Provides: varnishd(abi)%{_isa} = %{abi} @@ -146,6 +150,8 @@ ln -s pkg-varnish-cache-%{commit1}/debian debian cp redhat/find-provides . sed -i 's,rst2man-3.6,rst2man-3.4,g; s,rst2html-3.6,rst2html-3.4,g; s,phinx-build-3.6,phinx-build-3.4,g' configure +%patch 100 -p1 -b .CVE-2022-45060 + %build %if %{with system_allocator} export CFLAGS="%{optflags}" @@ -313,6 +319,9 @@ test -f /etc/varnish/secret || (uuidgen > /etc/varnish/secret && chmod 0600 /etc %changelog +* Tue May 20 2025 Luboš Uhliarik - 7.6.1-3 +- Resolves: RHEL-89691 - varnish: request smuggling attacks (CVE-2025-47905) + * Wed Jan 22 2025 Luboš Uhliarik - 7.6.1-2 - Resolves: RHEL-59267 - varnish rebase to 7.6.1