From 9fe3ac2862a8c175520a0f275f39f548c2cf9d1e Mon Sep 17 00:00:00 2001
From: Petr Lautrbach <plautrba@redhat.com>
Date: Feb 27 2020 11:38:39 +0000
Subject: [PATCH 1/5] setroubleshoot.util: get_rpm_nvr_by_type() and get_rpm_nvr_by_scontext()


get_rpm_nvr_by_scontext(scontext)
     Finds an SELinux module which defines given SELinux context

     ##### arguments

     * `scontext(s)`: an SELinux context

     ##### return values

     * `nvr(s)`: nvr of rpm which ships module where SELinux type used in `scontext` is defined

     ##### usage

     >>> get_rpm_nvr_by_scontext("system_u:system_r:syslogd_t:s0")
     selinux-policy-

     >>> get_rpm_nvr_by_scontext("system_u:system_r:mysqld_log_t:s0")
     mysqld-selinux-

     >>> get_rpm_nvr_by_scontext("system_u:system_r:timedatex_t:s0")
     selinux-policy-

 get_rpm_nvr_by_type(selinux_type)
     Finds an SELinux module which defines given SELinux type

     ##### arguments

     * `selinux_type(s)`: an SELinux type

     ##### return values

     * `nvr(s)`: nvr of rpm which ships module where `selinux_type` is defined

     ##### usage

     >>> get_rpm_nvr_by_type("sshd_t")
     selinux-policy-

     >>> get_rpm_nvr_by_type("mysqld_log_t")
     mysqld-selinux

---

