public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/setroubleshoot] rawhide: setroubleshoot-3.3.37-6
@ 2026-09-02 14:20 Vit Mojzis
  0 siblings, 0 replies; only message in thread
From: Vit Mojzis @ 2026-09-02 14:20 UTC (permalink / raw)
  To: git-commits

            A new commit has been pushed.

            Repo   : rpms/setroubleshoot
            Branch : rawhide
            Commit : 4c42798af0f657f681c258f524a2f6578a627788
            Author : Vit Mojzis <vmojzis@redhat.com>
            Date   : 2026-08-07T20:01:19+02:00
            Stats  : +340/-1 in 7 file(s)
            URL    : https://src.fedoraproject.org/rpms/setroubleshoot/c/4c42798af0f657f681c258f524a2f6578a627788?branch=rawhide

            Log:
            setroubleshoot-3.3.37-6

- Require privileged access to change email alert recipients
- Protect against malicious socket blocking
- Require root to delete alerts via D-Bus
- Fix port handling
- Reject logon() when peer credentials are unavailable
- Restrict RPC dispatch to registered methods only

---
diff --git a/0003-Restrict-RPC-dispatch-to-registered-methods-only.patch b/0003-Restrict-RPC-dispatch-to-registered-methods-only.patch
new file mode 100644
index 0000000..907db11
--- /dev/null
+++ b/0003-Restrict-RPC-dispatch-to-registered-methods-only.patch
@@ -0,0 +1,75 @@
+From 431d5a9a5f7ac4ce61210f4e21ecc64e79c8b31a Mon Sep 17 00:00:00 2001
+From: Vit Mojzis <vmojzis@redhat.com>
+Date: Mon, 27 Jul 2026 18:20:44 +0200
+Subject: [PATCH] Restrict RPC dispatch to registered methods only
+
+get_method_implementation() used unrestricted getattr() to resolve
+attacker-controlled method names on handler objects. Since server.py
+binds the full connection object as the RPC handler via
+connect_rpc_interface('SETroubleshootServer', self), all inherited
+internal methods (acquire_write_lock, close_connection, etc.) became
+callable through crafted RPC requests.
+
+An attacker could connect to the world-writable UNIX socket and invoke
+acquire_write_lock as a signal, then send any RPC method call. The
+response path calls send_data() which tries to acquire the same
+non-reentrant threading.Lock, deadlocking the daemon and making it
+unresponsive to all clients.
+
+Validate the requested method name against interface_registry before
+calling getattr(). The method must exist in the registry with an
+rpc_def.type matching the expected RPC type ('method' or 'signal').
+Unregistered names now fall through to the existing "not implemented"
+error response instead of being dispatched. The expected_type parameter
+defaults to None for backward compatibility with any callers outside
+default_request_handler.
+
+Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
+---
+ src/setroubleshoot/rpc.py | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/src/setroubleshoot/rpc.py b/src/setroubleshoot/rpc.py
+index aca2d7e..6a6f31a 100755
+--- a/src/setroubleshoot/rpc.py
++++ b/src/setroubleshoot/rpc.py
+@@ -869,10 +869,17 @@ class RpcChannel(ConnectionIO, RpcManage):
+                                      result_code, result_msg)
+         self.io_watch_remove()
+ 
+-    def get_method_implementation(self, interface, method):
++    def get_method_implementation(self, interface, method, expected_type=None):
+         handler_obj = self.rpc_handlers.get(interface, None)
+         if handler_obj is None:
+             return None
++        if expected_type is not None:
++            interface_dict = interface_registry.interfaces.get(interface)
++            if interface_dict is None:
++                return None
++            rpc_def = interface_dict.get(method)
++            if rpc_def is None or rpc_def.type != expected_type:
++                return None
+         method_ptr = getattr(handler_obj, method, None)
+         return method_ptr
+ 
+@@ -971,7 +978,7 @@ class RpcChannel(ConnectionIO, RpcManage):
+             self.handle_return(type, rpc_id, body)
+         elif type == 'method':
+             interface, method, args = convert_rpc_xml_to_args(body)
+-            method_ptr = self.get_method_implementation(interface, method)
++            method_ptr = self.get_method_implementation(interface, method, 'method')
+             if method_ptr:
+                 try:
+                     return_args = method_ptr(*args)
+@@ -990,7 +997,7 @@ class RpcChannel(ConnectionIO, RpcManage):
+                 self.emit_rpc(rpc_id, 'error_return', rpc_error_def, method, err_code, err_msg)
+         elif type == 'signal':
+             interface, method, args = convert_rpc_xml_to_args(body)
+-            method_ptr = self.get_method_implementation(interface, method)
++            method_ptr = self.get_method_implementation(interface, method, 'signal')
+             if method_ptr:
+                 try:
+                     method_ptr(*args)
+-- 
+2.53.0
+

