From d84f9ceec382d88083efcfcd97e163d7467266b2 Mon Sep 17 00:00:00 2001 From: Robert Scott Date: Sun, 23 Jul 2023 21:38:58 +0100 Subject: [PATCH] trafficserver: add patches for CVE-2022-47184, CVE-2023-33933 & CVE-2023-30631 --- .../trafficserver/9.1.4-CVE-2023-30631.patch | 168 ++++++++++++++++ .../trafficserver/9.1.4-CVE-2023-33933.patch | 188 ++++++++++++++++++ pkgs/servers/http/trafficserver/default.nix | 10 + 3 files changed, 366 insertions(+) create mode 100644 pkgs/servers/http/trafficserver/9.1.4-CVE-2023-30631.patch create mode 100644 pkgs/servers/http/trafficserver/9.1.4-CVE-2023-33933.patch diff --git a/pkgs/servers/http/trafficserver/9.1.4-CVE-2023-30631.patch b/pkgs/servers/http/trafficserver/9.1.4-CVE-2023-30631.patch new file mode 100644 index 000000000000..6fe3dd1fe0ec --- /dev/null +++ b/pkgs/servers/http/trafficserver/9.1.4-CVE-2023-30631.patch @@ -0,0 +1,168 @@ +Based on upstream ee46128fc7099956145be2147e4ddad7fbc7299b, with some +adjustments to apply cleanly and stale proxy tests omitted as they +didn't exist in 9.1.x + +diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc +index b8780ddac..2809e752c 100644 +--- a/proxy/http/HttpSM.cc ++++ b/proxy/http/HttpSM.cc +@@ -922,6 +922,13 @@ HttpSM::state_read_client_request_header(int event, void *data) + } + } + ++ if (t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_PUSH && ++ t_state.http_config_param->push_method_enabled == 0) { ++ SMDebug("http", "Rejecting PUSH request because push_method_enabled is 0."); ++ call_transact_and_set_next_state(HttpTransact::Forbidden); ++ return 0; ++ } ++ + // Call to ensure the content-length and transfer_encoding elements in client_request are filled in + HttpTransact::set_client_request_state(&t_state, &t_state.hdr_info.client_request); + +diff --git a/tests/gold_tests/bigobj/bigobj.test.py b/tests/gold_tests/bigobj/bigobj.test.py +index a287ac18b..9282883b2 100644 +--- a/tests/gold_tests/bigobj/bigobj.test.py ++++ b/tests/gold_tests/bigobj/bigobj.test.py +@@ -29,7 +29,7 @@ Test.SkipUnless( + Condition.HasCurlFeature('http2') + ) + +-ts = Test.MakeATSProcess("ts", enable_tls=True) ++ts = Test.MakeATSProcess("ts1", enable_tls=True) + + ts.addDefaultSSLFiles() + +@@ -39,8 +39,8 @@ ts.Disk.records_config.update({ + 'proxy.config.http.cache.required_headers': 0, # No required headers for caching + 'proxy.config.http.push_method_enabled': 1, + 'proxy.config.proxy_name': 'Poxy_Proxy', # This will be the server name. +- 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), +- 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), ++ 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, ++ 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, + 'proxy.config.url_remap.remap_required': 0 + }) + +@@ -71,56 +71,82 @@ tr = Test.AddTestRun() + tr.Processes.Default.Command = 'cc ' + Test.TestDirectory + '/check_ramp.c -o check_ramp' + tr.Processes.Default.ReturnCode = 0 + +-tr = Test.AddTestRun() ++tr = Test.AddTestRun("PUSH an object to the cache") + # Delay on readiness of TS IPv4 ssl port +-tr.Processes.Default.StartBefore(Test.Processes.ts) ++tr.Processes.Default.StartBefore(ts) + # + # Put object with URL http://localhost/bigobj in cache using PUSH request. + tr.Processes.Default.Command = ( +- './push_request {} | nc localhost {}'.format(obj_kilobytes, ts.Variables.port) ++ f'./push_request {obj_kilobytes} | nc localhost {ts.Variables.port}' + ) + tr.Processes.Default.ReturnCode = 0 + +-# GET bigobj -- cleartext, HTTP 1.1, IPv4 +-# +-tr = Test.AddTestRun() ++tr = Test.AddTestRun("GET bigobj: cleartext, HTTP/1.1, IPv4") + tr.Processes.Default.Command = ( +- 'curl --verbose --ipv4 --http1.1 --header "Host: localhost"' + +- ' http://localhost:{}/bigobj 2>> log.txt | ./check_ramp {}' +- .format(ts.Variables.port, obj_kilobytes) ++ 'curl --verbose --ipv4 --http1.1 --header "Host: localhost"' ++ f' http://localhost:{ts.Variables.port}/bigobj 2>> log.txt |' ++ f' ./check_ramp {obj_kilobytes}' + ) + tr.Processes.Default.ReturnCode = 0 + +-# GET bigobj -- TLS, HTTP 1.1, IPv4 +-# +-tr = Test.AddTestRun() ++tr = Test.AddTestRun("GET bigobj: TLS, HTTP/1.1, IPv4") + tr.Processes.Default.Command = ( +- 'curl --verbose --ipv4 --http1.1 --insecure --header "Host: localhost"' + +- ' https://localhost:{}/bigobj 2>> log.txt | ./check_ramp {}' +- .format(ts.Variables.ssl_port, obj_kilobytes) ++ 'curl --verbose --ipv4 --http1.1 --insecure --header "Host: localhost"' ++ f' https://localhost:{ts.Variables.ssl_port}/bigobj 2>> log.txt |' ++ f' ./check_ramp {obj_kilobytes}' + ) + tr.Processes.Default.ReturnCode = 0 + +-# GET bigobj -- TLS, HTTP 2, IPv4 +-# +-tr = Test.AddTestRun() ++tr = Test.AddTestRun("GET bigobj: TLS, HTTP/2, IPv4") + tr.Processes.Default.Command = ( +- 'curl --verbose --ipv4 --http2 --insecure --header "Host: localhost"' + +- ' https://localhost:{}/bigobj 2>> log.txt | ./check_ramp {}' +- .format(ts.Variables.ssl_port, obj_kilobytes) ++ 'curl --verbose --ipv4 --http2 --insecure --header "Host: localhost"' ++ f' https://localhost:{ts.Variables.ssl_port}/bigobj 2>> log.txt |' ++ f' ./check_ramp {obj_kilobytes}' + ) + tr.Processes.Default.ReturnCode = 0 + +-# GET bigobj -- TLS, HTTP 2, IPv6 +-# +-tr = Test.AddTestRun() ++tr = Test.AddTestRun("GET bigobj: TLS, HTTP/2, IPv6") + tr.Processes.Default.Command = ( +- 'curl --verbose --ipv6 --http2 --insecure --header "Host: localhost"' + +- ' https://localhost:{}/bigobj 2>> log.txt | ./check_ramp {}' +- .format(ts.Variables.ssl_portv6, obj_kilobytes) ++ 'curl --verbose --ipv6 --http2 --insecure --header "Host: localhost"' ++ f' https://localhost:{ts.Variables.ssl_portv6}/bigobj 2>> log.txt |' ++ f' ./check_ramp {obj_kilobytes}' + ) + tr.Processes.Default.ReturnCode = 0 + + tr = Test.AddTestRun() + tr.Processes.Default.Command = "sed 's/0 log2.txt" + tr.Processes.Default.ReturnCode = 0 ++ ++# Verify that PUSH requests are rejected when push_method_enabled is 0 (the ++# default configuration). ++ts = Test.MakeATSProcess("ts2", enable_tls=True) ++ts.addDefaultSSLFiles() ++ ++ts.Disk.records_config.update({ ++ 'proxy.config.diags.debug.enabled': 1, ++ 'proxy.config.diags.debug.tags': 'http|dns|cache', ++ 'proxy.config.http.cache.required_headers': 0, # No required headers for caching ++ 'proxy.config.proxy_name': 'Poxy_Proxy', # This will be the server name. ++ 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, ++ 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, ++ 'proxy.config.url_remap.remap_required': 0 ++}) ++ ++ts.Disk.ssl_multicert_config.AddLine( ++ 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key' ++) ++ ++ts.Disk.remap_config.AddLine( ++ 'map https://localhost http://localhost' ++) ++ ++tr = Test.AddTestRun("PUSH request is rejected when push_method_enabled is 0") ++tr.Processes.Default.StartBefore(ts) ++tr.Processes.Default.Command = ( ++ f'./push_request {obj_kilobytes} | nc localhost {ts.Variables.port}' ++) ++tr.Processes.Default.ReturnCode = 1 ++tr.Processes.Default.Streams.stdout = Testers.ContainsExpression( ++ "403 Access Denied", ++ "The PUSH request should have received a 403 response." ++) +diff --git a/tests/gold_tests/ip_allow/ip_allow.test.py b/tests/gold_tests/ip_allow/ip_allow.test.py +index d4f41c8cc..33951c426 100644 +--- a/tests/gold_tests/ip_allow/ip_allow.test.py ++++ b/tests/gold_tests/ip_allow/ip_allow.test.py +@@ -86,6 +86,7 @@ ts.Disk.ssl_multicert_config.AddLine( + ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'ip-allow', ++ 'proxy.config.http.push_method_enabled': 1, + 'proxy.config.http.connect_ports': '{0}'.format(server.Variables.SSL_Port), + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), diff --git a/pkgs/servers/http/trafficserver/9.1.4-CVE-2023-33933.patch b/pkgs/servers/http/trafficserver/9.1.4-CVE-2023-33933.patch new file mode 100644 index 000000000000..aeaad48f5de4 --- /dev/null +++ b/pkgs/servers/http/trafficserver/9.1.4-CVE-2023-33933.patch @@ -0,0 +1,188 @@ +Based on upstream 726a79cb2f70fcbe0e2139aab3fe56930d3d8c27, adapted +to avoid merge conflict from 034aab400ff79105dd7ca32f1c264d664414af90 + +diff --git a/plugins/s3_auth/aws_auth_v4.cc b/plugins/s3_auth/aws_auth_v4.cc +index 3f9aea077..6ba76d813 100644 +--- a/plugins/s3_auth/aws_auth_v4.cc ++++ b/plugins/s3_auth/aws_auth_v4.cc +@@ -303,6 +303,11 @@ getCanonicalRequestSha256Hash(TsInterface &api, bool signPayload, const StringSe + str = api.getPath(&length); + String path("/"); + path.append(str, length); ++ str = api.getParams(&length); ++ if (length > 0) { ++ path.append(";", 1); ++ path.append(str, length); ++ } + String canonicalUri = canonicalEncode(path, /* isObjectName */ true); + sha256Update(&canonicalRequestSha256Ctx, canonicalUri); + sha256Update(&canonicalRequestSha256Ctx, "\n"); +diff --git a/plugins/s3_auth/aws_auth_v4.h b/plugins/s3_auth/aws_auth_v4.h +index 865a19938..984bc6240 100644 +--- a/plugins/s3_auth/aws_auth_v4.h ++++ b/plugins/s3_auth/aws_auth_v4.h +@@ -47,6 +47,7 @@ public: + virtual const char *getMethod(int *length) = 0; + virtual const char *getHost(int *length) = 0; + virtual const char *getPath(int *length) = 0; ++ virtual const char *getParams(int *length) = 0; + virtual const char *getQuery(int *length) = 0; + virtual HeaderIterator headerBegin() = 0; + virtual HeaderIterator headerEnd() = 0; +diff --git a/plugins/s3_auth/aws_auth_v4_wrap.h b/plugins/s3_auth/aws_auth_v4_wrap.h +index 72221c3b8..3ed858a1e 100644 +--- a/plugins/s3_auth/aws_auth_v4_wrap.h ++++ b/plugins/s3_auth/aws_auth_v4_wrap.h +@@ -108,6 +108,11 @@ public: + return TSUrlPathGet(_bufp, _url, len); + } + const char * ++ getParams(int *len) override ++ { ++ return TSUrlHttpParamsGet(_bufp, _url, len); ++ } ++ const char * + getQuery(int *len) override + { + return TSUrlHttpQueryGet(_bufp, _url, len); +diff --git a/plugins/s3_auth/unit_tests/test_aws_auth_v4.cc b/plugins/s3_auth/unit_tests/test_aws_auth_v4.cc +index 595fe003e..a5e5cbc19 100644 +--- a/plugins/s3_auth/unit_tests/test_aws_auth_v4.cc ++++ b/plugins/s3_auth/unit_tests/test_aws_auth_v4.cc +@@ -404,6 +404,7 @@ TEST_CASE("AWSAuthSpecByExample: GET Object", "[AWS][auth][SpecByExample]") + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign("test.txt"); ++ api._params.assign(""); + api._query.assign(""); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Range", "bytes=0-9")); +@@ -449,6 +450,7 @@ TEST_CASE("AWSAuthSpecByExample: GET Bucket Lifecycle", "[AWS][auth][SpecByExamp + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("lifecycle"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("x-amz-content-sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); +@@ -493,6 +495,7 @@ TEST_CASE("AWSAuthSpecByExample: Get Bucket List Objects", "[AWS][auth][SpecByEx + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("x-amz-content-sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); +@@ -584,6 +587,7 @@ TEST_CASE("AWSAuthSpecByExample: GET Bucket List Objects, unsigned pay-load, exc + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("x-amz-content-sha256", "UNSIGNED-PAYLOAD")); +@@ -633,6 +637,7 @@ TEST_CASE("AWSAuthSpecByExample: GET Bucket List Objects, query param value alre + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign("PATH=="); ++ api._params.assign(""); + api._query.assign("key=TEST=="); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("x-amz-content-sha256", "UNSIGNED-PAYLOAD")); +@@ -679,6 +684,7 @@ TEST_CASE("S3AuthV4UtilParams: signing multiple same name fields", "[AWS][auth][ + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -743,6 +749,7 @@ TEST_CASE("S3AuthV4UtilParams: include all headers by default", "[AWS][auth][uti + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -777,6 +784,7 @@ TEST_CASE("S3AuthV4UtilParams: include all headers explicit", "[AWS][auth][SpecB + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -847,6 +855,7 @@ TEST_CASE("S3AuthV4UtilParams: include/exclude non overlapping headers", "[AWS][ + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -881,6 +890,7 @@ TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers", "[AWS][auth + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -916,6 +926,7 @@ TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers missing inclu + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -951,6 +962,7 @@ TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers missing exclu + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -989,6 +1001,7 @@ TEST_CASE("S3AuthV4UtilParams: include content type", "[AWS][auth][utility]") + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("Content-Type", "gzip")); +@@ -1022,6 +1035,7 @@ TEST_CASE("S3AuthV4UtilParams: include missing content type", "[AWS][auth][utili + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); ++ api._params.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers.insert(std::make_pair("Host", "examplebucket.s3.amazonaws.com")); + api._headers.insert(std::make_pair("x-amz-content-sha256", "UNSIGNED-PAYLOAD")); +diff --git a/plugins/s3_auth/unit_tests/test_aws_auth_v4.h b/plugins/s3_auth/unit_tests/test_aws_auth_v4.h +index e295d750f..e4eb4549c 100644 +--- a/plugins/s3_auth/unit_tests/test_aws_auth_v4.h ++++ b/plugins/s3_auth/unit_tests/test_aws_auth_v4.h +@@ -95,6 +95,12 @@ public: + return _path.c_str(); + } + const char * ++ getParams(int *length) ++ { ++ *length = _params.length(); ++ return _params.c_str(); ++ } ++ const char * + getQuery(int *length) + { + *length = _query.length(); +@@ -114,6 +120,7 @@ public: + String _method; + String _host; + String _path; ++ String _params; + String _query; + HeaderMultiMap _headers; + }; diff --git a/pkgs/servers/http/trafficserver/default.nix b/pkgs/servers/http/trafficserver/default.nix index 48bb698fc3fd..b47d71a1ab74 100644 --- a/pkgs/servers/http/trafficserver/default.nix +++ b/pkgs/servers/http/trafficserver/default.nix @@ -64,6 +64,16 @@ stdenv.mkDerivation rec { url = "https://github.com/apache/trafficserver/commit/19d3af481cf74c91fbf713fc9d2f8b138ed5fbaf.diff"; sha256 = "0z1ikgpp00rzrrcqh97931586yn9wbksgai9xlkcjd5cg8gq0150"; }) + + (fetchpatch { + # included in case any non-nixos users base their configuration on the + # default ip_allow + name = "CVE-2022-47184.patch"; + url = "https://github.com/apache/trafficserver/commit/c371b7b21a7e774f852af86b85c87d5d877a14bd.patch"; + sha256 = "sha256-DVzDvdT0i48fA9zTP6iCDXCiy0/wr3LlcpGJU19tq6Q="; + }) + ./9.1.4-CVE-2023-33933.patch + ./9.1.4-CVE-2023-30631.patch ]; # NOTE: The upstream README indicates that flex is needed for some features,