import CS git fence-agents-4.2.1-129.el8_10.27
This commit is contained in:
parent
91224529c2
commit
4a3fe1fe12
312
SOURCES/RHEL-193803-fix-bundled-httplib2-CVE-2026-59939.patch
Normal file
312
SOURCES/RHEL-193803-fix-bundled-httplib2-CVE-2026-59939.patch
Normal 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
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user