diff --git a/0004-Reject-logon-when-peer-credentials-are-unavailable.patch b/0004-Reject-logon-when-peer-credentials-are-unavailable.patch
new file mode 100644
index 0000000..f56b7c6
--- /dev/null
+++ b/0004-Reject-logon-when-peer-credentials-are-unavailable.patch
@@ -0,0 +1,34 @@
+From 920ca71562e462ed4d1d02f3ce1b35dec33bd1bb Mon Sep 17 00:00:00 2001
+From: Vit Mojzis <vmojzis@redhat.com>
+Date: Fri, 7 Aug 2026 14:57:21 +0200
+Subject: [PATCH] Reject logon() when peer credentials are unavailable
+
+get_credentials() returns uid=None for non-Unix client sockets (e.g. an
+INET/TCP listener). logon() then compared the supplied username against
+get_identity(None), which falls back to os.getuid() -- the daemon's own
+uid -- letting any caller authenticate as the 'setroubleshoot' account
+without a valid password on such a listener. Refuse logon outright when
+peer credentials could not be obtained.
+
+Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
+---
+ src/setroubleshoot/server.py | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/setroubleshoot/server.py b/src/setroubleshoot/server.py
+index cf0f345..bc38fa3 100755
+--- a/src/setroubleshoot/server.py
++++ b/src/setroubleshoot/server.py
+@@ -324,6 +324,9 @@ class SetroubleshootdClientConnectionHandler(ClientConnectionHandler,
+     def logon(self, type, username, password):
+         log_debug("logon(%s) type=%s username=%s" % (self, type, username))
+ 
++        if self.uid is None:
++            raise ProgramError(ERR_USER_LOOKUP, detail="peer credentials unavailable; refusing logon on non-unix socket")
++
+         if username != get_identity(self.uid):
+             raise ProgramError(ERR_USER_LOOKUP, detail="uid=%s does not match logon username (%s)" % (self.uid, username))
+ 
+-- 
+2.53.0
+

diff --git a/0005-Fix-port-handling.patch b/0005-Fix-port-handling.patch
new file mode 100644
index 0000000..b3be645
--- /dev/null
+++ b/0005-Fix-port-handling.patch
@@ -0,0 +1,73 @@
+From 49f3d79ff621e7cf3876cea759ded665cd192528 Mon Sep 17 00:00:00 2001
+From: Vit Mojzis <vmojzis@redhat.com>
+Date: Fri, 7 Aug 2026 14:58:20 +0200
+Subject: [PATCH] Fix port handling
+
+- Cast parsed port to int in SocketAddress.parse_inet_addr()
+
+An explicit port in an {inet} address (e.g. "{inet}127.0.0.1:16983") was
+kept as the raw regex-matched string instead of being converted to int.
+socket.bind() requires an int port for AF_INET, so any configured
+INET/TCP listener with an explicit port failed with TypeError, silently
+swallowed by ListeningServer.open()'s exception handler, leaving the
+daemon running with no client listener at all.
+
+- Forward the configured default port in get_socket_list_from_config()
+
+parse_socket_address_list() defaults its default_port parameter to None,
+and get_socket_list_from_config() called it without passing one through,
+so any {inet} address with no explicit port (e.g. "{inet}127.0.0.1") ended
+up with SocketAddress.port = None instead of the configured
+connection.default_port, causing socket.bind() to fail with TypeError.
+
+- Fix invalid default connection.default_port value
+
+The shipped default was '69783' with a comment acknowledging it was a
+placeholder ("FIXME: figure out defined port"). 69783 exceeds the valid
+TCP port range (0-65535), so any {inet} listen_for_client/client_connect_to
+address with no explicit port failed with OverflowError on bind()/connect().
+
+Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
+---
+ src/config.py.in          | 2 +-
+ src/setroubleshoot/rpc.py | 4 +++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/src/config.py.in b/src/config.py.in
+index d8e7e35..603a107 100644
+--- a/src/config.py.in
++++ b/src/config.py.in
+@@ -182,7 +182,7 @@ An empty string implies no limit''',
+     },
+     'connection': {
+         'default_port': {
+-            'value': '69783',     # FIXME: figure out defined port,
++            'value': '16983',
+             'description': '',
+         },
+     },
+diff --git a/src/setroubleshoot/rpc.py b/src/setroubleshoot/rpc.py
+index 6a6f31a..142228f 100755
+--- a/src/setroubleshoot/rpc.py
++++ b/src/setroubleshoot/rpc.py
+@@ -100,7 +100,7 @@ def get_default_port():
+ 
+ def get_socket_list_from_config(cfg_section):
+     addr_string = get_config(cfg_section, 'address_list')
+-    socket_addresses = parse_socket_address_list(addr_string)
++    socket_addresses = parse_socket_address_list(addr_string, get_default_port())
+     return socket_addresses
+ 
+ 
+@@ -577,6 +577,8 @@ class SocketAddress(object):
+             port = match.group(3)
+             if port is None:
+                 port = self.default_port
++            else:
++                port = int(port)
+ 
+             if addr == 'hostname':
+                 addr = get_hostname()
+-- 
+2.53.0
+

diff --git a/0006-Require-root-to-delete-alerts-via-D-Bus.patch b/0006-Require-root-to-delete-alerts-via-D-Bus.patch
new file mode 100644
index 0000000..8d025d8
--- /dev/null
+++ b/0006-Require-root-to-delete-alerts-via-D-Bus.patch
@@ -0,0 +1,50 @@
+From 66942f3b55339e59f768952312548e7eab3d19ca Mon Sep 17 00:00:00 2001
+From: Vit Mojzis <vmojzis@redhat.com>
+Date: Fri, 7 Aug 2026 17:44:46 +0200
+Subject: [PATCH] Require root to delete alerts via D-Bus
+
+delete_alert() removed an alert from the shared host database without
+checking the caller's identity, and the shipped D-Bus policy allowed any
+local user to invoke it. An unprivileged local user could enumerate alerts
+with get_all_alerts and delete entries other users/administrators rely on.
+
+Reject non-root callers in delete_alert() itself, and drop delete_alert
+from the default D-Bus policy context as defense in depth (root already
+has full access via the existing <policy user="root"> rule).
+
+Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
+---
+ org.fedoraproject.Setroubleshootd.conf | 3 ---
+ src/setroubleshoot/server.py           | 2 ++
+ 2 files changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/org.fedoraproject.Setroubleshootd.conf b/org.fedoraproject.Setroubleshootd.conf
+index 65a0daa..910958a 100644
+--- a/org.fedoraproject.Setroubleshootd.conf
++++ b/org.fedoraproject.Setroubleshootd.conf
+@@ -35,9 +35,6 @@
+ 		<allow send_destination="org.fedoraproject.Setroubleshootd"
+ 		       send_interface="org.fedoraproject.SetroubleshootdIface"
+ 		       send_member="set_filter"/>
+-		<allow send_destination="org.fedoraproject.Setroubleshootd"
+-		       send_interface="org.fedoraproject.SetroubleshootdIface"
+-		       send_member="delete_alert"/>
+ 	    <allow send_destination="org.fedoraproject.Setroubleshootd"
+ 		       send_interface="org.freedesktop.DBus.Introspectable"/>
+ 	</policy>
+diff --git a/src/setroubleshoot/server.py b/src/setroubleshoot/server.py
+index bc38fa3..dc29f63 100755
+--- a/src/setroubleshoot/server.py
++++ b/src/setroubleshoot/server.py
+@@ -665,6 +665,8 @@ Deletes an alert from the database.
+ 
+ * `success(b)`:
+ """
++        if self.connection.get_unix_user(sender) != 0:
++            return False
+         try:
+             database = get_host_database()
+             alert = self._get_alert(local_id, database)
+-- 
+2.53.0
+

diff --git a/0007-Protect-against-malicious-socket-blocking.patch b/0007-Protect-against-malicious-socket-blocking.patch
new file mode 100644
index 0000000..6ddc0ee
--- /dev/null
+++ b/0007-Protect-against-malicious-socket-blocking.patch
@@ -0,0 +1,55 @@
+From c30163055995cff80222899a35722819d8286417 Mon Sep 17 00:00:00 2001
+From: Vit Mojzis <vmojzis@redhat.com>
+Date: Fri, 7 Aug 2026 18:03:16 +0200
+Subject: [PATCH] Protect against malicious socket blocking
+
+Set a timeout (socket.timeout config, 5s default) on accepted client
+sockets so a stuck send() eventually times out instead of hanging forever
+(send_data() already had a Socket.timeout handler for this). As a second,
+independent layer, skip clients that have not completed logon() in both
+places that fan data out to the 'sealert' pool: send_alert_notification()
+and ClientNotifier.signatures_updated().
+
+Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
+---
+ src/setroubleshoot/rpc.py    | 1 +
+ src/setroubleshoot/server.py | 4 ++++
+ 2 files changed, 5 insertions(+)
+
+diff --git a/src/setroubleshoot/rpc.py b/src/setroubleshoot/rpc.py
+index 142228f..ecfd062 100755
+--- a/src/setroubleshoot/rpc.py
++++ b/src/setroubleshoot/rpc.py
+@@ -687,6 +687,7 @@ class ListeningServer(ConnectionIO):
+                 try:
+                     client_socket, client_address = socket.accept()
+                     fcntl.fcntl(client_socket.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
++                    client_socket.settimeout(RpcChannel.socket_timeout)
+                     client_handler = self.client_connection_handler_class(self.socket_address)
+                     client_handler.open(client_socket, client_address)
+                     self.connection_state.update(0, ConnectionState.PROBLEM_FLAGS)
+diff --git a/src/setroubleshoot/server.py b/src/setroubleshoot/server.py
+index dc29f63..6adc0c2 100755
+--- a/src/setroubleshoot/server.py
++++ b/src/setroubleshoot/server.py
+@@ -149,6 +149,8 @@ def send_alert_notification(siginfo):
+     system_bus.send_message(alert)
+ 
+     for client in connection_pool.clients('sealert'):
++        if not (client.connection_state.flags & ConnectionState.AUTHENTICATED):
++            continue
+         client.alert(siginfo)
+ 
+ #------------------------------ Variables -------------------------------
+@@ -445,6 +447,8 @@ class ClientNotifier(object):
+ 
+     def signatures_updated(self, type, item):
+         for client in self.connection_pool.clients('sealert'):
++            if not (client.connection_state.flags & ConnectionState.AUTHENTICATED):
++                continue
+             client.signatures_updated(type, item)
+ 
+ 
+-- 
+2.53.0
+

diff --git a/0008-Require-privileged-access-to-change-email-alert-reci.patch b/0008-Require-privileged-access-to-change-email-alert-reci.patch
new file mode 100644
index 0000000..f6dce69
--- /dev/null
+++ b/0008-Require-privileged-access-to-change-email-alert-reci.patch
@@ -0,0 +1,38 @@
+From 271fcfc09a53fba13b0e31df18acdc5d2605cf05 Mon Sep 17 00:00:00 2001
+From: Vit Mojzis <vmojzis@redhat.com>
+Date: Fri, 7 Aug 2026 18:05:56 +0200
+Subject: [PATCH] Require privileged access to change email alert recipients
+
+set_email_recipients() only checked that the RPC connection was
+authenticated, not that the caller was privileged. Since logon() grants
+authenticated state to any local user allowed to run the sealert client
+(client_users='*' by default) and never verifies the supplied password,
+any such user could overwrite the daemon-wide email_alert_recipients file
+that controls where SELinux alert emails are sent.
+
+Reuse the existing fix_cmd privilege model (already used to gate running
+alert fix commands as root) to restrict this to root and users listed in
+fix_cmd_users.
+
+Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
+---
+ src/setroubleshoot/server.py | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/setroubleshoot/server.py b/src/setroubleshoot/server.py
+index 6adc0c2..bf7a294 100755
+--- a/src/setroubleshoot/server.py
++++ b/src/setroubleshoot/server.py
+@@ -364,6 +364,9 @@ class SetroubleshootdClientConnectionHandler(ClientConnectionHandler,
+         if not (self.connection_state.flags & ConnectionState.AUTHENTICATED):
+             raise ProgramError(ERR_NOT_AUTHENTICATED)
+ 
++        if self.uid != 0 and not self.access.user_allowed('fix_cmd', self.username):
++            raise ProgramError(ERR_USER_PROHIBITED)
++
+         email_recipients = recipients
+         email_recipients.write_recipient_file(email_recipients_filepath)
+ 
+-- 
+2.53.0
+

diff --git a/setroubleshoot.spec b/setroubleshoot.spec
index 9d65143..7056f56 100644
--- a/setroubleshoot.spec
+++ b/setroubleshoot.spec
@@ -6,7 +6,7 @@
 Summary: Helps troubleshoot SELinux problems
 Name: setroubleshoot
 Version: 3.3.37
-Release: 5%{?dist}
+Release: 6%{?dist}
 License: GPL-2.0-or-later
 URL: https://gitlab.com/setroubleshoot/setroubleshoot
 Source0: https://gitlab.com/-/project/24478376/uploads/cbdfc2a87b350583c32b168fd9aad9fd/setroubleshoot-3.3.37.tar.gz
@@ -16,6 +16,12 @@ Source2: %{name}.sysusers
 # for j in 00*patch; do printf "Patch: %s\n" $j; done
 Patch: 0001-Update-GPL2-license-texts-to-the-latest-version.patch
 Patch: 0002-Limit-RPC-request-size-in-RequestReceiver-to-prevent.patch
+Patch: 0003-Restrict-RPC-dispatch-to-registered-methods-only.patch
+Patch: 0004-Reject-logon-when-peer-credentials-are-unavailable.patch
+Patch: 0005-Fix-port-handling.patch
+Patch: 0006-Require-root-to-delete-alerts-via-D-Bus.patch
+Patch: 0007-Protect-against-malicious-socket-blocking.patch
+Patch: 0008-Require-privileged-access-to-change-email-alert-reci.patch
 BuildRequires: gcc
 BuildRequires: make
 BuildRequires: libcap-ng-devel
@@ -194,6 +200,14 @@ to user preference. The same tools can be run on existing log files.
 %doc AUTHORS COPYING ChangeLog DBUS.md NEWS README TODO
 
 %changelog
+* Fri Aug 07 2026 Vit Mojzis <vmojzis@redhat.com> - 3.3.37-6
+- Require privileged access to change email alert recipients
+- Protect against malicious socket blocking
+- Require root to delete alerts via D-Bus
+- Fix port handling
+- Reject logon() when peer credentials are unavailable
+- Restrict RPC dispatch to registered methods only
+
 * Wed Jul 29 2026 Vit Mojzis <vmojzis@redhat.com> - 3.3.37-5
 - Update GPL2 license texts to the latest version
 - Limit RPC request size in RequestReceiver to prevent memory exhaustion

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-02 14:20 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-02 14:20 [rpms/setroubleshoot] rawhide: setroubleshoot-3.3.37-6 Vit Mojzis

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox