import UBI python-kdcproxy-1.1.0-1.el10
This commit is contained in:
parent
828042b536
commit
238356559c
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
|||||||
kdcproxy-1.0.0.tar.gz
|
kdcproxy-1.1.0.tar.gz
|
||||||
|
|||||||
@ -1,206 +0,0 @@
|
|||||||
From 697ba9115366481018dd486e2551f2d23c0addae Mon Sep 17 00:00:00 2001
|
|
||||||
From: Julien Rische <jrische@redhat.com>
|
|
||||||
Date: Fri, 3 Oct 2025 17:39:36 +0200
|
|
||||||
Subject: [PATCH] Fix DoS vulnerability based on unbounded TCP buffering
|
|
||||||
|
|
||||||
In Application.__handle_recv(), the next part of the TCP exchange is
|
|
||||||
received and queued to the io.BytesIO stream. Then, the content of the
|
|
||||||
stream was systematically exported to a buffer. However, this buffer
|
|
||||||
is only used if the data transfer is finished, causing a waste of
|
|
||||||
processing resources if the message is received in multiple parts.
|
|
||||||
|
|
||||||
On top of these unnecessary operations, this function does not handle
|
|
||||||
length limits properly: it accepts to receive chunks of data with both
|
|
||||||
an individual and total length larger than the maximum theoretical
|
|
||||||
length of a Kerberos message, and will continue to wait for data as long
|
|
||||||
as the input stream's length is not exactly the same as the one provided
|
|
||||||
in the header of the response (even if the stream is already longer than
|
|
||||||
the expected length).
|
|
||||||
|
|
||||||
If the kdcproxy service is not protected against DNS discovery abuse,
|
|
||||||
the attacker could take advantage of these problems to operate a
|
|
||||||
denial-of-service attack (CVE-2025-59089).
|
|
||||||
|
|
||||||
After this commit, kdcproxy will interrupt the receiving of a message
|
|
||||||
after it exceeds the maximum length of a Kerberos message or the length
|
|
||||||
indicated in the message header. Also it will only export the content of
|
|
||||||
the input stream to a buffer once the receiving process has ended.
|
|
||||||
|
|
||||||
Signed-off-by: Julien Rische <jrische@redhat.com>
|
|
||||||
(cherry picked from commit 93ba7376098d9a3b6d039475e15778b0ffd024de)
|
|
||||||
---
|
|
||||||
kdcproxy/__init__.py | 51 +++++++++++++++++++-------------
|
|
||||||
tests.py | 70 ++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
2 files changed, 100 insertions(+), 21 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/kdcproxy/__init__.py b/kdcproxy/__init__.py
|
|
||||||
index ce96a0c..d7fb61e 100644
|
|
||||||
--- a/kdcproxy/__init__.py
|
|
||||||
+++ b/kdcproxy/__init__.py
|
|
||||||
@@ -149,6 +149,7 @@ class Application:
|
|
||||||
if self.sock_type(sock) == socket.SOCK_STREAM:
|
|
||||||
# Remove broken TCP socket from readers
|
|
||||||
rsocks.remove(sock)
|
|
||||||
+ read_buffers.pop(sock)
|
|
||||||
else:
|
|
||||||
if reply is not None:
|
|
||||||
return reply
|
|
||||||
@@ -174,7 +175,7 @@ class Application:
|
|
||||||
if self.sock_type(sock) == socket.SOCK_DGRAM:
|
|
||||||
# For UDP sockets, recv() returns an entire datagram
|
|
||||||
# package. KDC sends one datagram as reply.
|
|
||||||
- reply = sock.recv(1048576)
|
|
||||||
+ reply = sock.recv(self.MAX_LENGTH)
|
|
||||||
# If we proxy over UDP, we will be missing the 4-byte
|
|
||||||
# length prefix. So add it.
|
|
||||||
reply = struct.pack("!I", len(reply)) + reply
|
|
||||||
@@ -186,30 +187,38 @@ class Application:
|
|
||||||
if buf is None:
|
|
||||||
read_buffers[sock] = buf = io.BytesIO()
|
|
||||||
|
|
||||||
- part = sock.recv(1048576)
|
|
||||||
- if not part:
|
|
||||||
- # EOF received. Return any incomplete data we have on the theory
|
|
||||||
- # that a decode error is more apparent than silent failure. The
|
|
||||||
- # client will fail faster, at least.
|
|
||||||
- read_buffers.pop(sock)
|
|
||||||
- reply = buf.getvalue()
|
|
||||||
- return reply
|
|
||||||
+ part = sock.recv(self.MAX_LENGTH)
|
|
||||||
+ if part:
|
|
||||||
+ # Data received, accumulate it in a buffer.
|
|
||||||
+ buf.write(part)
|
|
||||||
|
|
||||||
- # Data received, accumulate it in a buffer.
|
|
||||||
- buf.write(part)
|
|
||||||
+ reply = buf.getbuffer()
|
|
||||||
+ if len(reply) < 4:
|
|
||||||
+ # We don't have the length yet.
|
|
||||||
+ return None
|
|
||||||
|
|
||||||
- reply = buf.getvalue()
|
|
||||||
- if len(reply) < 4:
|
|
||||||
- # We don't have the length yet.
|
|
||||||
- return None
|
|
||||||
+ # Got enough data to check if we have the full package.
|
|
||||||
+ (length, ) = struct.unpack("!I", reply[0:4])
|
|
||||||
+ length += 4 # add prefix length
|
|
||||||
|
|
||||||
- # Got enough data to check if we have the full package.
|
|
||||||
- (length, ) = struct.unpack("!I", reply[0:4])
|
|
||||||
- if length + 4 == len(reply):
|
|
||||||
- read_buffers.pop(sock)
|
|
||||||
- return reply
|
|
||||||
+ if length > self.MAX_LENGTH:
|
|
||||||
+ raise ValueError('Message length exceeds the maximum length '
|
|
||||||
+ 'for a Kerberos message (%i > %i)'
|
|
||||||
+ % (length, self.MAX_LENGTH))
|
|
||||||
|
|
||||||
- return None
|
|
||||||
+ if len(reply) > length:
|
|
||||||
+ raise ValueError('Message length exceeds its expected length '
|
|
||||||
+ '(%i > %i)' % (len(reply), length))
|
|
||||||
+
|
|
||||||
+ if len(reply) < length:
|
|
||||||
+ return None
|
|
||||||
+
|
|
||||||
+ # Else (if part is None), EOF was received. Return any incomplete data
|
|
||||||
+ # we have on the theory that a decode error is more apparent than
|
|
||||||
+ # silent failure. The client will fail faster, at least.
|
|
||||||
+
|
|
||||||
+ read_buffers.pop(sock)
|
|
||||||
+ return buf.getvalue()
|
|
||||||
|
|
||||||
def __filter_addr(self, addr):
|
|
||||||
if addr[0] not in (socket.AF_INET, socket.AF_INET6):
|
|
||||||
diff --git a/tests.py b/tests.py
|
|
||||||
index cd82781..2a1ad6e 100644
|
|
||||||
--- a/tests.py
|
|
||||||
+++ b/tests.py
|
|
||||||
@@ -20,6 +20,8 @@
|
|
||||||
# THE SOFTWARE.
|
|
||||||
|
|
||||||
import os
|
|
||||||
+import socket
|
|
||||||
+import struct
|
|
||||||
import unittest
|
|
||||||
from base64 import b64decode
|
|
||||||
try:
|
|
||||||
@@ -122,6 +124,74 @@ class KDCProxyWSGITests(unittest.TestCase):
|
|
||||||
kpasswd=True)
|
|
||||||
self.assertEqual(response.status_code, 503)
|
|
||||||
|
|
||||||
+ @mock.patch("socket.getaddrinfo", return_value=addrinfo)
|
|
||||||
+ @mock.patch("socket.socket")
|
|
||||||
+ def test_tcp_message_length_exceeds_max(self, m_socket, m_getaddrinfo):
|
|
||||||
+ # Test that TCP messages with length > MAX_LENGTH raise ValueError
|
|
||||||
+ # Create a message claiming to be larger than MAX_LENGTH
|
|
||||||
+ max_len = self.app.MAX_LENGTH
|
|
||||||
+ # Length prefix claiming message is larger than allowed
|
|
||||||
+ oversized_length = max_len + 1
|
|
||||||
+ malicious_msg = struct.pack("!I", oversized_length)
|
|
||||||
+
|
|
||||||
+ # Mock socket to return the malicious length prefix
|
|
||||||
+ mock_sock = m_socket.return_value
|
|
||||||
+ mock_sock.recv.return_value = malicious_msg
|
|
||||||
+ mock_sock.getsockopt.return_value = socket.SOCK_STREAM
|
|
||||||
+
|
|
||||||
+ # Manually call the receive method to test it
|
|
||||||
+ read_buffers = {}
|
|
||||||
+ with self.assertRaises(ValueError) as cm:
|
|
||||||
+ self.app._Application__handle_recv(mock_sock, read_buffers)
|
|
||||||
+
|
|
||||||
+ self.assertIn("exceeds the maximum length", str(cm.exception))
|
|
||||||
+ self.assertIn(str(max_len), str(cm.exception))
|
|
||||||
+
|
|
||||||
+ @mock.patch("socket.getaddrinfo", return_value=addrinfo)
|
|
||||||
+ @mock.patch("socket.socket")
|
|
||||||
+ def test_tcp_message_data_exceeds_expected_length(
|
|
||||||
+ self, m_socket, m_getaddrinfo
|
|
||||||
+ ):
|
|
||||||
+ # Test that receiving more data than expected raises ValueError
|
|
||||||
+ # Create a message with length = 100 but send more data
|
|
||||||
+ expected_length = 100
|
|
||||||
+ length_prefix = struct.pack("!I", expected_length)
|
|
||||||
+ # Send more data than the length prefix indicates
|
|
||||||
+ extra_data = b"X" * (expected_length + 10)
|
|
||||||
+ malicious_msg = length_prefix + extra_data
|
|
||||||
+
|
|
||||||
+ mock_sock = m_socket.return_value
|
|
||||||
+ mock_sock.recv.return_value = malicious_msg
|
|
||||||
+ mock_sock.getsockopt.return_value = socket.SOCK_STREAM
|
|
||||||
+
|
|
||||||
+ read_buffers = {}
|
|
||||||
+ with self.assertRaises(ValueError) as cm:
|
|
||||||
+ self.app._Application__handle_recv(mock_sock, read_buffers)
|
|
||||||
+
|
|
||||||
+ self.assertIn("exceeds its expected length", str(cm.exception))
|
|
||||||
+
|
|
||||||
+ @mock.patch("socket.getaddrinfo", return_value=addrinfo)
|
|
||||||
+ @mock.patch("socket.socket")
|
|
||||||
+ def test_tcp_eof_returns_buffered_data(self, m_socket, m_getaddrinfo):
|
|
||||||
+ # Test that EOF returns any buffered data
|
|
||||||
+ initial_data = b"\x00\x00\x00\x10" # Length = 16
|
|
||||||
+ mock_sock = m_socket.return_value
|
|
||||||
+ mock_sock.getsockopt.return_value = socket.SOCK_STREAM
|
|
||||||
+
|
|
||||||
+ # First recv returns some data, second returns empty (EOF)
|
|
||||||
+ mock_sock.recv.side_effect = [initial_data, b""]
|
|
||||||
+
|
|
||||||
+ read_buffers = {}
|
|
||||||
+ # First call buffers the data
|
|
||||||
+ result = self.app._Application__handle_recv(mock_sock, read_buffers)
|
|
||||||
+ self.assertIsNone(result) # Not complete yet
|
|
||||||
+
|
|
||||||
+ # Second call gets EOF and returns buffered data
|
|
||||||
+ result = self.app._Application__handle_recv(mock_sock, read_buffers)
|
|
||||||
+ self.assertEqual(result, initial_data)
|
|
||||||
+ # Buffer should be cleaned up
|
|
||||||
+ self.assertNotIn(mock_sock, read_buffers)
|
|
||||||
+
|
|
||||||
|
|
||||||
def decode(data):
|
|
||||||
data = data.replace(b'\\n', b'')
|
|
||||||
--
|
|
||||||
2.51.0
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,100 +0,0 @@
|
|||||||
From 7b7aee01d72be5a310678cdad189cb7382f28549 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Robbie Harwood <rharwood@redhat.com>
|
|
||||||
Date: Tue, 19 Jan 2021 11:41:40 -0500
|
|
||||||
Subject: [PATCH] Drop coverage from tests
|
|
||||||
|
|
||||||
To my knowledge, we've never looked at or done anything with this
|
|
||||||
output. Test coverage is a noble goal, but this project is mostly
|
|
||||||
complete, so we don't expect heavy development soon.
|
|
||||||
|
|
||||||
Requested-by: Petr Viktorin <pviktori@redhat.com>
|
|
||||||
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
|
|
||||||
(cherry picked from commit 86c3da13d5d6cdb5822d194f2b820da1fd31dddb)
|
|
||||||
[rharwood@redhat.com: .gitignore]
|
|
||||||
---
|
|
||||||
.coveragerc | 23 -----------------------
|
|
||||||
MANIFEST.in | 1 -
|
|
||||||
setup.py | 2 +-
|
|
||||||
tox.ini | 12 ++----------
|
|
||||||
4 files changed, 3 insertions(+), 35 deletions(-)
|
|
||||||
delete mode 100644 .coveragerc
|
|
||||||
|
|
||||||
diff --git a/.coveragerc b/.coveragerc
|
|
||||||
deleted file mode 100644
|
|
||||||
index 4038562..0000000
|
|
||||||
--- a/.coveragerc
|
|
||||||
+++ /dev/null
|
|
||||||
@@ -1,23 +0,0 @@
|
|
||||||
-[run]
|
|
||||||
-branch = True
|
|
||||||
-source =
|
|
||||||
- kdcproxy
|
|
||||||
- tests.py
|
|
||||||
-
|
|
||||||
-[paths]
|
|
||||||
-source =
|
|
||||||
- kdcproxy
|
|
||||||
- .tox/*/lib/python*/site-packages/kdcproxy
|
|
||||||
-
|
|
||||||
-[report]
|
|
||||||
-ignore_errors = False
|
|
||||||
-precision = 1
|
|
||||||
-exclude_lines =
|
|
||||||
- pragma: no cover
|
|
||||||
- raise AssertionError
|
|
||||||
- raise NotImplementedError
|
|
||||||
- if 0:
|
|
||||||
- if False:
|
|
||||||
- if __name__ == .__main__.:
|
|
||||||
- if PY3
|
|
||||||
- if not PY3
|
|
||||||
diff --git a/MANIFEST.in b/MANIFEST.in
|
|
||||||
index 362f840..ff6b9a7 100644
|
|
||||||
--- a/MANIFEST.in
|
|
||||||
+++ b/MANIFEST.in
|
|
||||||
@@ -2,4 +2,3 @@ include README COPYING
|
|
||||||
include tox.ini
|
|
||||||
include setup.cfg
|
|
||||||
include tests.py tests.krb5.conf
|
|
||||||
-include .coveragerc
|
|
||||||
diff --git a/setup.py b/setup.py
|
|
||||||
index 20b335e..4b34fcc 100644
|
|
||||||
--- a/setup.py
|
|
||||||
+++ b/setup.py
|
|
||||||
@@ -29,7 +29,7 @@ install_requires = [
|
|
||||||
]
|
|
||||||
|
|
||||||
extras_require = {
|
|
||||||
- "tests": ["pytest", "coverage", "WebTest"],
|
|
||||||
+ "tests": ["pytest", "WebTest"],
|
|
||||||
"test_pep8": ['flake8', 'flake8-import-order', 'pep8-naming']
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/tox.ini b/tox.ini
|
|
||||||
index 038d996..9672cee 100644
|
|
||||||
--- a/tox.ini
|
|
||||||
+++ b/tox.ini
|
|
||||||
@@ -1,21 +1,13 @@
|
|
||||||
[tox]
|
|
||||||
minversion = 2.3.1
|
|
||||||
-envlist = py36,py37,py38,py39,pep8,py3pep8,doc,coverage-report
|
|
||||||
+envlist = py36,py37,py38,py39,pep8,py3pep8,doc
|
|
||||||
skip_missing_interpreters = true
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
deps =
|
|
||||||
.[tests]
|
|
||||||
commands =
|
|
||||||
- {envpython} -m coverage run --parallel \
|
|
||||||
- -m pytest --capture=no --strict {posargs}
|
|
||||||
-
|
|
||||||
-[testenv:coverage-report]
|
|
||||||
-deps = coverage
|
|
||||||
-skip_install = true
|
|
||||||
-commands =
|
|
||||||
- {envpython} -m coverage combine
|
|
||||||
- {envpython} -m coverage report --show-missing
|
|
||||||
+ {envpython} -m pytest --capture=no --strict {posargs}
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
basepython = python3
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
From 99babf4ba3ce4d1f5bb893e7678df44d16b74d03 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Julien Rische <jrische@redhat.com>
|
|
||||||
Date: Mon, 18 Nov 2024 10:01:16 +0100
|
|
||||||
Subject: [PATCH] Use dedicated "kdcproxy" logger
|
|
||||||
|
|
||||||
Signed-off-by: Julien Rische <jrische@redhat.com>
|
|
||||||
(cherry picked from commit c8a69dbc0777579ba3bf3d156baed0966327ebc2)
|
|
||||||
---
|
|
||||||
kdcproxy/__init__.py | 7 +++++--
|
|
||||||
kdcproxy/config/__init__.py | 7 +++++--
|
|
||||||
2 files changed, 10 insertions(+), 4 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/kdcproxy/__init__.py b/kdcproxy/__init__.py
|
|
||||||
index d0ca43e..ce96a0c 100644
|
|
||||||
--- a/kdcproxy/__init__.py
|
|
||||||
+++ b/kdcproxy/__init__.py
|
|
||||||
@@ -38,6 +38,9 @@ else:
|
|
||||||
import httplib
|
|
||||||
import urlparse
|
|
||||||
|
|
||||||
+logging.basicConfig()
|
|
||||||
+logger = logging.getLogger('kdcproxy')
|
|
||||||
+
|
|
||||||
|
|
||||||
class HTTPException(Exception):
|
|
||||||
|
|
||||||
@@ -327,8 +330,8 @@ class Application:
|
|
||||||
fail_socktype = self.addr2socktypename(fail_addr)
|
|
||||||
fail_ip = fail_addr[4][0]
|
|
||||||
fail_port = fail_addr[4][1]
|
|
||||||
- logging.warning("Exchange with %s:[%s]:%d failed: %s",
|
|
||||||
- fail_socktype, fail_ip, fail_port, e)
|
|
||||||
+ logger.warning("Exchange with %s:[%s]:%d failed: %s",
|
|
||||||
+ fail_socktype, fail_ip, fail_port, e)
|
|
||||||
if reply is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
diff --git a/kdcproxy/config/__init__.py b/kdcproxy/config/__init__.py
|
|
||||||
index a1435b7..8e17c5b 100644
|
|
||||||
--- a/kdcproxy/config/__init__.py
|
|
||||||
+++ b/kdcproxy/config/__init__.py
|
|
||||||
@@ -32,6 +32,9 @@ except ImportError: # Python 2.x
|
|
||||||
import dns.rdatatype
|
|
||||||
import dns.resolver
|
|
||||||
|
|
||||||
+logging.basicConfig()
|
|
||||||
+logger = logging.getLogger('kdcproxy')
|
|
||||||
+
|
|
||||||
|
|
||||||
class IResolver(object):
|
|
||||||
|
|
||||||
@@ -60,14 +63,14 @@ class KDCProxyConfig(IConfig):
|
|
||||||
try:
|
|
||||||
self.__cp.read(filenames)
|
|
||||||
except configparser.Error:
|
|
||||||
- logging.error("Unable to read config file(s): %s", filenames)
|
|
||||||
+ logger.error("Unable to read config file(s): %s", filenames)
|
|
||||||
|
|
||||||
try:
|
|
||||||
mod = self.__cp.get(self.GLOBAL, "configs")
|
|
||||||
try:
|
|
||||||
importlib.import_module("kdcproxy.config." + mod)
|
|
||||||
except ImportError as e:
|
|
||||||
- logging.log(logging.ERROR, "Error reading config: %s" % e)
|
|
||||||
+ logger.log(logging.ERROR, "Error reading config: %s" % e)
|
|
||||||
except configparser.Error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
--
|
|
||||||
2.46.0
|
|
||||||
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
From 0b2efa7b2901ada01758a0525a21af5447aa647a Mon Sep 17 00:00:00 2001
|
|
||||||
From: Julien Rische <jrische@redhat.com>
|
|
||||||
Date: Mon, 18 Nov 2024 09:38:13 +0100
|
|
||||||
Subject: [PATCH] Use exponential backoff for connection retries
|
|
||||||
|
|
||||||
Calls to socket.connect() are non-blocking, hence all subsequent calls
|
|
||||||
to socket.sendall() will fail if the target KDC service is temporarily
|
|
||||||
or indefinitely unreachable. Since the kdcproxy task uses busy-looping,
|
|
||||||
it results in the journal to be flooded with warning logs.
|
|
||||||
|
|
||||||
This commit introduces a per-socket reactivation delay which increases
|
|
||||||
exponentially as the number of reties is incremented, until timeout is
|
|
||||||
reached (i.e. 100ms, 200ms, 400ms, 800ms, 1.6s, 3.2s, ...).
|
|
||||||
|
|
||||||
Signed-off-by: Julien Rische <jrische@redhat.com>
|
|
||||||
(cherry picked from commit bac3c99c1b23487e38d965a79173ce9519e19c75)
|
|
||||||
---
|
|
||||||
kdcproxy/__init__.py | 63 +++++++++++++++++++++++++++++++++++++++++---
|
|
||||||
1 file changed, 60 insertions(+), 3 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/kdcproxy/__init__.py b/kdcproxy/__init__.py
|
|
||||||
index 1493b30..d0ca43e 100644
|
|
||||||
--- a/kdcproxy/__init__.py
|
|
||||||
+++ b/kdcproxy/__init__.py
|
|
||||||
@@ -61,6 +61,13 @@ class HTTPException(Exception):
|
|
||||||
return "%d %s" % (self.code, httplib.responses[self.code])
|
|
||||||
|
|
||||||
|
|
||||||
+class SocketException(Exception):
|
|
||||||
+
|
|
||||||
+ def __init__(self, message, sock):
|
|
||||||
+ super(Exception, self).__init__(message)
|
|
||||||
+ self.sockfno = sock.fileno()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
class Application:
|
|
||||||
MAX_LENGTH = 128 * 1024
|
|
||||||
SOCKTYPES = {
|
|
||||||
@@ -68,10 +75,23 @@ class Application:
|
|
||||||
"udp": socket.SOCK_DGRAM,
|
|
||||||
}
|
|
||||||
|
|
||||||
+ def addr2socktypename(self, addr):
|
|
||||||
+ ret = None
|
|
||||||
+ for name in self.SOCKTYPES:
|
|
||||||
+ if self.SOCKTYPES[name] == addr[1]:
|
|
||||||
+ ret = name
|
|
||||||
+ break
|
|
||||||
+ return ret
|
|
||||||
+
|
|
||||||
def __init__(self):
|
|
||||||
self.__resolver = MetaResolver()
|
|
||||||
|
|
||||||
def __await_reply(self, pr, rsocks, wsocks, timeout):
|
|
||||||
+ starting_time = time.time()
|
|
||||||
+ send_error = None
|
|
||||||
+ recv_error = None
|
|
||||||
+ failing_sock = None
|
|
||||||
+ reactivations = {}
|
|
||||||
extra = 0
|
|
||||||
read_buffers = {}
|
|
||||||
while (timeout + extra) > time.time():
|
|
||||||
@@ -92,6 +112,12 @@ class Application:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for sock in w:
|
|
||||||
+ # Fetch reactivation tuple:
|
|
||||||
+ # 1st element: reactivation index (-1 = first activation)
|
|
||||||
+ # 2nd element: planned reactivation time (0.0 = now)
|
|
||||||
+ (rn, rt) = reactivations.get(sock, (-1, 0.0))
|
|
||||||
+ if rt > time.time():
|
|
||||||
+ continue
|
|
||||||
try:
|
|
||||||
if self.sock_type(sock) == socket.SOCK_DGRAM:
|
|
||||||
# If we proxy over UDP, remove the 4-byte length
|
|
||||||
@@ -101,8 +127,13 @@ class Application:
|
|
||||||
sock.sendall(pr.request)
|
|
||||||
extra = 10 # New connections get 10 extra seconds
|
|
||||||
except Exception as e:
|
|
||||||
- logging.warning("Conection broken while writing (%s)", e)
|
|
||||||
+ send_error = e
|
|
||||||
+ failing_sock = sock
|
|
||||||
+ reactivations[sock] = (rn + 1,
|
|
||||||
+ time.time() + 2.0**(rn + 1) / 10)
|
|
||||||
continue
|
|
||||||
+ if sock in reactivations:
|
|
||||||
+ del reactivations[sock]
|
|
||||||
rsocks.append(sock)
|
|
||||||
wsocks.remove(sock)
|
|
||||||
|
|
||||||
@@ -110,7 +141,8 @@ class Application:
|
|
||||||
try:
|
|
||||||
reply = self.__handle_recv(sock, read_buffers)
|
|
||||||
except Exception as e:
|
|
||||||
- logging.warning("Connection broken while reading (%s)", e)
|
|
||||||
+ recv_error = e
|
|
||||||
+ failing_sock = sock
|
|
||||||
if self.sock_type(sock) == socket.SOCK_STREAM:
|
|
||||||
# Remove broken TCP socket from readers
|
|
||||||
rsocks.remove(sock)
|
|
||||||
@@ -118,6 +150,21 @@ class Application:
|
|
||||||
if reply is not None:
|
|
||||||
return reply
|
|
||||||
|
|
||||||
+ if reactivations:
|
|
||||||
+ raise SocketException("Timeout while sending packets after %.2fs "
|
|
||||||
+ "and %d tries: %s" % (
|
|
||||||
+ (timeout + extra) - starting_time,
|
|
||||||
+ sum(map(lambda r: r[0],
|
|
||||||
+ reactivations.values())),
|
|
||||||
+ send_error),
|
|
||||||
+ failing_sock)
|
|
||||||
+ elif recv_error is not None:
|
|
||||||
+ raise SocketException("Timeout while receiving packets after "
|
|
||||||
+ "%.2fs: %s" % (
|
|
||||||
+ (timeout + extra) - starting_time,
|
|
||||||
+ recv_error),
|
|
||||||
+ failing_sock)
|
|
||||||
+
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __handle_recv(self, sock, read_buffers):
|
|
||||||
@@ -215,6 +262,7 @@ class Application:
|
|
||||||
reply = None
|
|
||||||
wsocks = []
|
|
||||||
rsocks = []
|
|
||||||
+ sockfno2addr = {}
|
|
||||||
for server in map(urlparse.urlparse, servers):
|
|
||||||
# Enforce valid, supported URIs
|
|
||||||
scheme = server.scheme.lower().split("+", 1)
|
|
||||||
@@ -261,6 +309,7 @@ class Application:
|
|
||||||
continue
|
|
||||||
except io.BlockingIOError:
|
|
||||||
pass
|
|
||||||
+ sockfno2addr[sock.fileno()] = addr
|
|
||||||
wsocks.append(sock)
|
|
||||||
|
|
||||||
# Resend packets to UDP servers
|
|
||||||
@@ -271,7 +320,15 @@ class Application:
|
|
||||||
|
|
||||||
# Call select()
|
|
||||||
timeout = time.time() + (15 if addr is None else 2)
|
|
||||||
- reply = self.__await_reply(pr, rsocks, wsocks, timeout)
|
|
||||||
+ try:
|
|
||||||
+ reply = self.__await_reply(pr, rsocks, wsocks, timeout)
|
|
||||||
+ except SocketException as e:
|
|
||||||
+ fail_addr = sockfno2addr[e.sockfno]
|
|
||||||
+ fail_socktype = self.addr2socktypename(fail_addr)
|
|
||||||
+ fail_ip = fail_addr[4][0]
|
|
||||||
+ fail_port = fail_addr[4][1]
|
|
||||||
+ logging.warning("Exchange with %s:[%s]:%d failed: %s",
|
|
||||||
+ fail_socktype, fail_ip, fail_port, e)
|
|
||||||
if reply is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
--
|
|
||||||
2.46.0
|
|
||||||
|
|
||||||
1
kdcproxy-1.1.0.tar.gz.sha512sum.txt
Normal file
1
kdcproxy-1.1.0.tar.gz.sha512sum.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
f03b9d40d71322281d0197df6fe6b5936a8d09b0fee49fc5375b61974d005cedc5645f92a223d221c05c6ffd2613a86eb7d7295c4ac27a2f2c9eaa10fa24c182 kdcproxy-1.1.0.tar.gz
|
||||||
@ -1,33 +1,32 @@
|
|||||||
%global realname kdcproxy
|
%global realname kdcproxy
|
||||||
|
|
||||||
Name: python-%{realname}
|
Name: python-%{realname}
|
||||||
Version: 1.0.0
|
Version: 1.1.0
|
||||||
Release: 19%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: MS-KKDCP (kerberos proxy) WSGI module
|
Summary: MS-KKDCP (kerberos proxy) WSGI module
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
URL: https://github.com/latchset/%{realname}
|
URL: https://github.com/latchset/%{realname}
|
||||||
Source0: https://github.com/latchset/%{realname}/archive/%{realname}-%{version}.tar.gz
|
Source0: https://github.com/latchset/%{realname}/releases/download/v%{version}/%{realname}-%{version}.tar.gz
|
||||||
|
Source1: https://github.com/latchset/%{realname}/releases/download/v%{version}/%{realname}-%{version}.tar.gz.sha512sum.txt
|
||||||
|
|
||||||
Patch0001: Drop-coverage-from-tests.patch
|
# Patches
|
||||||
Patch0002: Use-exponential-backoff-for-connection-retries.patch
|
|
||||||
Patch0003: Use-dedicated-kdcproxy-logger.patch
|
|
||||||
Patch0004: 0004-Fix-DoS-vulnerability-based-on-unbounded-TCP-bufferi.patch
|
|
||||||
Patch0005: 0005-Use-DNS-discovery-for-declared-realms-only.patch
|
|
||||||
|
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
BuildRequires: git
|
|
||||||
|
|
||||||
BuildRequires: python3-devel
|
BuildRequires: git-core
|
||||||
BuildRequires: python3-dns
|
|
||||||
BuildRequires: python3-pyasn1
|
|
||||||
BuildRequires: python3-pytest
|
BuildRequires: python3-pytest
|
||||||
BuildRequires: python3-setuptools
|
|
||||||
|
|
||||||
%description
|
%generate_buildrequires
|
||||||
|
%pyproject_buildrequires
|
||||||
|
|
||||||
|
%global _description %{expand:
|
||||||
This package contains a Python WSGI module for proxying KDC requests over
|
This package contains a Python WSGI module for proxying KDC requests over
|
||||||
HTTP by following the MS-KKDCP protocol. It aims to be simple to deploy, with
|
HTTP by following the MS-KKDCP protocol. It aims to be simple to deploy, with
|
||||||
minimal configuration.
|
minimal configuration.
|
||||||
|
}
|
||||||
|
|
||||||
|
%description %{_description}
|
||||||
|
|
||||||
%package -n python3-%{realname}
|
%package -n python3-%{realname}
|
||||||
Summary: MS-KKDCP (kerberos proxy) WSGI module
|
Summary: MS-KKDCP (kerberos proxy) WSGI module
|
||||||
@ -36,35 +35,33 @@ Requires: python3-pyasn1
|
|||||||
|
|
||||||
%{?python_provide:%python_provide python3-%{realname}}
|
%{?python_provide:%python_provide python3-%{realname}}
|
||||||
|
|
||||||
%description -n python3-%{realname}
|
%description -n python3-%{realname} %{_description}
|
||||||
This package contains a Python 3.x WSGI module for proxying KDC requests over
|
|
||||||
HTTP by following the MS-KKDCP protocol. It aims to be simple to deploy, with
|
|
||||||
minimal configuration.
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%autosetup -S git -n %{realname}-%{version}
|
%autosetup -S git_am -n %{realname}-%{version}
|
||||||
|
|
||||||
%build
|
%build
|
||||||
%py3_build
|
%pyproject_wheel
|
||||||
|
|
||||||
%install
|
%install
|
||||||
%py3_install
|
%pyproject_install
|
||||||
|
%pyproject_save_files %{realname}
|
||||||
|
|
||||||
%check
|
%check
|
||||||
%{__python3} -m pytest
|
%pyproject_check_import
|
||||||
|
%pytest
|
||||||
|
|
||||||
%files -n python3-%{realname}
|
%files -n python%{python3_pkgversion}-%{realname} -f %{pyproject_files}
|
||||||
%doc README
|
%doc README
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%{python3_sitelib}/%{realname}/
|
|
||||||
%{python3_sitelib}/%{realname}-%{version}-*.egg-info
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Mon Oct 20 2025 Julien Rische <jrische@redhat.com> - 1.0.0-19
|
* Wed Nov 19 2025 Julien Rische <jrische@redhat.com> - 1.1.0-1
|
||||||
|
- New upstream version (1.1.0)
|
||||||
- Use DNS discovery for declared realms only (CVE-2025-59088)
|
- Use DNS discovery for declared realms only (CVE-2025-59088)
|
||||||
Resolves: RHEL-122777
|
Resolves: RHEL-113652
|
||||||
- Fix DoS vulnerability based on unbounded TCP buffering (CVE-2025-59089)
|
- Fix DoS vulnerability based on unbounded TCP buffering (CVE-2025-59089)
|
||||||
Resolves: RHEL-122776
|
Resolves: RHEL-113656
|
||||||
|
|
||||||
* Fri Nov 22 2024 Julien Rische <jrische@redhat.com> - 1.0.0-18
|
* Fri Nov 22 2024 Julien Rische <jrische@redhat.com> - 1.0.0-18
|
||||||
- Log KDC timeout only once per request
|
- Log KDC timeout only once per request
|
||||||
|
|||||||
2
sources
2
sources
@ -1 +1 @@
|
|||||||
SHA512 (kdcproxy-1.0.0.tar.gz) = 617dba929d1c87c60d9a321269fd23348af11eabd8db3cea4b4750ec9514c9dce3487e658c396a5d009c9ef92326d45f5f00a2a116ab6469c62eda8270e55391
|
SHA512 (kdcproxy-1.1.0.tar.gz) = f03b9d40d71322281d0197df6fe6b5936a8d09b0fee49fc5375b61974d005cedc5645f92a223d221c05c6ffd2613a86eb7d7295c4ac27a2f2c9eaa10fa24c182
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user