import CS git fence-agents-4.2.1-129.el8_10.27

This commit is contained in:
AlmaLinux RelEng Bot 2026-07-29 09:11:03 -04:00
parent 91224529c2
commit 4a3fe1fe12
2 changed files with 326 additions and 6 deletions

View File

@ -0,0 +1,312 @@
--- a/google/httplib2/__init__.py 2026-07-10 15:00:03.596085630 +0200
+++ b/google/httplib2/__init__.py 2026-07-13 15:19:47.402155330 +0200
@@ -1,6 +1,10 @@
# -*- coding: utf-8 -*-
"""Small, fast HTTP client library for Python."""
+import functools
+
+from httplib2.decode import ZlibDecoder, DecoderProtocol, LimitDecoder, DeflateDecoder
+
__author__ = "Joe Gregorio (joe@bitworking.org)"
__copyright__ = "Copyright 2006, Joe Gregorio"
__contributors__ = [
@@ -349,23 +353,25 @@
return retval
-def _decompressContent(response, new_content):
+def _decompressContent(response, new_content, limit_kwargs):
content = new_content
+ encoding_header = "content-encoding"
+ encoding = response.get(encoding_header, None)
+ limit_wrap = functools.partial(LimitDecoder, **limit_kwargs)
try:
- encoding = response.get("content-encoding", None)
- if encoding in ["gzip", "deflate"]:
- if encoding == "gzip":
- content = gzip.GzipFile(fileobj=io.BytesIO(new_content)).read()
- if encoding == "deflate":
- content = zlib.decompress(content, -zlib.MAX_WBITS)
+ if encoding in ["gzip", "deflate", "zlib"]:
+ try:
+ content = limit_wrap(ZlibDecoder()).consume_bytes(new_content, 0)
+ except (IOError, zlib.error):
+ content = limit_wrap(DeflateDecoder()).consume_bytes(new_content, 0)
response["content-length"] = str(len(content))
# Record the historical presence of the encoding in a way the won't interfere.
- response["-content-encoding"] = response["content-encoding"]
- del response["content-encoding"]
+ response["-content-encoding"] = response.pop(encoding_header)
except (IOError, zlib.error):
content = ""
raise FailedToDecompressContent(
- _("Content purported to be compressed with %s but failed to decompress.") % response.get("content-encoding"),
+ _("Content purported to be compressed with %s but failed to decompress.")
+ % encoding,
response,
content,
)
@@ -1212,6 +1218,10 @@
disable_ssl_certificate_validation=False,
tls_maximum_version=None,
tls_minimum_version=None,
+ decode_limit_hard=None,
+ decode_limit_safe=None,
+ decode_limit_ratio=None,
+ decode_limit_chunk=None,
):
"""If 'cache' is a string then it is used as a directory name for
a disk cache. Otherwise it must be an object that supports the
@@ -1238,7 +1248,12 @@
tls_maximum_version / tls_minimum_version require Python 3.7+ /
OpenSSL 1.1.0g+. A value of "TLSv1_3" requires OpenSSL 1.1.1+.
-"""
+
+ `decode_limit_{hard,safe,ratio,chunk}` options configure `httplib2.decode.LimitDecoder` in attempt order:
+ - Http() argument - top priority
+ - environment httplib2_decode_limit_{hard,safe,ratio,chunk}
+ - LimitDecoder defaults - least priority
+ """
self.proxy_info = proxy_info
self.ca_certs = ca_certs
self.disable_ssl_certificate_validation = disable_ssl_certificate_validation
@@ -1286,6 +1301,22 @@
# Keep Authorization: headers on a redirect.
self.forward_authorization_headers = False
+ limit_kwargs = dict(
+ hard_limit=try_value_or_env(
+ int, decode_limit_hard, "httplib2_decode_limit_hard"
+ ),
+ safe_limit=try_value_or_env(
+ int, decode_limit_safe, "httplib2_decode_limit_safe"
+ ),
+ ratio=try_value_or_env(
+ float, decode_limit_ratio, "httplib2_decode_limit_ratio"
+ ),
+ chunk_size=try_value_or_env(
+ int, decode_limit_chunk, "httplib2_decode_limit_chunk"
+ ),
+ )
+ self.limit_kwargs = {k: v for k, v in limit_kwargs.items() if v is not None}
+
def close(self):
"""Close persistent connections, clear sensitive data.
Not thread-safe, requires external synchronization against concurrent requests.
@@ -1405,7 +1436,7 @@
content = response.read()
response = Response(response)
if method != "HEAD":
- content = _decompressContent(response, content)
+ content = _decompressContent(response, content, self.limit_kwargs)
break
return (response, content)
@@ -1781,3 +1812,16 @@
return self
else:
raise AttributeError(name)
+
+
+def try_value_or_env(to, value, env_key, default=None):
+ candidates = (value, os.environ.get(env_key), os.environ.get(env_key.upper()))
+ # same as `to(x1) or to(x2) or to(x3)` except accepting falsey values like 0
+ for x in candidates:
+ if x is None:
+ continue
+ try:
+ return to(x)
+ except ValueError:
+ pass
+ return default
diff --git a/google/httplib2/decode.py b/google/httplib2/decode.py
new file mode 100644
index 0000000..10540fb
--- /dev/null
+++ b/google/httplib2/decode.py
@@ -0,0 +1,183 @@
+from typing_extensions import Protocol
+import zlib
+
+
+class DecodeRatioError(Exception):
+ """Output-to-input amplification ratio exceeded the configured limit."""
+
+
+class DecodeLimitError(Exception):
+ """Total output length exceeded the hard limit."""
+
+
+class DecoderProtocol(Protocol):
+ @property
+ def needs_input(self) -> bool:
+ ...
+
+ def decode(self, b: bytes) -> bytes:
+ ...
+
+ def flush(self) -> bytes:
+ ...
+
+ def consume_bytes(self, data: bytes, chunk_size: int = 64 << 10) -> bytes:
+ out = bytearray()
+ if chunk_size == 0:
+ chunk_size = len(data)
+ for i in range(0, len(data), chunk_size):
+ chunk = data[i : i + chunk_size]
+ out.extend(self.decode(chunk))
+ out.extend(self.flush())
+ return bytes(out)
+
+
+class ZlibDecoder(DecoderProtocol):
+ """
+ Thin wrapper around zlib.Decompressor conforming to the Decoder interface.
+
+ Note: zlib pushes all available decompressed data immediately upon receiving
+ input. It never holds back output requiring `decode(b"")` to extract it.
+ Thus, `needs_input` naturally remains True.
+ """
+
+ __slots__ = ("_decoder",)
+
+ WBITS_DEFLATE = -15
+ WBITS_ZLIB = 15
+ WBITS_GZIP = 15 | 16
+ WBITS_AUTO_GZIP_ZLIB = 15 | 32 # but not deflate
+
+ def __init__(self, wbits: int = WBITS_AUTO_GZIP_ZLIB):
+ self._decoder: zlib._Decompress | None = zlib.decompressobj(wbits)
+
+ @property
+ def needs_input(self) -> bool:
+ if self._decoder is None:
+ raise RuntimeError("used after flush()")
+ return not self._decoder.eof
+
+ def decode(self, b: bytes) -> bytes:
+ if self._decoder is None:
+ raise RuntimeError("used after flush()")
+ return self._decoder.decompress(b)
+
+ def flush(self) -> bytes:
+ if self._decoder is None:
+ raise RuntimeError("used after flush()")
+ result = self._decoder.flush()
+ self._decoder = None
+ return result
+
+
+def DeflateDecoder() -> ZlibDecoder:
+ return ZlibDecoder(ZlibDecoder.WBITS_DEFLATE)
+
+
+class LimitDecoder(DecoderProtocol):
+ __slots__ = (
+ "_decoder",
+ "_ratio",
+ "_chunk_size",
+ "_safe_limit",
+ "_hard_limit",
+ "_consumed_length",
+ "_output_length",
+ "_input_buffer",
+ "_flushed",
+ )
+
+ def __init__(
+ self,
+ decoder: DecoderProtocol,
+ ratio: float = 100,
+ chunk_size: int = 64 << 10,
+ safe_limit: int = 10 << 20,
+ hard_limit: int = 10 << 30,
+ ) -> None:
+ if ratio < 0:
+ raise ValueError(f"LimitDecoder() ratio={ratio} expected >= 0")
+ if chunk_size < 0:
+ raise ValueError(f"LimitDecoder() chunk_size={chunk_size} expected >= 0")
+ if safe_limit < 0:
+ raise ValueError(f"LimitDecoder() safe_limit={safe_limit} expected >= 0")
+ if hard_limit < 0:
+ raise ValueError(f"LimitDecoder() safe_limit={safe_limit} expected >= 0")
+
+ self._decoder: DecoderProtocol = decoder
+ self._ratio: float = ratio
+ self._chunk_size: int = chunk_size
+ self._safe_limit: int = safe_limit
+ self._hard_limit: int = hard_limit
+ self._consumed_length: int = 0
+ self._output_length: int = 0
+ self._input_buffer: bytearray = bytearray()
+ self._flushed: bool = False
+
+ def _check_limits(self) -> None:
+ if (self._hard_limit > 0) and (self._output_length > self._hard_limit):
+ raise DecodeLimitError(f"Output length {self._output_length} exceeds hard limit {self._hard_limit}")
+ if (self._safe_limit > 0) and (self._output_length < self._safe_limit):
+ return
+ if (self._ratio > 0) and (self._output_length > self._consumed_length * self._ratio):
+ actual_ratio = self._output_length / self._consumed_length if self._consumed_length > 0 else float("inf")
+ raise DecodeRatioError(
+ f"Amplification ratio {actual_ratio:.1f} ({self._output_length}/{self._consumed_length})"
+ f" exceeds limit {self._ratio}"
+ )
+
+ @property
+ def needs_input(self) -> bool:
+ return self._decoder.needs_input
+
+ def decode(self, b: bytes) -> bytes:
+ if self._flushed:
+ raise RuntimeError("decode() called after flush()")
+ output = self._pump(b)
+ return bytes(output)
+
+ def flush(self) -> bytes:
+ if self._flushed:
+ raise RuntimeError("flush() called more than once")
+ self._flushed = True
+
+ output = self._pump(b"")
+
+ data = self._decoder.flush()
+ output.extend(data)
+ self._output_length += len(data)
+ self._check_limits()
+
+ return bytes(output)
+
+ def _pump(self, b: bytes) -> bytearray:
+ self._input_buffer.extend(b)
+
+ output = bytearray()
+ while True:
+ if not self._decoder.needs_input:
+ data = self._decoder.decode(b"")
+ if data:
+ output.extend(data)
+ self._output_length += len(data)
+ self._check_limits()
+ continue
+
+ if self._input_buffer:
+ chunk = bytes(self._input_buffer[: self._chunk_size])
+ del self._input_buffer[: self._chunk_size]
+
+ data = self._decoder.decode(chunk)
+ self._consumed_length += len(chunk)
+
+ if data:
+ output.extend(data)
+ self._output_length += len(data)
+ self._check_limits()
+
+ continue
+
+ # neither input nor decoder progress
+ break
+
+ return output

