From 585619a59f217688811fc2d2a40f2e6468ba4ef9 Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Mon, 30 Mar 2026 10:24:59 -0400 Subject: [PATCH] import CS fence-agents-4.10.0-110.el9 --- ..._powervs-update-api-type-description.patch | 59 ++ ...nce_gce-make-zone-parameter-optional.patch | 29 + ...8926-2-fence_gce-fix-node-list-limit.patch | 43 + ...9-fix-bundled-urllib3-CVE-2025-66418.patch | 68 ++ ...9-fix-bundled-urllib3-CVE-2025-66471.patch | 845 ++++++++++++++++++ ...2-fix-bundled-urllib3-CVE-2026-21441.patch | 95 ++ ...60-fix-bundled-pyasn1-CVE-2026-23490.patch | 28 + ...8-fence_ibm_vpc-fix-missing-statuses.patch | 22 + ...1-fix-bundled-urllib3-CVE-2024-37891.patch | 32 + ...2-fix-bundled-urllib3-CVE-2025-66418.patch | 22 + ...3-fix-bundled-urllib3-CVE-2025-66471.patch | 281 ++++++ ...2-fix-bundled-urllib3-CVE-2026-21441.patch | 31 + ...1-fix-bundled-urllib3-CVE-2024-37891.patch | 32 + ...2-fix-bundled-urllib3-CVE-2025-66418.patch | 22 + ...3-fix-bundled-urllib3-CVE-2025-66471.patch | 281 ++++++ ...2-fix-bundled-urllib3-CVE-2026-21441.patch | 31 + ...-5-fix-bundled-pyasn1-CVE-2026-23490.patch | 28 + ...2-fix-bundled-urllib3-CVE-2024-37891.patch | 16 - SPECS/fence-agents.spec | 74 +- 19 files changed, 2022 insertions(+), 17 deletions(-) create mode 100644 SOURCES/RHEL-114753-fence_ibm_powervs-update-api-type-description.patch create mode 100644 SOURCES/RHEL-128926-1-fence_gce-make-zone-parameter-optional.patch create mode 100644 SOURCES/RHEL-128926-2-fence_gce-fix-node-list-limit.patch create mode 100644 SOURCES/RHEL-136069-fix-bundled-urllib3-CVE-2025-66418.patch create mode 100644 SOURCES/RHEL-139799-fix-bundled-urllib3-CVE-2025-66471.patch create mode 100644 SOURCES/RHEL-140796-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch create mode 100644 SOURCES/RHEL-142460-fix-bundled-pyasn1-CVE-2026-23490.patch create mode 100644 SOURCES/RHEL-145088-fence_ibm_vpc-fix-missing-statuses.patch create mode 100644 SOURCES/RHEL-146344-ibm-1-fix-bundled-urllib3-CVE-2024-37891.patch create mode 100644 SOURCES/RHEL-146344-ibm-2-fix-bundled-urllib3-CVE-2025-66418.patch create mode 100644 SOURCES/RHEL-146344-ibm-3-fix-bundled-urllib3-CVE-2025-66471.patch create mode 100644 SOURCES/RHEL-146344-ibm-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch create mode 100644 SOURCES/RHEL-146344-kubevirt-1-fix-bundled-urllib3-CVE-2024-37891.patch create mode 100644 SOURCES/RHEL-146344-kubevirt-2-fix-bundled-urllib3-CVE-2025-66418.patch create mode 100644 SOURCES/RHEL-146344-kubevirt-3-fix-bundled-urllib3-CVE-2025-66471.patch create mode 100644 SOURCES/RHEL-146344-kubevirt-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch create mode 100644 SOURCES/RHEL-146344-kubevirt-5-fix-bundled-pyasn1-CVE-2026-23490.patch diff --git a/SOURCES/RHEL-114753-fence_ibm_powervs-update-api-type-description.patch b/SOURCES/RHEL-114753-fence_ibm_powervs-update-api-type-description.patch new file mode 100644 index 0000000..1a7788c --- /dev/null +++ b/SOURCES/RHEL-114753-fence_ibm_powervs-update-api-type-description.patch @@ -0,0 +1,59 @@ +From f0b9a79b4c17ba3d98cf4dad9e15391595535f90 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Mon, 25 Aug 2025 13:28:59 +0200 +Subject: [PATCH] fence_ibm_powervs: update description of api-type parameter + to show correct default value + +--- + agents/ibm_powervs/fence_ibm_powervs.py | 6 +++--- + tests/data/metadata/fence_ibm_powervs.xml | 8 ++++---- + 2 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py +index d408e8727..b4d3bdc32 100755 +--- a/agents/ibm_powervs/fence_ibm_powervs.py ++++ b/agents/ibm_powervs/fence_ibm_powervs.py +@@ -258,9 +258,10 @@ def define_new_opts(): + all_opt["api-type"] = { + "getopt" : ":", + "longopt" : "api-type", +- "help" : "--api-type=[public|private] API-type: 'public' (default) or 'private'", ++ "help" : "--api-type=[private|public] API-type: 'private' (default) or 'public'", + "required" : "0", +- "shortdesc" : "API-type (public|private)", ++ "shortdesc" : "API-type (private|public)", ++ "default" : "private", + "order" : 0 + } + all_opt["proxy"] = { +@@ -293,7 +294,6 @@ def main(): + all_opt["power_timeout"]["default"] = "120" + all_opt["power_wait"]["default"] = "15" + all_opt["stonith_status_sleep"]["default"] = "10" +- all_opt["api-type"]["default"] = "private" + all_opt["proxy"]["default"] = "" + + options = check_input(device_opt, process_input(device_opt)) +diff --git a/tests/data/metadata/fence_ibm_powervs.xml b/tests/data/metadata/fence_ibm_powervs.xml +index ec59e1b53..2298d60cd 100644 +--- a/tests/data/metadata/fence_ibm_powervs.xml ++++ b/tests/data/metadata/fence_ibm_powervs.xml +@@ -4,14 +4,14 @@ + https://www.ibm.com + + +- ++ + +- API-type (public|private) ++ API-type (private|public) + + +- ++ + +- API-type (public|private) ++ API-type (private|public) + + + diff --git a/SOURCES/RHEL-128926-1-fence_gce-make-zone-parameter-optional.patch b/SOURCES/RHEL-128926-1-fence_gce-make-zone-parameter-optional.patch new file mode 100644 index 0000000..07bae28 --- /dev/null +++ b/SOURCES/RHEL-128926-1-fence_gce-make-zone-parameter-optional.patch @@ -0,0 +1,29 @@ +From 7c1dc4e3935b2246015bba7ba78be4ef7a200423 Mon Sep 17 00:00:00 2001 +From: kj1724 <78624900+kj1724@users.noreply.github.com> +Date: Wed, 18 May 2022 03:57:51 -0400 +Subject: [PATCH] fence_gce: Make zone optional for get_nodes_list (#487) + +--- + agents/gce/fence_gce.py | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/agents/gce/fence_gce.py b/agents/gce/fence_gce.py +index 63c7858f1..2bf4da5a1 100644 +--- a/agents/gce/fence_gce.py ++++ b/agents/gce/fence_gce.py +@@ -135,10 +135,12 @@ def translate_status(instance_status): + + def get_nodes_list(conn, options): + result = {} +- if "--zone" not in options: +- fail_fence_agent(options, "Failed: get_nodes_list: Please specify the --zone in the command") ++ plug = options["--plug"] if "--plug" in options else "" ++ zones = options["--zone"] if "--zone" in options else "" ++ if not zones: ++ zones = get_zone(conn, options, plug) if "--plugzonemap" not in options else options["--plugzonemap"][plug] + try: +- for zone in options["--zone"].split(","): ++ for zone in zones.split(","): + instanceList = retry_api_execute(options, conn.instances().list( + project=options["--project"], + zone=zone)) diff --git a/SOURCES/RHEL-128926-2-fence_gce-fix-node-list-limit.patch b/SOURCES/RHEL-128926-2-fence_gce-fix-node-list-limit.patch new file mode 100644 index 0000000..4910087 --- /dev/null +++ b/SOURCES/RHEL-128926-2-fence_gce-fix-node-list-limit.patch @@ -0,0 +1,43 @@ +From 07285aacbc2194c6a2dfc7dd62dda9a3308bc243 Mon Sep 17 00:00:00 2001 +From: dfellmeth-google +Date: Mon, 17 Nov 2025 04:44:49 -0500 +Subject: [PATCH] fence_gce: fix node list limit (#640) + +This change introduces filtering using the plug to the instances.list method to reduce the number of results returned. + +It also introduces pagination if the list returned is still too large. +--- + agents/gce/fence_gce.py | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/agents/gce/fence_gce.py b/agents/gce/fence_gce.py +index 759567c6f..c7bd2a5dc 100644 +--- a/agents/gce/fence_gce.py ++++ b/agents/gce/fence_gce.py +@@ -150,15 +150,24 @@ def get_nodes_list(conn, options): + result = {} + plug = options["--plug"] if "--plug" in options else "" + zones = options["--zone"] if "--zone" in options else "" ++ filter = "name="+plug if plug != "" else "" ++ max_results = 1 if options.get("--action") == "monitor" else 500 + if not zones: + zones = get_zone(conn, options, plug) if "--plugzonemap" not in options else options["--plugzonemap"][plug] + try: + for zone in zones.split(","): +- instanceList = retry_api_execute(options, conn.instances().list( ++ request = conn.instances().list( + project=options["--project"], +- zone=zone)) ++ zone=zone, ++ filter=filter, ++ maxResults=max_results) ++ while request is not None: ++ instanceList = retry_api_execute(options, request) ++ if "items" not in instanceList: ++ break + for instance in instanceList["items"]: + result[instance["id"]] = (instance["name"], translate_status(instance["status"])) ++ request = conn.instances().list_next(previous_request=request, previous_response=instanceList) + except Exception as err: + fail_fence_agent(options, "Failed: get_nodes_list: {}".format(str(err))) + diff --git a/SOURCES/RHEL-136069-fix-bundled-urllib3-CVE-2025-66418.patch b/SOURCES/RHEL-136069-fix-bundled-urllib3-CVE-2025-66418.patch new file mode 100644 index 0000000..ece497b --- /dev/null +++ b/SOURCES/RHEL-136069-fix-bundled-urllib3-CVE-2025-66418.patch @@ -0,0 +1,68 @@ +--- a/aws/urllib3/response.py 2023-10-17 19:42:56.000000000 +0200 ++++ b/aws/urllib3/response.py 2026-01-02 11:19:25.583808492 +0100 +@@ -135,8 +135,18 @@ + they were applied. + """ + ++ # Maximum allowed number of chained HTTP encodings in the ++ # Content-Encoding header. ++ max_decode_links = 5 ++ + def __init__(self, modes): +- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] ++ encodings = [m.strip() for m in modes.split(",")] ++ if len(encodings) > self.max_decode_links: ++ raise DecodeError( ++ "Too many content encodings in the chain: " ++ f"{len(encodings)} > {self.max_decode_links}" ++ ) ++ self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self): + return self._decoders[0].flush() + +--- a/azure/urllib3/response.py 2023-10-17 19:42:56.000000000 +0200 ++++ b/azure/urllib3/response.py 2026-01-02 11:19:25.583808492 +0100 +@@ -135,8 +135,18 @@ + they were applied. + """ + ++ # Maximum allowed number of chained HTTP encodings in the ++ # Content-Encoding header. ++ max_decode_links = 5 ++ + def __init__(self, modes): +- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] ++ encodings = [m.strip() for m in modes.split(",")] ++ if len(encodings) > self.max_decode_links: ++ raise DecodeError( ++ "Too many content encodings in the chain: " ++ f"{len(encodings)} > {self.max_decode_links}" ++ ) ++ self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self): + return self._decoders[0].flush() + +--- a/google/urllib3/response.py 2023-10-17 19:42:56.000000000 +0200 ++++ b/google/urllib3/response.py 2026-01-02 11:19:25.583808492 +0100 +@@ -135,8 +135,18 @@ + they were applied. + """ + ++ # Maximum allowed number of chained HTTP encodings in the ++ # Content-Encoding header. ++ max_decode_links = 5 ++ + def __init__(self, modes): +- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] ++ encodings = [m.strip() for m in modes.split(",")] ++ if len(encodings) > self.max_decode_links: ++ raise DecodeError( ++ "Too many content encodings in the chain: " ++ f"{len(encodings)} > {self.max_decode_links}" ++ ) ++ self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self): + return self._decoders[0].flush() diff --git a/SOURCES/RHEL-139799-fix-bundled-urllib3-CVE-2025-66471.patch b/SOURCES/RHEL-139799-fix-bundled-urllib3-CVE-2025-66471.patch new file mode 100644 index 0000000..7363272 --- /dev/null +++ b/SOURCES/RHEL-139799-fix-bundled-urllib3-CVE-2025-66471.patch @@ -0,0 +1,845 @@ +--- a/aws/urllib3/response.py 2026-01-20 10:46:57.006470161 +0100 ++++ b/aws/urllib3/response.py 2026-01-20 10:55:44.090084896 +0100 +@@ -23,6 +23,7 @@ + from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, ++ DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, +@@ -41,34 +42,60 @@ + class DeflateDecoder(object): + def __init__(self): + self._first_try = True +- self._data = b"" ++ self._first_try_data = b"" ++ self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): +- if not data: ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ data = self._unfed_data + data ++ self._unfed_data = b"" ++ if not data and not self._obj.unconsumed_tail: + return data ++ original_max_length = max_length ++ if original_max_length < 0: ++ max_length = 0 ++ elif original_max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unfed_data = data ++ return b"" + ++ # Subsequent calls always reuse `self._obj`. zlib requires ++ # passing the unconsumed tail if decompression is to continue. + if not self._first_try: +- return self._obj.decompress(data) ++ return self._obj.decompress( ++ self._obj.unconsumed_tail + data, max_length=max_length ++ ) + +- self._data += data ++ # First call tries with RFC 1950 ZLIB format. ++ self._first_try_data += data + try: +- decompressed = self._obj.decompress(data) ++ decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False +- self._data = None ++ self._first_try_data = b"" + return decompressed ++ # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: +- return self.decompress(self._data) ++ return self.decompress( ++ self._first_try_data, max_length=original_max_length ++ ) + finally: +- self._data = None ++ self._first_try_data = b"" + ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unfed_data) or ( ++ bool(self._obj.unconsumed_tail) and not self._first_try ++ ) + + class GzipDecoderState(object): + +@@ -81,30 +108,64 @@ + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER ++ self._unconsumed_tail = b"" + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() +- if self._state == GzipDecoderState.SWALLOW_DATA or not data: ++ if self._state == GzipDecoderState.SWALLOW_DATA: ++ return bytes(ret) ++ ++ if max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unconsumed_tail += data ++ return b"" ++ ++ # zlib requires passing the unconsumed tail to the subsequent ++ # call if decompression is to continue. ++ data = self._unconsumed_tail + data ++ if not data and self._obj.eof: + return bytes(ret) ++ + while True: + try: +- ret += self._obj.decompress(data) ++ ret += self._obj.decompress( ++ data, max_length=max(max_length - len(ret), 0) ++ ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA ++ self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise +- data = self._obj.unused_data ++ ++ self._unconsumed_tail = data = ( ++ self._obj.unconsumed_tail or self._obj.unused_data ++ ) ++ if max_length > 0 and len(ret) >= max_length: ++ break ++ + if not data: + return bytes(ret) +- self._state = GzipDecoderState.OTHER_MEMBERS +- self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ # When the end of a gzip member is reached, a new decompressor ++ # must be created for unused (possibly future) data. ++ if self._obj.eof: ++ self._state = GzipDecoderState.OTHER_MEMBERS ++ self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ ++ return bytes(ret) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unconsumed_tail) + + + if brotli is not None: +@@ -116,9 +177,35 @@ + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): +- self.decompress = self._obj.decompress ++ setattr(self, "_decompress", self._obj.decompress) + else: +- self.decompress = self._obj.process ++ setattr(self, "_decompress", self._obj.process) ++ ++ # Requires Brotli >= 1.2.0 for `output_buffer_limit`. ++ def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: ++ raise NotImplementedError() ++ ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ try: ++ if max_length > 0: ++ return self._decompress(data, output_buffer_limit=max_length) ++ else: ++ return self._decompress(data) ++ except TypeError: ++ # Fallback for Brotli/brotlicffi/brotlipy versions without ++ # the `output_buffer_limit` parameter. ++ warnings.warn( ++ "Brotli >= 1.2.0 is required to prevent decompression bombs.", ++ DependencyWarning, ++ ) ++ return self._decompress(data) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ try: ++ return not self._obj.can_accept_more_data() ++ except AttributeError: ++ return False + + def flush(self): + if hasattr(self._obj, "flush"): +@@ -151,10 +238,35 @@ + def flush(self): + return self._decoders[0].flush() + +- def decompress(self, data): +- for d in reversed(self._decoders): +- data = d.decompress(data) +- return data ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ if max_length <= 0: ++ for d in reversed(self._decoders): ++ data = d.decompress(data) ++ return data ++ ++ ret = bytearray() ++ # Every while loop iteration goes through all decoders once. ++ # It exits when enough data is read or no more data can be read. ++ # It is possible that the while loop iteration does not produce ++ # any data because we retrieve up to `max_length` from every ++ # decoder, and the amount of bytes may be insufficient for the ++ # next decoder to produce enough/any output. ++ while True: ++ any_data = False ++ for d in reversed(self._decoders): ++ data = d.decompress(data, max_length=max_length - len(ret)) ++ if data: ++ any_data = True ++ # We should not break when no data is returned because ++ # next decoders may produce data even with empty input. ++ ret += data ++ if not any_data or len(ret) >= max_length: ++ return bytes(ret) ++ data = b"" ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return any(d.has_unconsumed_tail for d in self._decoders) + + + def _get_decoder(mode): +@@ -405,16 +517,25 @@ + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + +- def _decode(self, data, decode_content, flush_decoder): ++ def _decode( ++ self, ++ data: bytes, ++ decode_content: bool, ++ flush_decoder: bool, ++ max_length: int = None, ++ ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + ++ if max_length is None or flush_decoder: ++ max_length = -1 ++ + try: + if self._decoder: +- data = self._decoder.decompress(data) ++ data = self._decoder.decompress(data, max_length=max_length) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( +@@ -634,7 +755,10 @@ + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: +- while not is_fp_closed(self._fp): ++ while ( ++ not is_fp_closed(self._fp) ++ or (self._decoder and self._decoder.has_unconsumed_tail) ++ ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: +@@ -840,7 +964,10 @@ + break + chunk = self._handle_chunk(amt) + decoded = self._decode( +- chunk, decode_content=decode_content, flush_decoder=False ++ chunk, ++ decode_content=decode_content, ++ flush_decoder=False, ++ max_length=amt, + ) + if decoded: + yield decoded + +--- a/azure/urllib3/response.py 2026-01-20 10:46:57.006470161 +0100 ++++ b/azure/urllib3/response.py 2026-01-20 10:55:44.090084896 +0100 +@@ -23,6 +23,7 @@ + from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, ++ DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, +@@ -41,34 +42,60 @@ + class DeflateDecoder(object): + def __init__(self): + self._first_try = True +- self._data = b"" ++ self._first_try_data = b"" ++ self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): +- if not data: ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ data = self._unfed_data + data ++ self._unfed_data = b"" ++ if not data and not self._obj.unconsumed_tail: + return data ++ original_max_length = max_length ++ if original_max_length < 0: ++ max_length = 0 ++ elif original_max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unfed_data = data ++ return b"" + ++ # Subsequent calls always reuse `self._obj`. zlib requires ++ # passing the unconsumed tail if decompression is to continue. + if not self._first_try: +- return self._obj.decompress(data) ++ return self._obj.decompress( ++ self._obj.unconsumed_tail + data, max_length=max_length ++ ) + +- self._data += data ++ # First call tries with RFC 1950 ZLIB format. ++ self._first_try_data += data + try: +- decompressed = self._obj.decompress(data) ++ decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False +- self._data = None ++ self._first_try_data = b"" + return decompressed ++ # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: +- return self.decompress(self._data) ++ return self.decompress( ++ self._first_try_data, max_length=original_max_length ++ ) + finally: +- self._data = None ++ self._first_try_data = b"" + ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unfed_data) or ( ++ bool(self._obj.unconsumed_tail) and not self._first_try ++ ) + + class GzipDecoderState(object): + +@@ -81,30 +108,64 @@ + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER ++ self._unconsumed_tail = b"" + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() +- if self._state == GzipDecoderState.SWALLOW_DATA or not data: ++ if self._state == GzipDecoderState.SWALLOW_DATA: ++ return bytes(ret) ++ ++ if max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unconsumed_tail += data ++ return b"" ++ ++ # zlib requires passing the unconsumed tail to the subsequent ++ # call if decompression is to continue. ++ data = self._unconsumed_tail + data ++ if not data and self._obj.eof: + return bytes(ret) ++ + while True: + try: +- ret += self._obj.decompress(data) ++ ret += self._obj.decompress( ++ data, max_length=max(max_length - len(ret), 0) ++ ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA ++ self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise +- data = self._obj.unused_data ++ ++ self._unconsumed_tail = data = ( ++ self._obj.unconsumed_tail or self._obj.unused_data ++ ) ++ if max_length > 0 and len(ret) >= max_length: ++ break ++ + if not data: + return bytes(ret) +- self._state = GzipDecoderState.OTHER_MEMBERS +- self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ # When the end of a gzip member is reached, a new decompressor ++ # must be created for unused (possibly future) data. ++ if self._obj.eof: ++ self._state = GzipDecoderState.OTHER_MEMBERS ++ self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ ++ return bytes(ret) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unconsumed_tail) + + + if brotli is not None: +@@ -116,9 +177,35 @@ + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): +- self.decompress = self._obj.decompress ++ setattr(self, "_decompress", self._obj.decompress) + else: +- self.decompress = self._obj.process ++ setattr(self, "_decompress", self._obj.process) ++ ++ # Requires Brotli >= 1.2.0 for `output_buffer_limit`. ++ def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: ++ raise NotImplementedError() ++ ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ try: ++ if max_length > 0: ++ return self._decompress(data, output_buffer_limit=max_length) ++ else: ++ return self._decompress(data) ++ except TypeError: ++ # Fallback for Brotli/brotlicffi/brotlipy versions without ++ # the `output_buffer_limit` parameter. ++ warnings.warn( ++ "Brotli >= 1.2.0 is required to prevent decompression bombs.", ++ DependencyWarning, ++ ) ++ return self._decompress(data) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ try: ++ return not self._obj.can_accept_more_data() ++ except AttributeError: ++ return False + + def flush(self): + if hasattr(self._obj, "flush"): +@@ -151,10 +238,35 @@ + def flush(self): + return self._decoders[0].flush() + +- def decompress(self, data): +- for d in reversed(self._decoders): +- data = d.decompress(data) +- return data ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ if max_length <= 0: ++ for d in reversed(self._decoders): ++ data = d.decompress(data) ++ return data ++ ++ ret = bytearray() ++ # Every while loop iteration goes through all decoders once. ++ # It exits when enough data is read or no more data can be read. ++ # It is possible that the while loop iteration does not produce ++ # any data because we retrieve up to `max_length` from every ++ # decoder, and the amount of bytes may be insufficient for the ++ # next decoder to produce enough/any output. ++ while True: ++ any_data = False ++ for d in reversed(self._decoders): ++ data = d.decompress(data, max_length=max_length - len(ret)) ++ if data: ++ any_data = True ++ # We should not break when no data is returned because ++ # next decoders may produce data even with empty input. ++ ret += data ++ if not any_data or len(ret) >= max_length: ++ return bytes(ret) ++ data = b"" ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return any(d.has_unconsumed_tail for d in self._decoders) + + + def _get_decoder(mode): +@@ -405,16 +517,25 @@ + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + +- def _decode(self, data, decode_content, flush_decoder): ++ def _decode( ++ self, ++ data: bytes, ++ decode_content: bool, ++ flush_decoder: bool, ++ max_length: int = None, ++ ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + ++ if max_length is None or flush_decoder: ++ max_length = -1 ++ + try: + if self._decoder: +- data = self._decoder.decompress(data) ++ data = self._decoder.decompress(data, max_length=max_length) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( +@@ -634,7 +755,10 @@ + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: +- while not is_fp_closed(self._fp): ++ while ( ++ not is_fp_closed(self._fp) ++ or (self._decoder and self._decoder.has_unconsumed_tail) ++ ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: +@@ -840,7 +964,10 @@ + break + chunk = self._handle_chunk(amt) + decoded = self._decode( +- chunk, decode_content=decode_content, flush_decoder=False ++ chunk, ++ decode_content=decode_content, ++ flush_decoder=False, ++ max_length=amt, + ) + if decoded: + yield decoded + +--- a/google/urllib3/response.py 2026-01-20 10:46:57.006470161 +0100 ++++ b/google/urllib3/response.py 2026-01-20 10:55:44.090084896 +0100 +@@ -23,6 +23,7 @@ + from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, ++ DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, +@@ -41,34 +42,60 @@ + class DeflateDecoder(object): + def __init__(self): + self._first_try = True +- self._data = b"" ++ self._first_try_data = b"" ++ self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): +- if not data: ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ data = self._unfed_data + data ++ self._unfed_data = b"" ++ if not data and not self._obj.unconsumed_tail: + return data ++ original_max_length = max_length ++ if original_max_length < 0: ++ max_length = 0 ++ elif original_max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unfed_data = data ++ return b"" + ++ # Subsequent calls always reuse `self._obj`. zlib requires ++ # passing the unconsumed tail if decompression is to continue. + if not self._first_try: +- return self._obj.decompress(data) ++ return self._obj.decompress( ++ self._obj.unconsumed_tail + data, max_length=max_length ++ ) + +- self._data += data ++ # First call tries with RFC 1950 ZLIB format. ++ self._first_try_data += data + try: +- decompressed = self._obj.decompress(data) ++ decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False +- self._data = None ++ self._first_try_data = b"" + return decompressed ++ # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: +- return self.decompress(self._data) ++ return self.decompress( ++ self._first_try_data, max_length=original_max_length ++ ) + finally: +- self._data = None ++ self._first_try_data = b"" + ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unfed_data) or ( ++ bool(self._obj.unconsumed_tail) and not self._first_try ++ ) + + class GzipDecoderState(object): + +@@ -81,30 +108,64 @@ + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER ++ self._unconsumed_tail = b"" + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() +- if self._state == GzipDecoderState.SWALLOW_DATA or not data: ++ if self._state == GzipDecoderState.SWALLOW_DATA: ++ return bytes(ret) ++ ++ if max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unconsumed_tail += data ++ return b"" ++ ++ # zlib requires passing the unconsumed tail to the subsequent ++ # call if decompression is to continue. ++ data = self._unconsumed_tail + data ++ if not data and self._obj.eof: + return bytes(ret) ++ + while True: + try: +- ret += self._obj.decompress(data) ++ ret += self._obj.decompress( ++ data, max_length=max(max_length - len(ret), 0) ++ ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA ++ self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise +- data = self._obj.unused_data ++ ++ self._unconsumed_tail = data = ( ++ self._obj.unconsumed_tail or self._obj.unused_data ++ ) ++ if max_length > 0 and len(ret) >= max_length: ++ break ++ + if not data: + return bytes(ret) +- self._state = GzipDecoderState.OTHER_MEMBERS +- self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ # When the end of a gzip member is reached, a new decompressor ++ # must be created for unused (possibly future) data. ++ if self._obj.eof: ++ self._state = GzipDecoderState.OTHER_MEMBERS ++ self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ ++ return bytes(ret) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unconsumed_tail) + + + if brotli is not None: +@@ -116,9 +177,35 @@ + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): +- self.decompress = self._obj.decompress ++ setattr(self, "_decompress", self._obj.decompress) + else: +- self.decompress = self._obj.process ++ setattr(self, "_decompress", self._obj.process) ++ ++ # Requires Brotli >= 1.2.0 for `output_buffer_limit`. ++ def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: ++ raise NotImplementedError() ++ ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ try: ++ if max_length > 0: ++ return self._decompress(data, output_buffer_limit=max_length) ++ else: ++ return self._decompress(data) ++ except TypeError: ++ # Fallback for Brotli/brotlicffi/brotlipy versions without ++ # the `output_buffer_limit` parameter. ++ warnings.warn( ++ "Brotli >= 1.2.0 is required to prevent decompression bombs.", ++ DependencyWarning, ++ ) ++ return self._decompress(data) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ try: ++ return not self._obj.can_accept_more_data() ++ except AttributeError: ++ return False + + def flush(self): + if hasattr(self._obj, "flush"): +@@ -151,10 +238,35 @@ + def flush(self): + return self._decoders[0].flush() + +- def decompress(self, data): +- for d in reversed(self._decoders): +- data = d.decompress(data) +- return data ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ if max_length <= 0: ++ for d in reversed(self._decoders): ++ data = d.decompress(data) ++ return data ++ ++ ret = bytearray() ++ # Every while loop iteration goes through all decoders once. ++ # It exits when enough data is read or no more data can be read. ++ # It is possible that the while loop iteration does not produce ++ # any data because we retrieve up to `max_length` from every ++ # decoder, and the amount of bytes may be insufficient for the ++ # next decoder to produce enough/any output. ++ while True: ++ any_data = False ++ for d in reversed(self._decoders): ++ data = d.decompress(data, max_length=max_length - len(ret)) ++ if data: ++ any_data = True ++ # We should not break when no data is returned because ++ # next decoders may produce data even with empty input. ++ ret += data ++ if not any_data or len(ret) >= max_length: ++ return bytes(ret) ++ data = b"" ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return any(d.has_unconsumed_tail for d in self._decoders) + + + def _get_decoder(mode): +@@ -405,16 +517,25 @@ + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + +- def _decode(self, data, decode_content, flush_decoder): ++ def _decode( ++ self, ++ data: bytes, ++ decode_content: bool, ++ flush_decoder: bool, ++ max_length: int = None, ++ ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + ++ if max_length is None or flush_decoder: ++ max_length = -1 ++ + try: + if self._decoder: +- data = self._decoder.decompress(data) ++ data = self._decoder.decompress(data, max_length=max_length) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( +@@ -634,7 +755,10 @@ + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: +- while not is_fp_closed(self._fp): ++ while ( ++ not is_fp_closed(self._fp) ++ or (self._decoder and self._decoder.has_unconsumed_tail) ++ ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: +@@ -840,7 +964,10 @@ + break + chunk = self._handle_chunk(amt) + decoded = self._decode( +- chunk, decode_content=decode_content, flush_decoder=False ++ chunk, ++ decode_content=decode_content, ++ flush_decoder=False, ++ max_length=amt, + ) + if decoded: + yield decoded diff --git a/SOURCES/RHEL-140796-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch b/SOURCES/RHEL-140796-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch new file mode 100644 index 0000000..ce3acbc --- /dev/null +++ b/SOURCES/RHEL-140796-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch @@ -0,0 +1,95 @@ +--- a/aws/urllib3/response.py 2026-02-03 08:20:11.000000000 +0100 ++++ b/aws/urllib3/response.py 2026-02-03 09:11:38.017998476 +0100 +@@ -350,6 +350,7 @@ + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -414,7 +415,11 @@ + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: +- self.read() ++ self.read( ++ # Do not spend resources decoding the content unless ++ # decoding has already been initiated. ++ decode_content=self._has_decoded_content, ++ ) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +@@ -536,6 +541,7 @@ + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + +--- a/azure/urllib3/response.py 2026-02-03 08:20:11.000000000 +0100 ++++ b/azure/urllib3/response.py 2026-02-03 09:11:38.017998476 +0100 +@@ -350,6 +350,7 @@ + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -414,7 +415,11 @@ + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: +- self.read() ++ self.read( ++ # Do not spend resources decoding the content unless ++ # decoding has already been initiated. ++ decode_content=self._has_decoded_content, ++ ) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +@@ -536,6 +541,7 @@ + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + +--- a/google/urllib3/response.py 2026-02-03 08:20:11.000000000 +0100 ++++ b/google/urllib3/response.py 2026-02-03 09:11:38.017998476 +0100 +@@ -350,6 +350,7 @@ + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -414,7 +415,11 @@ + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: +- self.read() ++ self.read( ++ # Do not spend resources decoding the content unless ++ # decoding has already been initiated. ++ decode_content=self._has_decoded_content, ++ ) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +@@ -536,6 +541,7 @@ + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( diff --git a/SOURCES/RHEL-142460-fix-bundled-pyasn1-CVE-2026-23490.patch b/SOURCES/RHEL-142460-fix-bundled-pyasn1-CVE-2026-23490.patch new file mode 100644 index 0000000..4f73e78 --- /dev/null +++ b/SOURCES/RHEL-142460-fix-bundled-pyasn1-CVE-2026-23490.patch @@ -0,0 +1,28 @@ +--- a/google/pyasn1/codec/ber/decoder.py 2019-10-17 07:00:19.000000000 +0200 ++++ b/google/pyasn1/codec/ber/decoder.py 2026-01-27 10:43:12.757563432 +0100 +@@ -22,6 +22,10 @@ + + noValue = base.noValue + ++# Maximum number of continuation octets (high-bit set) allowed per OID arc. ++# 20 octets allows up to 140-bit integers, supporting UUID-based OIDs ++MAX_OID_ARC_CONTINUATION_OCTETS = 20 ++ + + class AbstractDecoder(object): + protoComponent = None +@@ -342,7 +346,14 @@ + # Construct subid from a number of octets + nextSubId = subId + subId = 0 ++ continuationOctetCount = 0 + while nextSubId >= 128: ++ continuationOctetCount += 1 ++ if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS: ++ raise error.PyAsn1Error( ++ 'OID arc exceeds maximum continuation octets limit (%d) ' ++ 'at position %d' % (MAX_OID_ARC_CONTINUATION_OCTETS, index) ++ ) + subId = (subId << 7) + (nextSubId & 0x7F) + if index >= substrateLen: + raise error.SubstrateUnderrunError( diff --git a/SOURCES/RHEL-145088-fence_ibm_vpc-fix-missing-statuses.patch b/SOURCES/RHEL-145088-fence_ibm_vpc-fix-missing-statuses.patch new file mode 100644 index 0000000..341a4cd --- /dev/null +++ b/SOURCES/RHEL-145088-fence_ibm_vpc-fix-missing-statuses.patch @@ -0,0 +1,22 @@ +From fb50ca8a9e552a4b900c1101d49fb4ed9fa4144a Mon Sep 17 00:00:00 2001 +From: Ilias Romanos +Date: Mon, 8 Dec 2025 16:57:48 +0100 +Subject: [PATCH] fence_ibm_vpc: fix missing statuses + +--- + agents/ibm_vpc/fence_ibm_vpc.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py +index a87e9e6dc..46cf9cc87 100755 +--- a/agents/ibm_vpc/fence_ibm_vpc.py ++++ b/agents/ibm_vpc/fence_ibm_vpc.py +@@ -16,6 +16,8 @@ + "stopping": "unknown", + "restarting": "unknown", + "pending": "unknown", ++ "deleting": "unknown", ++ "failed": "unknown", + } + + def get_list(conn, options): diff --git a/SOURCES/RHEL-146344-ibm-1-fix-bundled-urllib3-CVE-2024-37891.patch b/SOURCES/RHEL-146344-ibm-1-fix-bundled-urllib3-CVE-2024-37891.patch new file mode 100644 index 0000000..dd7abb5 --- /dev/null +++ b/SOURCES/RHEL-146344-ibm-1-fix-bundled-urllib3-CVE-2024-37891.patch @@ -0,0 +1,32 @@ +From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001 +From: Quentin Pradet +Date: Mon, 17 Jun 2024 11:09:06 +0400 +Subject: [PATCH] Merge pull request from GHSA-34jh-p97f-mpxf + +* Strip Proxy-Authorization header on redirects + +* Fix test_retry_default_remove_headers_on_redirect + +* Set release date +--- + CHANGES.rst | 5 +++++ + src/urllib3/util/retry.py | 4 +++- + test/test_retry.py | 6 ++++- + test/with_dummyserver/test_poolmanager.py | 27 ++++++++++++++++++++--- + 4 files changed, 37 insertions(+), 5 deletions(-) + +diff --git a/ibm/urllib3/util/retry.py b/ibm/urllib3/util/retry.py +index 7a76a4a6ad..0456cceba4 100644 +--- a/ibm/urllib3/util/retry.py ++++ b/ibm/urllib3/util/retry.py +@@ -189,7 +189,9 @@ class Retry: + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` +- DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"]) ++ DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( ++ ["Cookie", "Authorization", "Proxy-Authorization"] ++ ) + + #: Default maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 diff --git a/SOURCES/RHEL-146344-ibm-2-fix-bundled-urllib3-CVE-2025-66418.patch b/SOURCES/RHEL-146344-ibm-2-fix-bundled-urllib3-CVE-2025-66418.patch new file mode 100644 index 0000000..8592d81 --- /dev/null +++ b/SOURCES/RHEL-146344-ibm-2-fix-bundled-urllib3-CVE-2025-66418.patch @@ -0,0 +1,22 @@ +--- a/ibm/urllib3/response.py 2023-10-17 19:42:56.000000000 +0200 ++++ b/ibm/urllib3/response.py 2026-01-02 11:19:25.583808492 +0100 +@@ -135,8 +135,18 @@ + they were applied. + """ + ++ # Maximum allowed number of chained HTTP encodings in the ++ # Content-Encoding header. ++ max_decode_links = 5 ++ + def __init__(self, modes): +- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] ++ encodings = [m.strip() for m in modes.split(",")] ++ if len(encodings) > self.max_decode_links: ++ raise DecodeError( ++ "Too many content encodings in the chain: " ++ f"{len(encodings)} > {self.max_decode_links}" ++ ) ++ self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self): + return self._decoders[0].flush() diff --git a/SOURCES/RHEL-146344-ibm-3-fix-bundled-urllib3-CVE-2025-66471.patch b/SOURCES/RHEL-146344-ibm-3-fix-bundled-urllib3-CVE-2025-66471.patch new file mode 100644 index 0000000..761b186 --- /dev/null +++ b/SOURCES/RHEL-146344-ibm-3-fix-bundled-urllib3-CVE-2025-66471.patch @@ -0,0 +1,281 @@ +--- a/ibm/urllib3/response.py 2026-01-20 10:46:57.006470161 +0100 ++++ b/ibm/urllib3/response.py 2026-01-20 10:55:44.090084896 +0100 +@@ -23,6 +23,7 @@ + from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, ++ DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, +@@ -41,34 +42,60 @@ + class DeflateDecoder(object): + def __init__(self): + self._first_try = True +- self._data = b"" ++ self._first_try_data = b"" ++ self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): +- if not data: ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ data = self._unfed_data + data ++ self._unfed_data = b"" ++ if not data and not self._obj.unconsumed_tail: + return data ++ original_max_length = max_length ++ if original_max_length < 0: ++ max_length = 0 ++ elif original_max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unfed_data = data ++ return b"" + ++ # Subsequent calls always reuse `self._obj`. zlib requires ++ # passing the unconsumed tail if decompression is to continue. + if not self._first_try: +- return self._obj.decompress(data) ++ return self._obj.decompress( ++ self._obj.unconsumed_tail + data, max_length=max_length ++ ) + +- self._data += data ++ # First call tries with RFC 1950 ZLIB format. ++ self._first_try_data += data + try: +- decompressed = self._obj.decompress(data) ++ decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False +- self._data = None ++ self._first_try_data = b"" + return decompressed ++ # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: +- return self.decompress(self._data) ++ return self.decompress( ++ self._first_try_data, max_length=original_max_length ++ ) + finally: +- self._data = None ++ self._first_try_data = b"" + ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unfed_data) or ( ++ bool(self._obj.unconsumed_tail) and not self._first_try ++ ) + + class GzipDecoderState(object): + +@@ -81,30 +108,64 @@ + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER ++ self._unconsumed_tail = b"" + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() +- if self._state == GzipDecoderState.SWALLOW_DATA or not data: ++ if self._state == GzipDecoderState.SWALLOW_DATA: ++ return bytes(ret) ++ ++ if max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unconsumed_tail += data ++ return b"" ++ ++ # zlib requires passing the unconsumed tail to the subsequent ++ # call if decompression is to continue. ++ data = self._unconsumed_tail + data ++ if not data and self._obj.eof: + return bytes(ret) ++ + while True: + try: +- ret += self._obj.decompress(data) ++ ret += self._obj.decompress( ++ data, max_length=max(max_length - len(ret), 0) ++ ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA ++ self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise +- data = self._obj.unused_data ++ ++ self._unconsumed_tail = data = ( ++ self._obj.unconsumed_tail or self._obj.unused_data ++ ) ++ if max_length > 0 and len(ret) >= max_length: ++ break ++ + if not data: + return bytes(ret) +- self._state = GzipDecoderState.OTHER_MEMBERS +- self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ # When the end of a gzip member is reached, a new decompressor ++ # must be created for unused (possibly future) data. ++ if self._obj.eof: ++ self._state = GzipDecoderState.OTHER_MEMBERS ++ self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ ++ return bytes(ret) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unconsumed_tail) + + + if brotli is not None: +@@ -116,9 +177,35 @@ + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): +- self.decompress = self._obj.decompress ++ setattr(self, "_decompress", self._obj.decompress) + else: +- self.decompress = self._obj.process ++ setattr(self, "_decompress", self._obj.process) ++ ++ # Requires Brotli >= 1.2.0 for `output_buffer_limit`. ++ def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: ++ raise NotImplementedError() ++ ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ try: ++ if max_length > 0: ++ return self._decompress(data, output_buffer_limit=max_length) ++ else: ++ return self._decompress(data) ++ except TypeError: ++ # Fallback for Brotli/brotlicffi/brotlipy versions without ++ # the `output_buffer_limit` parameter. ++ warnings.warn( ++ "Brotli >= 1.2.0 is required to prevent decompression bombs.", ++ DependencyWarning, ++ ) ++ return self._decompress(data) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ try: ++ return not self._obj.can_accept_more_data() ++ except AttributeError: ++ return False + + def flush(self): + if hasattr(self._obj, "flush"): +@@ -151,10 +238,35 @@ + def flush(self): + return self._decoders[0].flush() + +- def decompress(self, data): +- for d in reversed(self._decoders): +- data = d.decompress(data) +- return data ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ if max_length <= 0: ++ for d in reversed(self._decoders): ++ data = d.decompress(data) ++ return data ++ ++ ret = bytearray() ++ # Every while loop iteration goes through all decoders once. ++ # It exits when enough data is read or no more data can be read. ++ # It is possible that the while loop iteration does not produce ++ # any data because we retrieve up to `max_length` from every ++ # decoder, and the amount of bytes may be insufficient for the ++ # next decoder to produce enough/any output. ++ while True: ++ any_data = False ++ for d in reversed(self._decoders): ++ data = d.decompress(data, max_length=max_length - len(ret)) ++ if data: ++ any_data = True ++ # We should not break when no data is returned because ++ # next decoders may produce data even with empty input. ++ ret += data ++ if not any_data or len(ret) >= max_length: ++ return bytes(ret) ++ data = b"" ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return any(d.has_unconsumed_tail for d in self._decoders) + + + def _get_decoder(mode): +@@ -405,16 +517,25 @@ + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + +- def _decode(self, data, decode_content, flush_decoder): ++ def _decode( ++ self, ++ data: bytes, ++ decode_content: bool, ++ flush_decoder: bool, ++ max_length: int = None, ++ ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + ++ if max_length is None or flush_decoder: ++ max_length = -1 ++ + try: + if self._decoder: +- data = self._decoder.decompress(data) ++ data = self._decoder.decompress(data, max_length=max_length) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( +@@ -634,7 +755,10 @@ + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: +- while not is_fp_closed(self._fp): ++ while ( ++ not is_fp_closed(self._fp) ++ or (self._decoder and self._decoder.has_unconsumed_tail) ++ ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: +@@ -840,7 +964,10 @@ + break + chunk = self._handle_chunk(amt) + decoded = self._decode( +- chunk, decode_content=decode_content, flush_decoder=False ++ chunk, ++ decode_content=decode_content, ++ flush_decoder=False, ++ max_length=amt, + ) + if decoded: + yield decoded diff --git a/SOURCES/RHEL-146344-ibm-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch b/SOURCES/RHEL-146344-ibm-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch new file mode 100644 index 0000000..99ee1ca --- /dev/null +++ b/SOURCES/RHEL-146344-ibm-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch @@ -0,0 +1,31 @@ +--- a/ibm/urllib3/response.py 2026-02-03 08:20:11.000000000 +0100 ++++ b/ibm/urllib3/response.py 2026-02-03 09:11:38.017998476 +0100 +@@ -350,6 +350,7 @@ + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -414,7 +415,11 @@ + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: +- self.read() ++ self.read( ++ # Do not spend resources decoding the content unless ++ # decoding has already been initiated. ++ decode_content=self._has_decoded_content, ++ ) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +@@ -536,6 +541,7 @@ + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( diff --git a/SOURCES/RHEL-146344-kubevirt-1-fix-bundled-urllib3-CVE-2024-37891.patch b/SOURCES/RHEL-146344-kubevirt-1-fix-bundled-urllib3-CVE-2024-37891.patch new file mode 100644 index 0000000..f5abf83 --- /dev/null +++ b/SOURCES/RHEL-146344-kubevirt-1-fix-bundled-urllib3-CVE-2024-37891.patch @@ -0,0 +1,32 @@ +From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001 +From: Quentin Pradet +Date: Mon, 17 Jun 2024 11:09:06 +0400 +Subject: [PATCH] Merge pull request from GHSA-34jh-p97f-mpxf + +* Strip Proxy-Authorization header on redirects + +* Fix test_retry_default_remove_headers_on_redirect + +* Set release date +--- + CHANGES.rst | 5 +++++ + src/urllib3/util/retry.py | 4 +++- + test/test_retry.py | 6 ++++- + test/with_dummyserver/test_poolmanager.py | 27 ++++++++++++++++++++--- + 4 files changed, 37 insertions(+), 5 deletions(-) + +diff --git a/kubevirt/urllib3/util/retry.py b/kubevirt/urllib3/util/retry.py +index 7a76a4a6ad..0456cceba4 100644 +--- a/kubevirt/urllib3/util/retry.py ++++ b/kubevirt/urllib3/util/retry.py +@@ -189,7 +189,9 @@ class Retry: + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` +- DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"]) ++ DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( ++ ["Cookie", "Authorization", "Proxy-Authorization"] ++ ) + + #: Default maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 diff --git a/SOURCES/RHEL-146344-kubevirt-2-fix-bundled-urllib3-CVE-2025-66418.patch b/SOURCES/RHEL-146344-kubevirt-2-fix-bundled-urllib3-CVE-2025-66418.patch new file mode 100644 index 0000000..49a9c40 --- /dev/null +++ b/SOURCES/RHEL-146344-kubevirt-2-fix-bundled-urllib3-CVE-2025-66418.patch @@ -0,0 +1,22 @@ +--- a/kubevirt/urllib3/response.py 2023-10-17 19:42:56.000000000 +0200 ++++ b/kubevirt/urllib3/response.py 2026-01-02 11:19:25.583808492 +0100 +@@ -135,8 +135,18 @@ + they were applied. + """ + ++ # Maximum allowed number of chained HTTP encodings in the ++ # Content-Encoding header. ++ max_decode_links = 5 ++ + def __init__(self, modes): +- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] ++ encodings = [m.strip() for m in modes.split(",")] ++ if len(encodings) > self.max_decode_links: ++ raise DecodeError( ++ "Too many content encodings in the chain: " ++ f"{len(encodings)} > {self.max_decode_links}" ++ ) ++ self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self): + return self._decoders[0].flush() diff --git a/SOURCES/RHEL-146344-kubevirt-3-fix-bundled-urllib3-CVE-2025-66471.patch b/SOURCES/RHEL-146344-kubevirt-3-fix-bundled-urllib3-CVE-2025-66471.patch new file mode 100644 index 0000000..b2aa99c --- /dev/null +++ b/SOURCES/RHEL-146344-kubevirt-3-fix-bundled-urllib3-CVE-2025-66471.patch @@ -0,0 +1,281 @@ +--- a/kubevirt/urllib3/response.py 2026-01-20 10:46:57.006470161 +0100 ++++ b/kubevirt/urllib3/response.py 2026-01-20 10:55:44.090084896 +0100 +@@ -23,6 +23,7 @@ + from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, ++ DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, +@@ -41,34 +42,60 @@ + class DeflateDecoder(object): + def __init__(self): + self._first_try = True +- self._data = b"" ++ self._first_try_data = b"" ++ self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): +- if not data: ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ data = self._unfed_data + data ++ self._unfed_data = b"" ++ if not data and not self._obj.unconsumed_tail: + return data ++ original_max_length = max_length ++ if original_max_length < 0: ++ max_length = 0 ++ elif original_max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unfed_data = data ++ return b"" + ++ # Subsequent calls always reuse `self._obj`. zlib requires ++ # passing the unconsumed tail if decompression is to continue. + if not self._first_try: +- return self._obj.decompress(data) ++ return self._obj.decompress( ++ self._obj.unconsumed_tail + data, max_length=max_length ++ ) + +- self._data += data ++ # First call tries with RFC 1950 ZLIB format. ++ self._first_try_data += data + try: +- decompressed = self._obj.decompress(data) ++ decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False +- self._data = None ++ self._first_try_data = b"" + return decompressed ++ # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: +- return self.decompress(self._data) ++ return self.decompress( ++ self._first_try_data, max_length=original_max_length ++ ) + finally: +- self._data = None ++ self._first_try_data = b"" + ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unfed_data) or ( ++ bool(self._obj.unconsumed_tail) and not self._first_try ++ ) + + class GzipDecoderState(object): + +@@ -81,30 +108,64 @@ + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER ++ self._unconsumed_tail = b"" + + def __getattr__(self, name): + return getattr(self._obj, name) + +- def decompress(self, data): ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() +- if self._state == GzipDecoderState.SWALLOW_DATA or not data: ++ if self._state == GzipDecoderState.SWALLOW_DATA: ++ return bytes(ret) ++ ++ if max_length == 0: ++ # We should not pass 0 to the zlib decompressor because 0 is ++ # the default value that will make zlib decompress without a ++ # length limit. ++ # Data should be stored for subsequent calls. ++ self._unconsumed_tail += data ++ return b"" ++ ++ # zlib requires passing the unconsumed tail to the subsequent ++ # call if decompression is to continue. ++ data = self._unconsumed_tail + data ++ if not data and self._obj.eof: + return bytes(ret) ++ + while True: + try: +- ret += self._obj.decompress(data) ++ ret += self._obj.decompress( ++ data, max_length=max(max_length - len(ret), 0) ++ ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA ++ self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise +- data = self._obj.unused_data ++ ++ self._unconsumed_tail = data = ( ++ self._obj.unconsumed_tail or self._obj.unused_data ++ ) ++ if max_length > 0 and len(ret) >= max_length: ++ break ++ + if not data: + return bytes(ret) +- self._state = GzipDecoderState.OTHER_MEMBERS +- self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ # When the end of a gzip member is reached, a new decompressor ++ # must be created for unused (possibly future) data. ++ if self._obj.eof: ++ self._state = GzipDecoderState.OTHER_MEMBERS ++ self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) ++ ++ return bytes(ret) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return bool(self._unconsumed_tail) + + + if brotli is not None: +@@ -116,9 +177,35 @@ + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): +- self.decompress = self._obj.decompress ++ setattr(self, "_decompress", self._obj.decompress) + else: +- self.decompress = self._obj.process ++ setattr(self, "_decompress", self._obj.process) ++ ++ # Requires Brotli >= 1.2.0 for `output_buffer_limit`. ++ def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: ++ raise NotImplementedError() ++ ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ try: ++ if max_length > 0: ++ return self._decompress(data, output_buffer_limit=max_length) ++ else: ++ return self._decompress(data) ++ except TypeError: ++ # Fallback for Brotli/brotlicffi/brotlipy versions without ++ # the `output_buffer_limit` parameter. ++ warnings.warn( ++ "Brotli >= 1.2.0 is required to prevent decompression bombs.", ++ DependencyWarning, ++ ) ++ return self._decompress(data) ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ try: ++ return not self._obj.can_accept_more_data() ++ except AttributeError: ++ return False + + def flush(self): + if hasattr(self._obj, "flush"): +@@ -151,10 +238,35 @@ + def flush(self): + return self._decoders[0].flush() + +- def decompress(self, data): +- for d in reversed(self._decoders): +- data = d.decompress(data) +- return data ++ def decompress(self, data: bytes, max_length: int = -1) -> bytes: ++ if max_length <= 0: ++ for d in reversed(self._decoders): ++ data = d.decompress(data) ++ return data ++ ++ ret = bytearray() ++ # Every while loop iteration goes through all decoders once. ++ # It exits when enough data is read or no more data can be read. ++ # It is possible that the while loop iteration does not produce ++ # any data because we retrieve up to `max_length` from every ++ # decoder, and the amount of bytes may be insufficient for the ++ # next decoder to produce enough/any output. ++ while True: ++ any_data = False ++ for d in reversed(self._decoders): ++ data = d.decompress(data, max_length=max_length - len(ret)) ++ if data: ++ any_data = True ++ # We should not break when no data is returned because ++ # next decoders may produce data even with empty input. ++ ret += data ++ if not any_data or len(ret) >= max_length: ++ return bytes(ret) ++ data = b"" ++ ++ @property ++ def has_unconsumed_tail(self) -> bool: ++ return any(d.has_unconsumed_tail for d in self._decoders) + + + def _get_decoder(mode): +@@ -405,16 +517,25 @@ + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + +- def _decode(self, data, decode_content, flush_decoder): ++ def _decode( ++ self, ++ data: bytes, ++ decode_content: bool, ++ flush_decoder: bool, ++ max_length: int = None, ++ ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + ++ if max_length is None or flush_decoder: ++ max_length = -1 ++ + try: + if self._decoder: +- data = self._decoder.decompress(data) ++ data = self._decoder.decompress(data, max_length=max_length) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( +@@ -634,7 +755,10 @@ + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: +- while not is_fp_closed(self._fp): ++ while ( ++ not is_fp_closed(self._fp) ++ or (self._decoder and self._decoder.has_unconsumed_tail) ++ ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: +@@ -840,7 +964,10 @@ + break + chunk = self._handle_chunk(amt) + decoded = self._decode( +- chunk, decode_content=decode_content, flush_decoder=False ++ chunk, ++ decode_content=decode_content, ++ flush_decoder=False, ++ max_length=amt, + ) + if decoded: + yield decoded diff --git a/SOURCES/RHEL-146344-kubevirt-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch b/SOURCES/RHEL-146344-kubevirt-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch new file mode 100644 index 0000000..5e42f36 --- /dev/null +++ b/SOURCES/RHEL-146344-kubevirt-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch @@ -0,0 +1,31 @@ +--- a/kubevirt/urllib3/response.py 2026-02-03 08:20:11.000000000 +0100 ++++ b/kubevirt/urllib3/response.py 2026-02-03 09:11:38.017998476 +0100 +@@ -350,6 +350,7 @@ + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -414,7 +415,11 @@ + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: +- self.read() ++ self.read( ++ # Do not spend resources decoding the content unless ++ # decoding has already been initiated. ++ decode_content=self._has_decoded_content, ++ ) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +@@ -536,6 +541,7 @@ + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( diff --git a/SOURCES/RHEL-146344-kubevirt-5-fix-bundled-pyasn1-CVE-2026-23490.patch b/SOURCES/RHEL-146344-kubevirt-5-fix-bundled-pyasn1-CVE-2026-23490.patch new file mode 100644 index 0000000..e7ba33d --- /dev/null +++ b/SOURCES/RHEL-146344-kubevirt-5-fix-bundled-pyasn1-CVE-2026-23490.patch @@ -0,0 +1,28 @@ +--- a/kubevirt/pyasn1/codec/ber/decoder.py 2019-10-17 07:00:19.000000000 +0200 ++++ b/kubevirt/pyasn1/codec/ber/decoder.py 2026-01-27 10:43:12.757563432 +0100 +@@ -22,6 +22,10 @@ + + noValue = base.noValue + ++# Maximum number of continuation octets (high-bit set) allowed per OID arc. ++# 20 octets allows up to 140-bit integers, supporting UUID-based OIDs ++MAX_OID_ARC_CONTINUATION_OCTETS = 20 ++ + + class AbstractDecoder(object): + protoComponent = None +@@ -342,7 +346,14 @@ + # Construct subid from a number of octets + nextSubId = subId + subId = 0 ++ continuationOctetCount = 0 + while nextSubId >= 128: ++ continuationOctetCount += 1 ++ if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS: ++ raise error.PyAsn1Error( ++ 'OID arc exceeds maximum continuation octets limit (%d) ' ++ 'at position %d' % (MAX_OID_ARC_CONTINUATION_OCTETS, index) ++ ) + subId = (subId << 7) + (nextSubId & 0x7F) + if index >= substrateLen: + raise error.SubstrateUnderrunError( diff --git a/SOURCES/RHEL-43562-fix-bundled-urllib3-CVE-2024-37891.patch b/SOURCES/RHEL-43562-fix-bundled-urllib3-CVE-2024-37891.patch index 88258d4..84eec46 100644 --- a/SOURCES/RHEL-43562-fix-bundled-urllib3-CVE-2024-37891.patch +++ b/SOURCES/RHEL-43562-fix-bundled-urllib3-CVE-2024-37891.patch @@ -62,19 +62,3 @@ index 7a76a4a6ad..0456cceba4 100644 #: Default maximum backoff time. DEFAULT_BACKOFF_MAX = 120 - -diff --git a/kubevirt/urllib3/util/retry.py b/kubevirt/urllib3/util/retry.py -index 7a76a4a6ad..0456cceba4 100644 ---- a/kubevirt/urllib3/util/retry.py -+++ b/kubevirt/urllib3/util/retry.py -@@ -189,7 +189,9 @@ class Retry: - RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) - - #: Default headers to be used for ``remove_headers_on_redirect`` -- DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"]) -+ DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( -+ ["Cookie", "Authorization", "Proxy-Authorization"] -+ ) - - #: Default maximum backoff time. - DEFAULT_BACKOFF_MAX = 120 diff --git a/SPECS/fence-agents.spec b/SPECS/fence-agents.spec index 73d72dc..e455ec6 100644 --- a/SPECS/fence-agents.spec +++ b/SPECS/fence-agents.spec @@ -57,7 +57,7 @@ Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") Version: 4.10.0 -Release: 98%{?alphatag:.%{alphatag}}%{?dist} +Release: 110%{?alphatag:.%{alphatag}}%{?dist} License: GPLv2+ and LGPLv2+ URL: https://github.com/ClusterLabs/fence-agents Source0: https://fedorahosted.org/releases/f/e/fence-agents/%{name}-%{version}.tar.gz @@ -254,14 +254,32 @@ Patch69: RHEL-13088-fence_sbd-improve-error-handling.patch Patch70: RHEL-82193-fence_kubevirt-force-off.patch Patch71: RHEL-107505-fence_ibm_vpc-add-apikey-file-support.patch Patch72: RHEL-7601-fence_aws-add-skipshutdown-parameter.patch +Patch73: RHEL-114753-fence_ibm_powervs-update-api-type-description.patch +Patch74: RHEL-128926-1-fence_gce-make-zone-parameter-optional.patch +Patch75: RHEL-128926-2-fence_gce-fix-node-list-limit.patch +Patch76: RHEL-145088-fence_ibm_vpc-fix-missing-statuses.patch ### HA support libs/utils ### # all archs Patch1000: bz2217902-1-kubevirt-fix-bundled-dateutil-CVE-2007-4559.patch +Patch1001: RHEL-146344-kubevirt-1-fix-bundled-urllib3-CVE-2024-37891.patch +Patch1002: RHEL-146344-kubevirt-2-fix-bundled-urllib3-CVE-2025-66418.patch +Patch1003: RHEL-146344-kubevirt-3-fix-bundled-urllib3-CVE-2025-66471.patch +Patch1004: RHEL-146344-kubevirt-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch +Patch1005: RHEL-146344-kubevirt-5-fix-bundled-pyasn1-CVE-2026-23490.patch # cloud (x86_64 only) Patch2000: bz2217902-2-aws-azure-fix-bundled-dateutil-CVE-2007-4559.patch Patch2001: RHEL-43562-fix-bundled-urllib3-CVE-2024-37891.patch Patch2002: RHEL-95901-pkg_resources-suppress-UserWarning.patch +Patch2003: RHEL-136069-fix-bundled-urllib3-CVE-2025-66418.patch +Patch2004: RHEL-139799-fix-bundled-urllib3-CVE-2025-66471.patch +Patch2005: RHEL-140796-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch +Patch2006: RHEL-142460-fix-bundled-pyasn1-CVE-2026-23490.patch +# cloud (ppc64le only) +Patch3000: RHEL-146344-ibm-1-fix-bundled-urllib3-CVE-2024-37891.patch +Patch3001: RHEL-146344-ibm-2-fix-bundled-urllib3-CVE-2025-66418.patch +Patch3002: RHEL-146344-ibm-3-fix-bundled-urllib3-CVE-2025-66471.patch +Patch3003: RHEL-146344-ibm-4-RHEL-146282-fix-bundled-urllib3-CVE-2026-21441.patch %global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hpblade ibmblade ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan kdump kubevirt lpar mpath nutanix_ahv redfish rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti %ifarch x86_64 @@ -448,6 +466,10 @@ BuildRequires: %{systemd_units} %patch -p1 -P 70 %patch -p1 -P 71 %patch -p1 -P 72 -F2 +%patch -p1 -P 73 +%patch -p1 -P 74 +%patch -p1 -P 75 +%patch -p1 -P 76 # prevent compilation of something that won't get used anyway sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac @@ -503,11 +525,26 @@ rm -rf kubevirt/rsa* # regular patch doesnt work in build-section pushd support /usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH1000} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH1001} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1002} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1003} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1004} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1005} %ifarch x86_64 /usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH2000} /usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH2001} /usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2002} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2003} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2004} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2005} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2006} +%endif +%ifarch ppc64le +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH3000} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH3001} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH3002} +/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH3003} %endif popd @@ -1568,6 +1605,41 @@ are located on corosync cluster nodes. %endif %changelog +* Tue Feb 10 2026 Oyvind Albrigtsen - 4.10.0-110 +- bundled urllib3: fix CVE-2024-37891, CVE-2025-66418, CVE-2025-66471, + CVE-2026-21441, and pyasn1 CVE-2026-23490 on all archs + Resolves: RHEL-146344 + +* Thu Feb 5 2026 Oyvind Albrigtsen - 4.10.0-108 +- bundled urllib3: fix issue with CVE-2026-21441 patch + Resolves: RHEL-146282 + +* Thu Jan 29 2026 Oyvind Albrigtsen - 4.10.0-107 +- fence_ibm_vpc: fix missing statuses + Resolves: RHEL-145088 + +* Tue Jan 27 2026 Oyvind Albrigtsen - 4.10.0-106 +- bundled pyasn1: fix CVE-2026-23490 + Resolves: RHEL-142460 + +* Tue Jan 20 2026 Oyvind Albrigtsen - 4.10.0-105 +- bundled urllib3: fix CVE-2025-66471 +- bundled urllib3: fix CVE-2026-21441 + Resolves: RHEL-139799, RHEL-140796 + +* Fri Jan 2 2026 Oyvind Albrigtsen - 4.10.0-102 +- bundled urllib3: fix CVE-2025-66418 + Resolves: RHEL-136069 + +* Tue Nov 18 2025 Oyvind Albrigtsen - 4.10.0-101 +- fence_gce: fix node list limit + Resolves: RHEL-128926 + +* Wed Sep 17 2025 Oyvind Albrigtsen - 4.10.0-100 +- fence_ibm_powervs: update description of api-type parameter to show + correct default value + Resolves: RHEL-114753 + * Tue Aug 19 2025 Oyvind Albrigtsen - 4.10.0-98 - fence_aws: add "skip_os_shutdown" parameter to allow hard poweroff Resolves: RHEL-7601