import CS fence-agents-4.10.0-110.el9

This commit is contained in:
AlmaLinux RelEng Bot 2026-03-30 10:24:59 -04:00
parent 01545163d1
commit 585619a59f
19 changed files with 2022 additions and 17 deletions

View File

@ -0,0 +1,59 @@
From f0b9a79b4c17ba3d98cf4dad9e15391595535f90 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
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 @@
<vendor-url>https://www.ibm.com</vendor-url>
<parameters>
<parameter name="api-type" unique="0" required="0" deprecated="1">
- <getopt mixed="--api-type=[public|private]" />
+ <getopt mixed="--api-type=[private|public]" />
<content type="string" default="private" />
- <shortdesc lang="en">API-type (public|private)</shortdesc>
+ <shortdesc lang="en">API-type (private|public)</shortdesc>
</parameter>
<parameter name="api_type" unique="0" required="0" obsoletes="api-type">
- <getopt mixed="--api-type=[public|private]" />
+ <getopt mixed="--api-type=[private|public]" />
<content type="string" default="private" />
- <shortdesc lang="en">API-type (public|private)</shortdesc>
+ <shortdesc lang="en">API-type (private|public)</shortdesc>
</parameter>
<parameter name="crn" unique="0" required="1">
<getopt mixed="--crn=[crn]" />

View File

@ -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))

View File

@ -0,0 +1,43 @@
From 07285aacbc2194c6a2dfc7dd62dda9a3308bc243 Mon Sep 17 00:00:00 2001
From: dfellmeth-google <dfellmeth@google.com>
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)))

View File

@ -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()

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -0,0 +1,22 @@
From fb50ca8a9e552a4b900c1101d49fb4ed9fa4144a Mon Sep 17 00:00:00 2001
From: Ilias Romanos <ilias@rwx.gr>
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):

View File

@ -0,0 +1,32 @@
From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001
From: Quentin Pradet <quentin.pradet@gmail.com>
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

View File

@ -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()

View File

@ -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

View File

@ -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(

View File

@ -0,0 +1,32 @@
From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001
From: Quentin Pradet <quentin.pradet@gmail.com>
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

View File

@ -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()

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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 <oalbrigt@redhat.com> - 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 <oalbrigt@redhat.com> - 4.10.0-108
- bundled urllib3: fix issue with CVE-2026-21441 patch
Resolves: RHEL-146282
* Thu Jan 29 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.10.0-107
- fence_ibm_vpc: fix missing statuses
Resolves: RHEL-145088
* Tue Jan 27 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.10.0-106
- bundled pyasn1: fix CVE-2026-23490
Resolves: RHEL-142460
* Tue Jan 20 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 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 <oalbrigt@redhat.com> - 4.10.0-102
- bundled urllib3: fix CVE-2025-66418
Resolves: RHEL-136069
* Tue Nov 18 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.10.0-101
- fence_gce: fix node list limit
Resolves: RHEL-128926
* Wed Sep 17 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 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 <oalbrigt@redhat.com> - 4.10.0-98
- fence_aws: add "skip_os_shutdown" parameter to allow hard poweroff
Resolves: RHEL-7601