View File

@ -87,7 +87,7 @@
Name: fence-agents
Summary: Set of unified programs capable of host isolation ("fencing")
Version: 4.2.1
Release: 129%{?alphatag:.%{alphatag}}%{?dist}.26
Release: 129%{?alphatag:.%{alphatag}}%{?dist}.27
License: GPLv2+ and LGPLv2+
Group: System Environment/Base
URL: https://github.com/ClusterLabs/fence-agents
@ -346,6 +346,7 @@ Patch2003: RHEL-109814-2-botocore-add-SkipOsShutdown.patch
Patch2004: RHEL-136027-fix-bundled-urllib3-CVE-2025-66418.patch
Patch2005: RHEL-139756-fix-bundled-urllib3-CVE-2025-66471.patch
Patch2006: RHEL-140783-RHEL-146288-fix-bundled-urllib3-CVE-2026-21441.patch
Patch2007: RHEL-193803-fix-bundled-httplib2-CVE-2026-59939.patch
%if 0%{?fedora} || 0%{?rhel} > 7
%global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hds_cb 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
@ -667,11 +668,6 @@ popd
pushd %{aliyunsdkvpc_dir}
%{__python3} setup.py install -O1 --skip-build --root %{buildroot} --install-lib /usr/lib/fence-agents/%{bundled_lib_dir}/aliyun
popd
# google cloud
## for httplib2 install only
%{__python3} -m pip install --user --no-index --find-links %{_sourcedir} pyparsing
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/google --no-index --find-links %{_sourcedir} httplib2
%endif
# aws/kubevirt
@ -694,6 +690,12 @@ rm -rf %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt/rsa*
# We're unable to `pip install` without cryptography and PyJWT,
# so we delete them and replace them with depencies instead
rm -rf %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/azure/{cryptography*,jwt,PyJWT-*}
# google cloud
## for httplib2 install only
%{__python3} -m pip install --user --no-index --find-links %{_sourcedir} pyparsing
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/google --no-index --find-links %{_sourcedir} httplib2
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/google --no-index --find-links %{_sourcedir} typing_extensions
%endif
# regular patch doesnt work in build-section
@ -718,6 +720,7 @@ pushd %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}
/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}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2007}
%endif
popd
@ -1111,6 +1114,7 @@ Requires: python3-google-api-client
Requires: python3-pysocks
# google cloud
Provides: bundled(python-httplib2) = %{httplib2_version}
Provides: bundled(python3-typing-extensions) = 4.1.1
Obsoletes: %{name} < %{version}-%{release}
BuildArch: noarch
%description gce
@ -1650,6 +1654,10 @@ Fence agent for IBM z/VM over IP.
%endif
%changelog
* Tue Jul 14 2026 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.27
- bundled httplib2: fix CVE-2026-59939
Resolves: RHEL-193803
* Fri Jun 19 2026 Arslan Ahmad <arahmad@redhat.com> - 4.2.1-129.26
- fence_openstack: fix list-action to avoid timeout when
there are 100+ VMs on the hypervisor