diff --git a/framework/src/setroubleshoot/util.py b/framework/src/setroubleshoot/util.py
index b826e7f..a692691 100755
--- a/framework/src/setroubleshoot/util.py
+++ b/framework/src/setroubleshoot/util.py
@@ -35,6 +35,8 @@ __all__ = [
     'get_rpm_nvr_from_header',
     'get_rpm_nvr_by_name',
     'get_rpm_nvr_by_file_path',
+    'get_rpm_nvr_by_type',
+    'get_rpm_nvr_by_scontext',
     'is_hex',
     'split_rpm_nvr',
     'file_types',
@@ -62,6 +64,7 @@ __all__ = [
     'Retry',
 ]
 
+import bz2
 import six
 import datetime
 import glob
@@ -69,6 +72,7 @@ from gi.repository import GObject
 import os
 import pwd
 import re
+import selinux
 import sys
 import textwrap
 import time
@@ -404,6 +408,81 @@ def split_rpm_nvr(nvr):
     name = '-'.join(components[:-2])
     return (name, version, release)
 
+def get_rpm_nvr_by_type(selinux_type):
+    """
+Finds an SELinux module which defines given SELinux type
+
+##### arguments
+
+* `selinux_type(s)`: an SELinux type
+
+##### return values
+
+* `nvr(s)`: nvr of rpm which ships module where `selinux_type` is defined
+
+##### usage
+
+>>> get_rpm_nvr_by_type("sshd_t")
+selinux-policy-
+
+>>> get_rpm_nvr_by_type("mysqld_log_t")
+mysqld-selinux
+
+    """
+    retval, policytype = selinux.selinux_getpolicytype()
+    if retval != 0:
+        return None
+    typedef = "(type {})\n".format(selinux_type)
+    modules = []
+    for (dirpath, dirnames, filenames) in os.walk("/var/lib/selinux/{}/active/modules".format(policytype)):
+        if "cil" in filenames:
+            try:
+                defined = False
+                try:
+                    # cil files are bzip2'ed by default
+                    defined = typedef.encode() in bz2.open("{}/cil".format(dirpath))
+                except:
+                    # maybe cil file is not bzip2'ed, try plain text
+                    defined = typedef in open("{}/cil".format(dirpath))
+
+                if defined:
+                    modules.append(dirpath)
+            except:
+                # something's wrong, move on
+                # FIXME: log a problem?
+                pass
+
+    if len(modules) > 0:
+        return get_rpm_nvr_by_file_path(sorted(modules)[-1])
+
+    return None
+
+def get_rpm_nvr_by_scontext(scontext):
+    """
+Finds an SELinux module which defines given SELinux context
+
+##### arguments
+
+* `scontext(s)`: an SELinux context
+
+##### return values
+
+* `nvr(s)`: nvr of rpm which ships module where SELinux type used in `scontext` is defined
+
+##### usage
+
+>>> get_rpm_nvr_by_scontext("system_u:system_r:syslogd_t:s0")
+selinux-policy-
+
+>>> get_rpm_nvr_by_scontext("system_u:system_r:mysqld_log_t:s0")
+mysqld-selinux-
+
+>>> get_rpm_nvr_by_scontext("system_u:system_r:timedatex_t:s0")
+selinux-policy-
+
+    """
+    context = selinux.context_new(str(scontext))
+    return get_rpm_nvr_by_type(str(selinux.context_type_get(context)))
 
 def get_user_home_dir():
     uid = os.getuid()

From 5242f26ab29c7787a6071d10bf613e6b283512ef Mon Sep 17 00:00:00 2001
From: Petr Lautrbach <plautrba@redhat.com>
Date: Feb 27 2020 11:38:39 +0000
Subject: [PATCH 2/5] Export setroubleshoot.utils.get_rpm_nvr_by_scontext via DBUS


$ dbus-send --system --print-reply --dest=org.fedoraproject.SetroubleshootPrivileged \
  /org/fedoraproject/SetroubleshootPrivileged/object \
  org.fedoraproject.SetroubleshootPrivileged.get_rpm_nvr_by_scontext \
  string:"system_u:system_r:mysqld_log_t:s0"

org.fedoraproject.SetroubleshootPrivileged is available only for
`setroubleshoot` user and it's supposed to be a privileged helper which is used
by `setroubleshootd`

---

diff --git a/framework/Makefile.am b/framework/Makefile.am
index 56a8b37..f330b7c 100644
--- a/framework/Makefile.am
+++ b/framework/Makefile.am
@@ -14,12 +14,15 @@ dbus_session_DATA = sealert.service
 dbus_systemservicedir = $(datadir)/dbus-1/system-services
 dbus_systemservice_DATA = \
 			org.fedoraproject.Setroubleshootd.service \
-			org.fedoraproject.SetroubleshootFixit.service 
+			org.fedoraproject.SetroubleshootFixit.service \
+			org.fedoraproject.SetroubleshootPrivileged.service
 
 dbus_systemdir = $(sysconfdir)/dbus-1/system.d
 dbus_system_DATA = \
 			org.fedoraproject.Setroubleshootd.conf \
-			org.fedoraproject.SetroubleshootFixit.conf
+			org.fedoraproject.SetroubleshootFixit.conf \
+			org.fedoraproject.SetroubleshootPrivileged.conf
+
 
 polkit_systemdir = $(datadir)/polkit-1/actions
 polkit_system_DATA = \
diff --git a/framework/org.fedoraproject.SetroubleshootPrivileged.conf b/framework/org.fedoraproject.SetroubleshootPrivileged.conf
new file mode 100644
index 0000000..aaa0a0f
--- /dev/null
+++ b/framework/org.fedoraproject.SetroubleshootPrivileged.conf
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
+
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+  <!-- Only root can own the service -->
+  <policy user="root">
+    <allow own="org.fedoraproject.SetroubleshootPrivileged"/>
+  </policy>
+
+  <policy user="setroubleshoot">
+    <allow send_destination="org.fedoraproject.SetroubleshootPrivileged"
+	   send_interface="org.fedoraproject.SetroubleshootPrivileged"/>
+    <allow send_destination="org.fedoraproject.SetroubleshootPrivileged"
+		send_interface="org.freedesktop.DBus.Introspectable"/>
+  </policy>
+
+</busconfig>
diff --git a/framework/org.fedoraproject.SetroubleshootPrivileged.service b/framework/org.fedoraproject.SetroubleshootPrivileged.service
new file mode 100644
index 0000000..7a45458
--- /dev/null
+++ b/framework/org.fedoraproject.SetroubleshootPrivileged.service
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.fedoraproject.SetroubleshootPrivileged
+Exec=/usr/share/setroubleshoot/SetroubleshootPrivileged.py
+User=root
diff --git a/framework/src/Makefile.am b/framework/src/Makefile.am
index e1782d5..bf53763 100644
--- a/framework/src/Makefile.am
+++ b/framework/src/Makefile.am
@@ -38,7 +38,8 @@ pkglibexec_SCRIPTS =            	\
 pkgdir = $(datarootdir)/setroubleshoot
 pkg_SCRIPTS =			\
 	SetroubleshootFixit.py		\
-	updater.py
+	updater.py \
+	SetroubleshootPrivileged.py
 
 pkgconfig_DATA =			\
 	setroubleshoot.conf		\
diff --git a/framework/src/SetroubleshootPrivileged.py b/framework/src/SetroubleshootPrivileged.py
new file mode 100644
index 0000000..858115b
--- /dev/null
+++ b/framework/src/SetroubleshootPrivileged.py
@@ -0,0 +1,57 @@
+#!/usr/bin/python3
+
+# Authors: Petr Lautrbach <plautrba@redhat.com>
+#
+# Copyright (C) 2020 Red Hat, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import dbus
+import dbus.service
+from dbus.mainloop.glib import DBusGMainLoop
+from gi.repository import GLib
+import setroubleshoot.util
+import signal
+
+DBusGMainLoop(set_as_default=True)
+
+class Privileged(dbus.service.Object):
+
+    def __init__(self, timeout=10):
+        self.timeout = timeout
+        self.alarm(self.timeout)
+
+        bus = dbus.SystemBus()
+        bus.request_name("org.fedoraproject.SetroubleshootPrivileged")
+        bus_name = dbus.service.BusName("org.fedoraproject.SetroubleshootPrivileged", bus=bus)
+        dbus.service.Object.__init__(self, bus_name, "/org/fedoraproject/SetroubleshootPrivileged/object")
+
+    def alarm(self, timeout=10):
+        signal.alarm(timeout)
+
+    @dbus.service.method("org.fedoraproject.SetroubleshootPrivileged", in_signature='s', out_signature='s')
+    def get_rpm_nvr_by_scontext(self, scontext):
+        signal.alarm(self.timeout)
+        rpmnvr = setroubleshoot.util.get_rpm_nvr_by_scontext(scontext)
+        if rpmnvr is None:
+            return ""
+
+        return rpmnvr
+
+if __name__ == "__main__":
+    privileged = Privileged()
+
+    loop = GLib.MainLoop()
+    loop.run()

From 72de472c510e6e3d50c72efbd1e1fd291ed35b68 Mon Sep 17 00:00:00 2001
From: Petr Lautrbach <plautrba@redhat.com>
Date: Feb 27 2020 11:38:39 +0000
Subject: [PATCH 3/5] setroubleshoot.utils.get_rpm_nvr_by_scontext add option to use DBUS method


Using keyword `use_dbus=True`:

get_rpm_nvr_by_scontext("system_u:system_r:timedatex_t:s0", use_dbus=True)

the function calls org.fedoraproject.SetroubleshootPrivileged.get_rpm_nvr_by_scontext
DBUS method in order to get data using privileged process.

---

diff --git a/framework/src/setroubleshoot/util.py b/framework/src/setroubleshoot/util.py
index a692691..94bb988 100755
--- a/framework/src/setroubleshoot/util.py
+++ b/framework/src/setroubleshoot/util.py
@@ -67,6 +67,7 @@ __all__ = [
 import bz2
 import six
 import datetime
+import dbus
 import glob
 from gi.repository import GObject
 import os
@@ -423,10 +424,10 @@ Finds an SELinux module which defines given SELinux type
 ##### usage
 
 >>> get_rpm_nvr_by_type("sshd_t")
-selinux-policy-
+'selinux-policy-...
 
 >>> get_rpm_nvr_by_type("mysqld_log_t")
-mysqld-selinux
+'mysql-selinux-...
 
     """
     retval, policytype = selinux.selinux_getpolicytype()
@@ -457,7 +458,7 @@ mysqld-selinux
 
     return None
 
-def get_rpm_nvr_by_scontext(scontext):
+def get_rpm_nvr_by_scontext(scontext, use_dbus=False):
     """
 Finds an SELinux module which defines given SELinux context
 
@@ -472,17 +473,31 @@ Finds an SELinux module which defines given SELinux context
 ##### usage
 
 >>> get_rpm_nvr_by_scontext("system_u:system_r:syslogd_t:s0")
-selinux-policy-
+'selinux-policy-...
 
 >>> get_rpm_nvr_by_scontext("system_u:system_r:mysqld_log_t:s0")
-mysqld-selinux-
+'mysql-selinux-...
 
->>> get_rpm_nvr_by_scontext("system_u:system_r:timedatex_t:s0")
-selinux-policy-
+>>> get_rpm_nvr_by_scontext("system_u:system_r:timedatex_t:s0", use_dbus=True)
+'selinux-policy-...
 
     """
-    context = selinux.context_new(str(scontext))
-    return get_rpm_nvr_by_type(str(selinux.context_type_get(context)))
+    if use_dbus:
+        bus = dbus.SystemBus()
+
+        try:
+            remote_object = bus.get_object("org.fedoraproject.SetroubleshootPrivileged",
+                                       "/org/fedoraproject/SetroubleshootPrivileged/object")
+
+            return str(remote_object.get_rpm_nvr_by_scontext(str(scontext),
+                    dbus_interface = "org.fedoraproject.SetroubleshootPrivileged"))
+        except dbus.DBusException:
+            from traceback import print_exc
+            print_exc()
+            return None
+    else:
+        context = selinux.context_new(str(scontext))
+        return get_rpm_nvr_by_type(str(selinux.context_type_get(context)))
 
 def get_user_home_dir():
     uid = os.getuid()

From 74926ff27b35329819d74ea53eef2aff376cc6e1 Mon Sep 17 00:00:00 2001
From: Petr Lautrbach <plautrba@redhat.com>
Date: Feb 27 2020 11:38:39 +0000
Subject: [PATCH 4/5] Add Local SELinux policy package version to analyses reports


Sometimes a SELinux domain is shipped by other than selinux-policy packages. In
this case it's useful to report other package policy version together with
selinux-policy version, e.g. for the following AVC:

type=AVC msg=audit(1582621541.469:6896): avc:  denied  { write } for pid=1627505 comm="python3" name="plautrba" dev="dm-4" ino=19529729 scontext=system_u:system_r:mysqld_t:s0 tcontext=unconfined_u:object_r:user_home_dir_t:s0 tclass=dir permissive=1

a report will contain the following lines:

SELinux Policy RPM     selinux-policy-3.14.5-24.fc32.1.contrib.50770ffc2a14.noarch
Local Policy RPM       mysql-selinux-1.0.0-9.fc32.noarch

---

diff --git a/framework/src/setroubleshoot/signature.py b/framework/src/setroubleshoot/signature.py
index 711c287..7287eec 100755
--- a/framework/src/setroubleshoot/signature.py
+++ b/framework/src/setroubleshoot/signature.py
@@ -120,6 +120,7 @@ class SEEnvironment(XmlSerialize):
         'kernel': {'XMLForm': 'element'},
         'policy_type': {'XMLForm': 'element'},
         'policy_rpm': {'XMLForm': 'element'},
+        'local_policy_rpm': {'XMLForm': 'element'},
         'enforce': {'XMLForm': 'element'},
         'selinux_enabled': {'XMLForm': 'element', 'import_typecast': boolean, },
         'selinux_mls_enabled': {'XMLForm': 'element', 'import_typecast': boolean, },
@@ -141,6 +142,7 @@ class SEEnvironment(XmlSerialize):
         self.platform, self.kernel = get_os_environment()
         self.policy_type = selinux.selinux_getpolicytype()[1]
         self.policy_rpm = get_rpm_nvr_by_name("selinux-policy")
+        self.local_policy_rpm = self.policy_rpm
         self.policyvers = str(selinux.security_policyvers())
         enforce = selinux.security_getenforce()
         if enforce == 0:
@@ -312,6 +314,7 @@ class SEFaultSignatureInfo(XmlSerialize):
             setattr(self, k, v)
         self.report_count = 1
         self.plugin_list = []
+        self.environment.local_policy_rpm = get_rpm_nvr_by_scontext(self.scontext, use_dbus=True)
 
     def update_merge(self, siginfo):
         if siginfo.last_seen_date != self.last_seen_date:
@@ -524,7 +527,8 @@ class SEFaultSignatureInfo(XmlSerialize):
             text += format_2_column_name_value(_("Host"), default_text(self.sig.host))
         text += format_2_column_name_value(_("Source RPM Packages"), default_text(self.format_rpm_list(self.src_rpm_list)))
         text += format_2_column_name_value(_("Target RPM Packages"), default_text(self.format_rpm_list(self.tgt_rpm_list)))
-        text += format_2_column_name_value(_("Policy RPM"), default_text(env.policy_rpm))
+        text += format_2_column_name_value(_("SELinux Policy RPM"), default_text(env.policy_rpm))
+        text += format_2_column_name_value(_("Local Policy RPM"), default_text(env.local_policy_rpm))
         text += format_2_column_name_value(_("Selinux Enabled"), default_text(env.selinux_enabled))
         text += format_2_column_name_value(_("Policy Type"), default_text(env.policy_type))
         text += format_2_column_name_value(_("Enforcing Mode"), default_text(env.enforce))

From a9a1d1b99c30208006a86474c19ab288c933afb6 Mon Sep 17 00:00:00 2001
From: Petr Lautrbach <plautrba@redhat.com>
Date: Feb 27 2020 11:38:39 +0000
Subject: [PATCH 5/5] Report bug on a package which owns the related SELinux domain


---

diff --git a/framework/src/setroubleshoot/browser.py b/framework/src/setroubleshoot/browser.py
index 22ffd13..4b1c143 100644
--- a/framework/src/setroubleshoot/browser.py
+++ b/framework/src/setroubleshoot/browser.py
@@ -997,7 +997,8 @@ class BugReport:
         text_buf = self.error_submit_text.get_buffer()
         content = text_buf.get_text(text_buf.get_start_iter(),
                                     text_buf.get_end_iter(), False)
-        signature = report.createAlertSignature("selinux-policy",
+        local_policy_package = get_rpm_source_package(self.alert.environment.local_policy_rpm)
+        signature = report.createAlertSignature(local_policy_package,
                                                 "setroubleshoot",
                                                 self.alert.get_hash(),
                                                 self.summary,
diff --git a/framework/src/setroubleshoot/util.py b/framework/src/setroubleshoot/util.py
index 94bb988..77b3668 100755
--- a/framework/src/setroubleshoot/util.py
+++ b/framework/src/setroubleshoot/util.py
@@ -37,6 +37,7 @@ __all__ = [
     'get_rpm_nvr_by_file_path',
     'get_rpm_nvr_by_type',
     'get_rpm_nvr_by_scontext',
+    'get_rpm_source_package',
     'is_hex',
     'split_rpm_nvr',
     'file_types',
@@ -499,6 +500,29 @@ Finds an SELinux module which defines given SELinux context
         context = selinux.context_new(str(scontext))
         return get_rpm_nvr_by_type(str(selinux.context_type_get(context)))
 
+def get_rpm_source_package(name):
+    """
+    Find a source package for `name` rpm
+    
+    >>> get_rpm_source_package("policycoreutils-python-utils")
+    'policycoreutils'
+
+    >>> get_rpm_source_package("selinux-policy-targeted")
+    'selinux-policy'
+
+    """
+    if name is None:
+        return None
+
+    src = None
+    try:
+        import subprocess
+        src = subprocess.check_output(["rpm", "-q", "--qf", "%{SOURCERPM}", name], universal_newlines=True).rsplit('-',2)[0]
+    except:
+        syslog.syslog(syslog.LOG_ERR, "failed to retrieve rpm info for %s" % name)
+    return src
+
+
 def get_user_home_dir():
     uid = os.getuid()
     try:

