Hello community,

here is the log from the commit of package rpmlint for openSUSE:Factory checked 
in at 2019-12-11 12:04:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rpmlint (Old)
 and      /work/SRC/openSUSE:Factory/.rpmlint.new.4691 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rpmlint"

Wed Dec 11 12:04:22 2019 rev:325 rq:755596 version:1.11

Changes:
--------
--- /work/SRC/openSUSE:Factory/rpmlint/rpmlint.changes  2019-10-21 
12:26:14.539813893 +0200
+++ /work/SRC/openSUSE:Factory/.rpmlint.new.4691/rpmlint.changes        
2019-12-11 12:04:49.440747954 +0100
@@ -1,0 +2,12 @@
+Tue Dec 10 14:46:11 UTC 2019 - [email protected]
+
+- Update to version master:
+  * new common whitelisting code for CheckPolkitPrivs and CheckCronJobs
+
+-------------------------------------------------------------------
+Thu Nov 28 11:58:21 UTC 2019 - Malte Kraus <[email protected]>
+
+- whitelist sssd infopipe (bsc#1157663)
+- whitelist sysprof3 D-Bus services (bsc#1151418)
+
+-------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rpmlint-tests.spec ++++++
--- /var/tmp/diff_new_pack.EyfTUT/_old  2019-12-11 12:04:51.316747164 +0100
+++ /var/tmp/diff_new_pack.EyfTUT/_new  2019-12-11 12:04:51.320747163 +0100
@@ -12,7 +12,7 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 # icecream 0
 
@@ -30,7 +30,7 @@
 License:        SUSE-Public-Domain
 Group:          Development/Tools/Building
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
-Url:            http://www.opensuse.org/
+URL:            http://www.opensuse.org/
 Source:         rpmlint-tests-%version.tar.xz
 Patch0:         rpmlint-tests-sle15.patch
 

++++++ rpmlint.spec ++++++
--- /var/tmp/diff_new_pack.EyfTUT/_old  2019-12-11 12:04:51.340747154 +0100
+++ /var/tmp/diff_new_pack.EyfTUT/_new  2019-12-11 12:04:51.344747153 +0100
@@ -12,7 +12,7 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
@@ -22,7 +22,7 @@
 Summary:        RPM file correctness checker
 License:        GPL-2.0-or-later
 Group:          System/Packages
-Url:            https://github.com/rpm-software-management/rpmlint
+URL:            https://github.com/rpm-software-management/rpmlint
 Source0:        
https://github.com/rpm-software-management/rpmlint/archive/rpmlint-%{version}.tar.gz
 Source1:        rpmlint-checks-master.tar.xz
 Source2:        config

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.EyfTUT/_old  2019-12-11 12:04:51.412747124 +0100
+++ /var/tmp/diff_new_pack.EyfTUT/_new  2019-12-11 12:04:51.412747124 +0100
@@ -3,4 +3,4 @@
             <param 
name="url">https://github.com/openSUSE/rpmlint-tests.git</param>
           <param 
name="changesrevision">e27d43198d06699c9a705b71e2d511a94efab752</param></service><service
 name="tar_scm">
             <param 
name="url">https://github.com/openSUSE/rpmlint-checks.git</param>
-          <param 
name="changesrevision">97ff0bdbab5a7039bd4a6551772cf9446ab01d70</param></service></servicedata>
\ No newline at end of file
+          <param 
name="changesrevision">00e6393112de7c6da46780842cd787f693b05af3</param></service></servicedata>
\ No newline at end of file

++++++ config ++++++
--- /var/tmp/diff_new_pack.EyfTUT/_old  2019-12-11 12:04:51.428747117 +0100
+++ /var/tmp/diff_new_pack.EyfTUT/_new  2019-12-11 12:04:51.428747117 +0100
@@ -697,6 +697,9 @@
     # sysprof (bsc#996111)
     "org.gnome.Sysprof2.service",
     "org.gnome.Sysprof2.conf",
+    # sysprof (bsc#1151418)
+    "org.gnome.Sysprof3.service",
+    "org.gnome.Sysprof3.conf",
     # flatpak (bsc#984817)
     "org.freedesktop.Flatpak.SystemHelper.service",
     "org.freedesktop.Flatpak.SystemHelper.conf",
@@ -776,6 +779,9 @@
     # systemd-portabled (boo#1145639)
     "org.freedesktop.portable1.service",
     "org.freedesktop.portable1.conf",
+    # sssd (bsc#1157663, bsc#1106600)
+    "org.freedesktop.sssd.infopipe.service",
+    "org.freedesktop.sssd.infopipe.conf",
 ))
 
 setOption("PAMModules.WhiteList", (

++++++ rpmlint-checks-master.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpmlint-checks-master/CheckCronJobs.py 
new/rpmlint-checks-master/CheckCronJobs.py
--- old/rpmlint-checks-master/CheckCronJobs.py  1970-01-01 01:00:00.000000000 
+0100
+++ new/rpmlint-checks-master/CheckCronJobs.py  2019-12-10 13:24:44.000000000 
+0100
@@ -0,0 +1,100 @@
+# vim: sw=4 ts=4 sts=4 et :
+#############################################################################
+# Author        : Matthias Gerstner
+# Purpose       : Enforce Whitelisting for cron jobs in /etc/cron.* directories
+#############################################################################
+
+import os
+
+import AbstractCheck
+import Config
+import Whitelisting
+
+from Filter import addDetails
+
+# this option is found in config files in /opt/testing/share/rpmlint/mini,
+# installed there by the rpmlint-mini package.
+WHITELIST_DIR = Config.getOption('WhitelistDataDir', [])
+
+
+class CronCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "CheckCronJobs")
+
+        for wd in WHITELIST_DIR:
+            candidate = os.path.join(wd, "cron-whitelist.json")
+            if os.path.exists(candidate):
+                whitelist_path = candidate
+                break
+        else:
+            whitelist_path = None
+
+        self.m_check_configured = whitelist_path is not None
+
+        if not self.m_check_configured:
+            return
+
+        parser = Whitelisting.WhitelistParser(whitelist_path)
+        whitelist_entries = parser.parse()
+        self.m_wl_checker = Whitelisting.WhitelistChecker(
+            whitelist_entries,
+            restricted_paths=(
+                "/etc/cron.d/", "/etc/cron.hourly/", "/etc/cron.daily/",
+                "/etc/cron.weekly/", "/etc/cron.monthly/"
+            ),
+            error_map={
+                "unauthorized": "cronjob-unauthorized-file",
+                "changed": "cronjob-changed-file",
+                "ghost": "cronjob-ghost-file"
+            }
+        )
+
+    def _getPrintPrefix(self):
+        """Returns a prefix for error / warning output."""
+        return self.__class__.__name__ + ":"
+
+    def _getErrorPrefix(self):
+        return self._getPrintPrefix() + " ERROR: "
+
+    def _getWarnPrefix(self):
+        return self._getPrintPrefix() + " WARN: "
+
+    def check(self, pkg):
+        """This is called by rpmlint to perform the cron check on the given
+        pkg."""
+
+        if not self.m_check_configured:
+            # don't ruin the whole run if this check is not configured, this
+            # was hopefully intended by the user.
+            return
+
+        self.m_wl_checker.check(pkg)
+
+
+# needs to be instantiated for the check to be registered with rpmlint
+check = CronCheck()
+
+for _id, desc in (
+        (
+            'cronjob-unauthorized-file',
+            """A cron job rule file is installed by this package. If the 
package is
+            intended for inclusion in any SUSE product please open a bug 
report to request
+            review of the package by the security team. Please refer to {url} 
for more
+            information"""
+        ),
+        (
+            'cronjob-changed-file',
+            """A cron job or cron job related file installed by this package 
changed
+            in content. Please open a bug report to request follow-up review 
of the
+            introduced changes by the security team. Please refer to {url} for 
more
+            information."""
+        ),
+        (
+            'cronjob-ghost-file',
+            """A cron job path has been marked as %ghost file by this package.
+            This is not allowed as it is impossible to review. Please refer to
+            {url} for more information."""
+        )
+):
+    addDetails(_id, desc.format(url=Whitelisting.AUDIT_BUG_URL))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpmlint-checks-master/CheckDBUSServices.py 
new/rpmlint-checks-master/CheckDBUSServices.py
--- old/rpmlint-checks-master/CheckDBUSServices.py      2019-09-03 
14:03:37.000000000 +0200
+++ new/rpmlint-checks-master/CheckDBUSServices.py      2019-12-10 
13:24:44.000000000 +0100
@@ -1,4 +1,4 @@
-# vim:sw=4:et
+# vim: sw=4 et sts=4 ts=4 :
 #############################################################################
 # File          : CheckDBUSServices.py
 # Package       : rpmlint
@@ -10,6 +10,7 @@
 
 from Filter import *
 import AbstractCheck
+import Whitelisting
 
 SERVICES_WHITELIST = Config.getOption('DBUSServices.WhiteList', ())  # set of 
file names
 
@@ -35,12 +36,13 @@
         files = pkg.files()
 
         for f in files:
-            if f in pkg.ghostFiles():
-                continue
-
             for p in _dbus_system_paths:
                 if f.startswith(p):
 
+                    if f in pkg.ghostFiles():
+                        printError(pkig, "suse-dbus-ghost-service", f)
+                        continue
+
                     bn = f[len(p):]
                     if bn not in SERVICES_WHITELIST:
                         printError(pkg, "suse-dbus-unauthorized-service", f)
@@ -49,11 +51,19 @@
 check = DBUSServiceCheck()
 
 if Config.info:
-    addDetails(
-'suse-dbus-unauthorized-service',
-"""The package installs a DBUS system service file. If the package
-is intended for inclusion in any SUSE product please open a bug
-report to request review of the service by the security team. Please
-refer to 
https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs
-for more information.""",
-)
+    for _id, desc in (
+        (
+            'suse-dbus-unauthorized-service',
+            """The package installs a DBUS system service file. If the package
+            is intended for inclusion in any SUSE product please open a bug
+            report to request review of the service by the security team. 
Please
+            refer to {url} for more information."""
+        ),
+        (
+            'suse-dbus-ghost-service',
+            """This package installs a DBUS system service marked as %ghost.
+            This is not allowed, since it is impossible to review. Please
+            refer to {url} for more information."""
+        )
+    ):
+        addDetails(_id, desc.format(url=Whitelisting.AUDIT_BUG_URL))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpmlint-checks-master/CheckPAMModules.py 
new/rpmlint-checks-master/CheckPAMModules.py
--- old/rpmlint-checks-master/CheckPAMModules.py        2019-09-03 
14:03:37.000000000 +0200
+++ new/rpmlint-checks-master/CheckPAMModules.py        2019-12-10 
13:24:44.000000000 +0100
@@ -1,4 +1,4 @@
-# vim:sw=4:et
+# vim: sw=4 ts=4 sts=4 et :
 #############################################################################
 # File          : CheckPAMModules.py
 # Package       : rpmlint
@@ -9,6 +9,7 @@
 from Filter import *
 import AbstractCheck
 import re
+import Whitelisting
 
 PAM_WHITELIST = Config.getOption('PAMModules.WhiteList', ())  # set of file 
names
 
@@ -28,11 +29,12 @@
         files = pkg.files()
 
         for f in files:
-            if f in pkg.ghostFiles():
-                continue
-
             m = pam_module_re.match(f)
             if m:
+                if f in pkg.ghostFiles():
+                    printError(pkg, 'suse-pam-ghost-module', f)
+                    continue
+
                 bn = m.groups()[0]
                 if bn not in PAM_WHITELIST:
                     printError(pkg, "suse-pam-unauthorized-module", bn)
@@ -41,10 +43,20 @@
 check = PAMModulesCheck()
 
 if Config.info:
-    addDetails(
-'suse-pam-unauthorized-module',
-"""The package installs a PAM module. If the package
-is intended for inclusion in any SUSE product please open a bug
-report to request review of the service by the security team.
-Please refer to 
https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs""";,
-)
+
+    for _id, desc in (
+        (
+            'suse-pam-unauthorized-module',
+            """The package installs a PAM module. If the package
+            is intended for inclusion in any SUSE product please open a bug
+            report to request review of the service by the security team.
+            Please refer to {url}"""
+        ),
+        (
+            'suse-pam-ghost-module',
+            """The package installs a PAM module as %ghost file. This is not
+            allowed as it is impossible to review. For more information please
+            refer to {url} for more information."""
+        )
+    ):
+        addDetails(_id, desc.format(url=Whitelisting.AUDIT_BUG_URL))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpmlint-checks-master/CheckPolkitPrivs.py 
new/rpmlint-checks-master/CheckPolkitPrivs.py
--- old/rpmlint-checks-master/CheckPolkitPrivs.py       2019-09-03 
14:03:37.000000000 +0200
+++ new/rpmlint-checks-master/CheckPolkitPrivs.py       2019-12-10 
13:24:44.000000000 +0100
@@ -1,4 +1,4 @@
-# vim:sw=4:et
+# vim: sw=4 et sts=4 ts=4 :
 #############################################################################
 # File          : CheckPolkitPrivs.py
 # Package       : rpmlint
@@ -11,9 +11,7 @@
 import Config
 import re
 import os
-import sys
-import json
-import hashlib
+import Whitelisting
 from xml.dom.minidom import parse
 
 POLKIT_PRIVS_WHITELIST = Config.getOption('PolkitPrivsWhiteList', ())   # set 
of file names
@@ -27,23 +25,6 @@
         AbstractCheck.AbstractCheck.__init__(self, "CheckPolkitPrivs")
         self.privs = {}
         self._collect_privs()
-
-        # a structure like this:
-        # {
-        #   "<package>": {
-        #       "skip-digest-check": bool
-        #       "<path>": {
-        #           "audits": [
-        #               {
-        #                   "bug": "bsc#4711",
-        #                   "comment": "note about whitelisting",
-        #                   "digest": "<alg>:<digest>"
-        #               }
-        #           ]
-        #       }
-        #   }
-        # }
-        self.rules = {}
         self._collect_rules_whitelist()
 
     def _get_err_prefix(self):
@@ -67,77 +48,25 @@
                     self.privs[priv] = value
 
     def _collect_rules_whitelist(self):
+        rules_entries = {}
         for filename in POLKIT_RULES_WHITELIST:
-            if os.path.exists(filename):
-                self._parse_rules_whitelist(filename)
-
-    def _parse_rules_whitelist(self, filename):
-        """
-        The JSON data is structured like this:
-
-        [
-            {
-                "package": "polkit-default-privs",
-                "path": "/etc/polkit-1/rules.d/90-default-privs.rules",
-                # can be left out, default is false
-                # if set then the content will not
-                # be checked (only to be used for special cases)
-                "skip-digest-check": true,
-                "audits": [
-                    {
-                        "bug": "bsc#1125314",
-                        "comment": "rules dynamically generated by our own 
polkit profile tooling",
-                        "digest": 
"sha256:aea3041de2c15db8683620de8533206e50241c309eb27893605d5ead17e5e75f"
-                    },
-                    {
-                        "bug": "bsc#4711",
-                        "comment": "no-op changes in comments",
-                        "digest": "<alg>:<digest>"
-                    }
-                ]
-            },
-            {
-                ...
+            if not os.path.exists(filename):
+                continue
+            parser = Whitelisting.WhitelistParser(filename)
+            res = parser.parse()
+            rules_entries.update(res)
+
+        self.m_rules_checker = Whitelisting.WhitelistChecker(
+            rules_entries,
+            restricted_paths=(
+                "/etc/polkit-1/rules.d/", "/usr/share/polkit-1/rules.d/"
+            ),
+            error_map={
+                "unauthorized": "polkit-unauthorized-rules",
+                "changed": "polkit-changed-rules",
+                "ghost": "polkit-ghost-file"
             }
-        ]
-        """
-
-        try:
-            with open(filename, 'r') as fd:
-                data = json.load(fd)
-
-                for entry in data:
-                    self._parse_rules_whitelist_entry(entry)
-
-        except Exception as e:
-            print(self._get_err_prefix(), "failed to parse json file {}: 
{}".format(
-                filename, str(e)),
-                file=sys.stderr
-            )
-
-    def _parse_rules_whitelist_entry(self, entry):
-        path = entry["path"]
-        package = entry["package"]
-        skip_digest_check = entry.get("skip-digest-check", False)
-
-        audits = entry.get("audits")
-
-        # it is thinkable that the same rules file is shipped by a
-        # different conflicting package, therefore support
-        # multiple packages claiming the same path
-        pkg_dict = self.rules.setdefault(path, {})
-
-        if package in pkg_dict:
-            print(self._get_err_prefix(), "duplicate entry for path {} and 
package {}".format(
-                path, package),
-                file=sys.stderr
-            )
-            return
-
-        pkg_dict[package] = {
-            "skip-digest-check": skip_digest_check,
-            "audits": audits
-        }
+        )
 
     def check_perm_files(self, pkg):
         """Checks files in polkit-default-privs.d."""
@@ -149,11 +78,13 @@
         permfiles = []
         # first pass, find additional files
         for f in files:
-            if f in pkg.ghostFiles():
-                continue
 
             if f.startswith(prefix):
 
+                if f in pkg.ghostFiles():
+                    printError(pkg, 'polkit-ghost-file', f)
+                    continue
+
                 bn = f[len(prefix):]
                 if bn not in POLKIT_PRIVS_WHITELIST:
                     printError(pkg, "polkit-unauthorized-file", f)
@@ -170,7 +101,7 @@
             f = pkg.dirName() + prefix + f
 
             for profile in profiles:
-                path = '.'.join(f, profile)
+                path = '.'.join((f, profile))
                 if os.path.exists(path):
                     self._parse_privs_file(path)
                     break
@@ -184,12 +115,13 @@
         prefix = "/usr/share/polkit-1/actions/"
 
         for f in files:
-            if f in pkg.ghostFiles():
-                continue
-
             # catch xml exceptions
             try:
                 if f.startswith(prefix):
+                    if f in pkg.ghostFiles():
+                        printError(pkg, 'polkit-ghost-file', f)
+                        continue
+
                     xml = parse(pkg.dirName() + f)
                     for a in xml.getElementsByTagName("action"):
                         self.check_action(pkg, a)
@@ -250,82 +182,7 @@
     def check_rules(self, pkg):
         """Process files and whitelist for entries in rules.d dirs."""
 
-        files = pkg.files()
-        rule_dirs = ("/etc/polkit-1/rules.d/", "/usr/share/polkit-1/rules.d/")
-
-        for f in files:
-            if f in pkg.ghostFiles():
-                continue
-
-            for rule_dir in rule_dirs:
-                if f.startswith(rule_dir):
-                    break
-            else:
-                # no match
-                continue
-
-            pkgs = self.rules.get(f, None)
-            wl_entry = pkgs.get(pkg.name, None) if pkgs else None
-
-            # TODO: at the moment these are only warnings while we're newly
-            # implementing this feature.
-            # We should turn these into errors with badness - or change our
-            # enforcement procedure in OBS to not require this any more.
-
-            if not pkgs or not wl_entry:
-                # no whitelist entry exists for this file
-                printWarning(pkg, 'polkit-unauthorized-rules', f)
-                continue
-
-            if wl_entry["skip-digest-check"]:
-                # for this package/file combination no file content digest
-                # verification needs to be performed, so we're already fine
-                continue
-
-            # check the newest entry first it is more likely to match what we
-            # have
-            for audit in reversed(wl_entry["audits"]):
-                digest_matches = self._checkDigest(pkg, f, audit["digest"])
-
-                if digest_matches:
-                    break
-            else:
-                # none of the digest entries matched
-                printWarning(pkg, 'polkit-changed-rules', f)
-                continue
-
-    def _checkDigest(self, pkg, path, digest_spec):
-        if not digest_spec:
-            return False
-
-        parts = digest_spec.split(':', 1)
-        if len(parts) != 2:
-            print(self._get_err_prefix(), "bad digest specification for 
package {} file {}".format(
-                pkg.name, path),
-                file=sys.stderr
-            )
-            return False
-
-        alg, digest = parts
-
-        try:
-            h = hashlib.new(alg)
-        except ValueError:
-            print(self._get_err_prefix(), "bad digest algorithm '{}' for 
package {} file {}".format(
-                alg, pkg.name, path),
-                file=sys.stderr
-            )
-            return False
-
-        with open(pkg.dirName() + path, 'rb') as fd:
-            while True:
-                data = fd.read(4096)
-                if not data:
-                    break
-
-                h.update(data)
-
-        return h.hexdigest() == digest
+        self.m_rules_checker.check(pkg)
 
     def check(self, pkg):
 
@@ -339,45 +196,57 @@
 
 check = PolkitCheck()
 
-AUDIT_BUG_URL = 
"https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs";
-
-addDetails(
-'polkit-unauthorized-file',
-"""A custom polkit rule file is installed by this package. If the package is
-intended for inclusion in any SUSE product please open a bug report to request
-review of the package by the security team. Please refer to {} for more
-information""".format(AUDIT_BUG_URL),
-
-'polkit-unauthorized-privilege',
-"""The package allows unprivileged users to carry out privileged
-operations without authentication. This could cause security
-problems if not done carefully. If the package is intended for
-inclusion in any SUSE product please open a bug report to request
-review of the package by the security team. Please refer to {}
-for more information.""".format(AUDIT_BUG_URL),
-
-'polkit-untracked-privilege',
-"""The privilege is not listed in /etc/polkit-default-privs.*
-which makes it harder for admins to find. Furthermore polkit
-authorization checks can easily introduce security issues. If the
-package is intended for inclusion in any SUSE product please open
-a bug report to request review of the package by the security team.
-Please refer to {} for more information.""".format(AUDIT_BUG_URL),
-
-'polkit-cant-acquire-privilege',
-"""Usability can be improved by allowing users to acquire privileges
-via authentication. Use e.g. 'auth_admin' instead of 'no' and make
-sure to define 'allow_any'. This is an issue only if the privilege
-is not listed in /etc/polkit-default-privs.*""",
-
-'polkit-unauthorized-rules',
-"""A polkit rules file installed by this package is not whitelisted in the
-polkit-whitelisting package. If the package is intended for inclusion in any
-SUSE product please open a bug report to request review of the package by the
-security team. Please refer to {} for more 
information.""".format(AUDIT_BUG_URL),
-
-'polkit-changed-rules',
-"""A polkit rules file installed by this package changed in content. Please
-open a bug report to request follow-up review of the introduced changes by
-the security team. Please refer to {} for more 
information.""".format(AUDIT_BUG_URL),
-)
+for _id, desc in (
+        (
+            'polkit-unauthorized-file',
+            """A custom polkit rule file is installed by this package. If the 
package is
+            intended for inclusion in any SUSE product please open a bug 
report to request
+            review of the package by the security team. Please refer to {url} 
for more
+            information"""
+        ),
+        (
+            'polkit-unauthorized-privilege',
+            """The package allows unprivileged users to carry out privileged
+            operations without authentication. This could cause security
+            problems if not done carefully. If the package is intended for
+            inclusion in any SUSE product please open a bug report to request
+            review of the package by the security team. Please refer to {url}
+            for more information."""
+        ),
+        (
+            'polkit-untracked-privilege',
+            """The privilege is not listed in /etc/polkit-default-privs.*
+            which makes it harder for admins to find. Furthermore polkit
+            authorization checks can easily introduce security issues. If the
+            package is intended for inclusion in any SUSE product please open
+            a bug report to request review of the package by the security team.
+            Please refer to {url} for more information."""
+        ),
+        (
+            'polkit-cant-acquire-privilege',
+            """Usability can be improved by allowing users to acquire 
privileges
+            via authentication. Use e.g. 'auth_admin' instead of 'no' and make
+            sure to define 'allow_any'. This is an issue only if the privilege
+            is not listed in /etc/polkit-default-privs.*"""
+        ),
+        (
+            'polkit-unauthorized-rules',
+            """A polkit rules file installed by this package is not 
whitelisted in the
+            polkit-whitelisting package. If the package is intended for 
inclusion in any
+            SUSE product please open a bug report to request review of the 
package by the
+            security team. Please refer to {url} for more information."""
+        ),
+        (
+            'polkit-changed-rules',
+            """A polkit rules file installed by this package changed in 
content. Please
+            open a bug report to request follow-up review of the introduced 
changes by
+            the security team. Please refer to {url} for more information."""
+        ),
+        (
+            'polkit-ghost-file',
+            """This package installs a polkit rule or policy as %ghost file.
+            This is not allowed as it is impossible to review. For more
+            information please refer to {url} for more information."""
+        )
+):
+    addDetails(_id, desc.format(url=Whitelisting.AUDIT_BUG_URL))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpmlint-checks-master/CheckSUIDPermissions.py 
new/rpmlint-checks-master/CheckSUIDPermissions.py
--- old/rpmlint-checks-master/CheckSUIDPermissions.py   2019-09-03 
14:03:37.000000000 +0200
+++ new/rpmlint-checks-master/CheckSUIDPermissions.py   2019-12-10 
13:24:44.000000000 +0100
@@ -1,4 +1,4 @@
-# vim:sw=4:et
+# vim: sw=4 et sts=4 ts=4 :
 #############################################################################
 # File          : CheckSUIDPermissions.py
 # Package       : rpmlint
@@ -10,6 +10,7 @@
 
 from Filter import printWarning, printError, printInfo, addDetails
 import AbstractCheck
+import Whitelisting
 import os
 import re
 import rpm
@@ -83,13 +84,15 @@
 
         permfiles = {}
         # first pass, find and parse permissions.d files
+        prefix = "/etc/permissions.d/"
         for f in files:
-            if f in pkg.ghostFiles():
-                continue
+            if f.startswith(prefix):
 
-            if f.startswith("/etc/permissions.d/"):
+                if f in pkg.ghostFiles():
+                    printError(pkg, 'polkit-ghost-file', f)
+                    continue
 
-                bn = f[19:]
+                bn = f[len(prefix):]
                 if bn not in _permissions_d_whitelist:
                     printError(pkg, "permissions-unauthorized-file", f)
 
@@ -240,57 +243,90 @@
 
 check = SUIDCheck()
 
-AUDIT_BUG_URL = 
"https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs";
-
-addDetails(
-'permissions-unauthorized-file',
-"""If the package is intended for inclusion in any SUSE product
-please open a bug report to request review of the package by the
-security team. Please refer to {} for more
-information.""".format(AUDIT_BUG_URL),
-'permissions-symlink',
-"""permissions handling for symlinks is useless. Please contact
[email protected] to remove the entry. Please refer to {} for more
-information.""".format(AUDIT_BUG_URL),
-'permissions-dir-without-slash',
-"""the entry in the permissions file refers to a directory. Please
-contact [email protected] to append a slash to the entry in order to
-avoid security problems. Please refer to {} for more 
information.""".format(AUDIT_BUG_URL),
-'permissions-file-as-dir',
-"""the entry in the permissions file refers to a directory but the
-package actually contains a file. Please contact [email protected] to
-remove the slash. Please refer to {} for more 
information.""".format(AUDIT_BUG_URL),
-'permissions-incorrect',
-"""please use the %attr macro to set the correct permissions.""",
-'permissions-incorrect-owner',
-"""please use the %attr macro to set the correct ownership.""",
-'permissions-file-setuid-bit',
-"""If the package is intended for inclusion in any SUSE product
-please open a bug report to request review of the program by the
-security team. Please refer to {} for more 
information.""".format(AUDIT_BUG_URL),
-'permissions-directory-setuid-bit',
-"""If the package is intended for inclusion in any SUSE product
-please open a bug report to request review of the package by the
-security team. Please refer to {} for more
-information.""".format(AUDIT_BUG_URL),
-'permissions-world-writable',
-"""If the package is intended for inclusion in any SUSE product
-please open a bug report to request review of the package by the
-security team. Please refer to {} for more
-information.""".format(AUDIT_BUG_URL),
-'permissions-fscaps',
-"""Packaging file capabilities is currently not supported. Please
-use normal permissions instead. You may contact the security team to
-request an entry that sets capabilities in /etc/permissions
-instead.""",
-'permissions-missing-postin',
-"""Please add an appropriate %post section""",
-'permissions-missing-requires',
-"""Please add \"PreReq: permissions\"""",
-'permissions-missing-verifyscript',
-"""Please add a %verifyscript section""",
-'permissions-suseconfig-obsolete',
-"""The %run_permissions macro calls SuSEconfig which sets permissions for all
-files in the system. Please use %set_permissions <filename> instead
-to only set permissions for files contained in this package""",
-)
+for _id, desc in (
+        (
+            'permissions-unauthorized-file',
+            """If the package is intended for inclusion in any SUSE product
+            please open a bug report to request review of the package by the
+            security team. Please refer to {url} for more
+            information."""
+        ),
+        (
+            'permissions-symlink',
+            """permissions handling for symlinks is useless. Please contact
+            [email protected] to remove the entry. Please refer to {url} for 
more
+            information."""
+        ),
+        (
+            'permissions-dir-without-slash',
+            """the entry in the permissions file refers to a directory. Please
+            contact [email protected] to append a slash to the entry in order to
+            avoid security problems. Please refer to {url} for more 
information."""
+        ),
+        (
+            'permissions-file-as-dir',
+            """the entry in the permissions file refers to a directory but the
+            package actually contains a file. Please contact [email protected] 
to
+            remove the slash. Please refer to {url} for more information."""
+        ),
+        (
+            'permissions-incorrect',
+            """please use the %attr macro to set the correct permissions."""
+        ),
+        (
+            'permissions-incorrect-owner',
+            """please use the %attr macro to set the correct ownership."""
+        ),
+        (
+            'permissions-file-setuid-bit',
+            """If the package is intended for inclusion in any SUSE product
+            please open a bug report to request review of the program by the
+            security team. Please refer to {url} for more information."""
+        ),
+        (
+            'permissions-directory-setuid-bit',
+            """If the package is intended for inclusion in any SUSE product
+            please open a bug report to request review of the package by the
+            security team. Please refer to {url} for more
+            information."""
+        ),
+        (
+            'permissions-world-writable',
+            """If the package is intended for inclusion in any SUSE product
+            please open a bug report to request review of the package by the
+            security team. Please refer to {url} for more
+            information."""
+        ),
+        (
+            'permissions-fscaps',
+            """Packaging file capabilities is currently not supported. Please
+            use normal permissions instead. You may contact the security team 
to
+            request an entry that sets capabilities in /etc/permissions
+            instead.""",
+        ),
+        (
+            'permissions-missing-postin',
+            """Please add an appropriate %post section"""
+        ),
+        (
+            'permissions-missing-requires',
+            """Please add 'PreReq: permissions'"""
+        ),
+        (
+            'permissions-missing-verifyscript',
+            """Please add a %verifyscript section"""
+        ),
+        (
+            'permissions-suseconfig-obsolete',
+            """The %run_permissions macro calls SuSEconfig which sets 
permissions for all
+            files in the system. Please use %set_permissions <filename> instead
+            to only set permissions for files contained in this package""",
+        ),
+        (
+            'permissions-ghostfile',
+            """This package installs a permissions file as a %ghost file. This
+            is not allowed as it is impossible to review. Please refer to
+            {url} for more information."""
+        )
+):
+    addDetails(_id, desc.format(url=Whitelisting.AUDIT_BUG_URL))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpmlint-checks-master/Whitelisting.py 
new/rpmlint-checks-master/Whitelisting.py
--- old/rpmlint-checks-master/Whitelisting.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/rpmlint-checks-master/Whitelisting.py   2019-12-10 13:24:44.000000000 
+0100
@@ -0,0 +1,400 @@
+# vim: sw=4 ts=4 sts=4 et :
+#############################################################################
+# Author        : Matthias Gerstner
+# Purpose       : reusable code for dealing with security whitelistings
+#############################################################################
+
+import os
+import sys
+import json
+import hashlib
+import traceback
+
+from Filter import printError
+
+AUDIT_BUG_URL = 
"https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs";
+
+
+class DigestVerificationResult(object):
+    """This type represents the result of a digest verification as returned
+    from AuditEntry.compareDigests()."""
+
+    def __init__(self, path, alg, expected, encountered):
+
+        self.m_path = path
+        self.m_alg = alg
+        self.m_expected = expected
+        self.m_encountered = encountered
+
+    def path(self):
+        return self.m_path
+
+    def algorithm(self):
+        return self.m_alg
+
+    def matches(self):
+        """Returns a boolean whether the encountered digest matches the
+        expected digest."""
+        return self.m_expected == self.m_encountered
+
+    def expected(self):
+        return self.m_expected
+
+    def encountered(self):
+        return self.m_encountered
+
+
+class AuditEntry(object):
+    """This object represents a single audit entry as found in a whitelisting
+    entry like:
+
+    "bsc#1234": {
+        "comment": "some comment",
+        "digests": {
+            "/some/file": "<alg>:<digest>",
+            ...
+        }
+    }
+
+    """
+
+    def __init__(self, bug):
+
+        self.m_bug = bug
+        self._verifyBugNr()
+        self.m_comment = ""
+        self.m_digests = {}
+
+    def bug(self):
+        return self.m_bug
+
+    def setComment(self, comment):
+        self.m_comment = comment
+
+    def comment(self):
+        return self.m_comment
+
+    def setDigests(self, digests):
+        for path, digest in digests.items():
+            self._verifyPath(path)
+            self._verifyDigestSyntax(digest)
+
+        self.m_digests = digests
+
+    def digests(self):
+        """Returns a dictionary specifying file paths and their whitelisted
+        digests. The digests are suitable for the
+        Python hashlib module. They're of the form '<alg>:<hexdigest>'. As a
+        special case the digest entry can be 'skip:<none>' which indicates
+        that no digest verification should be performed and the file is
+        acceptable regardless of its contents."""
+        return self.m_digests
+
+    def isSkipDigest(self, digest):
+        """Returns whether the given digest entry denotes the special "skip
+        digest" case which means not to check the file digest at all."""
+        return digest == 'skip:<none>'
+
+    def compareDigests(self, pkg):
+        """Compares the digests recorded in this AuditEntry against the actual
+        files coming from the given rpmlint @pkg. Returns a tuple of
+        (boolean, [DigestVerificationResult, ...]). The boolean indicates the
+        overall verification result, while the list of
+        DigestVerificationResult entries provides detailed information about
+        the encountered data. Any "skip digest" entries will be ignored and
+        not be included in the result list."""
+
+        results = []
+
+        # NOTE: syntax and algorithm validity of stored digests was already
+        # checked in setDigests() so we can skip the respective error handling
+        # here.
+
+        for path, digest in self.digests().items():
+            if self.isSkipDigest(digest):
+                continue
+
+            alg, digest = digest.split(':', 1)
+
+            try:
+                h = hashlib.new(alg)
+
+                # NOTE: this path is dynamic and rpmlint unpacks the RPM
+                # contents into a temporary directory even when outside the
+                # build environment i.e. the file content should always be
+                # available to us.
+                with open(pkg.dirName() + path, 'rb') as fd:
+                    while True:
+                        chunk = fd.read(4096)
+                        if not chunk:
+                            break
+
+                        h.update(chunk)
+
+                    encountered = h.hexdigest()
+            except IOError as e:
+                encountered = "error:" + str(e)
+
+            dig_res = DigestVerificationResult(path, alg, digest, encountered)
+            results.append(dig_res)
+
+        return (all([res.matches() for res in results]), results)
+
+    def _verifyBugNr(self):
+        """Perform some sanity checks on the bug nr associated with this audit
+        entry."""
+
+        parts = self.m_bug.split('#')
+
+        if len(parts) != 2 or \
+                parts[0] not in ("bsc", "boo", "bnc") or \
+                not parts[1].isdigit():
+            raise Exception("Bad bug nr# '{}'".format(self.m_bug))
+
+    def _verifyDigestSyntax(self, digest):
+        if self.isSkipDigest(digest):
+            return
+
+        parts = digest.split(':')
+        if len(parts) != 2:
+            raise Exception("Bad digest specification " + digest)
+
+        alg, hexdigest = parts
+
+        try:
+            hashlib.new(alg)
+        except ValueError:
+            raise Exception("Bad digest algorithm in " + digest)
+
+    def _verifyPath(self, path):
+        if not path.startswith(os.path.sep):
+            raise Exception("Bad whitelisting path " + path)
+
+
+class WhitelistEntry(object):
+    """This object represents a single whitelisting entry like:
+
+    "somepackage" {
+        "audits": {
+            ...
+        }
+    },
+    """
+
+    def __init__(self, package):
+        self.m_package = package
+        # a list of AuditEntry objects associated with this whitelisting entry
+        self.m_audits = []
+
+    def package(self):
+        return self.m_package
+
+    def addAudit(self, audit):
+        self.m_audits.append(audit)
+
+    def audits(self):
+        return self.m_audits
+
+
+class WhitelistParser(object):
+    """This type knows how to parse the JSON whitelisting format. The format
+    is documented in [1].
+
+    [1]: 
https://github.com/openSUSE/rpmlint-security-whitelistings/blob/master/README.md
+    """
+
+    def __init__(self, wl_path):
+        """Creates a new instance of WhitelistParser that operates on
+        @wl_path."""
+
+        self.m_path = wl_path
+
+    def parse(self):
+        """Parses the whitelisting file and returns a dictionary of the
+        following structure:
+
+        {
+            "path/to/file": [WhitelistEntry(), ...],
+            ...
+        }
+
+        Since a single path might be claimed by more than one package the
+        values of the dictionary are lists, to cover for this possibility.
+        """
+
+        ret = {}
+
+        try:
+            with open(self.m_path, 'r') as fd:
+                data = json.load(fd)
+
+                for pkg, config in data.items():
+                    entry = self._parseWhitelistEntry(pkg, config)
+                    if not entry:
+                        # soft error, continue parsing
+                        continue
+                    for a in entry.audits():
+                        for path in a.digests():
+                            entries = ret.setdefault(path, [])
+                            entries.append(entry)
+        except Exception as e:
+            _, _, tb = sys.exc_info()
+            fn, ln, _, _ = traceback.extract_tb(tb)[-1]
+            raise Exception(self._getErrorPrefix() + "Failed to parse JSON 
file: {}:{}: {}".format(
+                fn, ln, str(e)
+            ))
+
+        return ret
+
+    def _parseWhitelistEntry(self, package, config):
+        """Parses a single JSON whitelist entry and returns a WhitelistEntry()
+        object for it. On non-critical error conditions None is returned,
+        otherwise an exception is raised."""
+
+        ret = WhitelistEntry(package)
+
+        audits = config.get("audits", {})
+
+        if not audits:
+            raise Exception(self._getErrorPrefix() + "no 'audits' entries for 
package {}".format(package))
+
+        for bug, data in audits.items():
+            try:
+                audit = self._parseAuditEntry(bug, data)
+            except Exception as e:
+                raise Exception(self._getErrorPrefix() + "Failed to parse 
audit entries: " + str(e))
+
+            if not audit:
+                # soft error, continue parsing
+                continue
+            ret.addAudit(audit)
+
+        return ret
+
+    def _parseAuditEntry(self, bug, data):
+        """Parses a single JSON audit sub-entry returns an AuditEntry() object
+        for it. On non-critical error conditions None is returned, otherwise
+        an exception is raised"""
+
+        ret = AuditEntry(bug)
+
+        comment = data.get("comment", None)
+        if comment:
+            ret.setComment(comment)
+
+        digests = data.get("digests", {})
+
+        if not digests:
+            raise Exception(self._getErrorPrefix() + "no 'digests' entry for 
'{}'".format(bug))
+
+        ret.setDigests(digests)
+
+        return ret
+
+    def _getErrorPrefix(self):
+        return self.m_path + ": ERROR: "
+
+    def _getWarnPrefix(self):
+        return self.m_path + ": WARN: "
+
+
+class WhitelistChecker(object):
+    """This type actually compares files found in an RPM against whitelist
+    entries."""
+
+    def __init__(self, whitelist_entries, restricted_paths, error_map):
+        """Instantiate a properly configured checker
+
+        :param whitelist_entries: is a dictionary data structure as returned
+                                  from WhitelistParser.parse().
+        :param restricted_paths: a sequence of path prefixes that will trigger
+                                 the whitelisting check. All other paths will
+                                 be ignored.
+        :param error_map: is a specification of rpmlint error labels for ghost
+                          files, unauthorized files and changed files like:
+                          {
+                            "unauthorized": "polkit-unauthorized-rules",
+                            "changed": "polkit-changed-rules",
+                            "ghost": "polkit-ghost-file"
+                          }
+        """
+
+        self.m_restricted_paths = restricted_paths
+        self.m_whitelist_entries = whitelist_entries
+        self.m_error_map = error_map
+
+        req_error_keys = ("unauthorized", "changed", "ghost")
+
+        for req_key in req_error_keys:
+            if req_key not in self.m_error_map:
+                raise Exception("Missing {} error mapping".format(req_key))
+
+    def check(self, pkg):
+        """Checks the given RPM pkg instance against the configured whitelist
+        restriction.
+
+        Each whitelist violation will be printed with the according error tag.
+        Nothing is returned from this function.
+        """
+
+        if pkg.isSource():
+            return
+
+        files = pkg.files()
+
+        for f in files:
+            for restricted in self.m_restricted_paths:
+                if f.startswith(restricted):
+                    break
+            else:
+                # no match
+                continue
+
+            if f in pkg.ghostFiles():
+                printError(pkg, self.m_error_map['ghost'], f)
+                continue
+
+            entries = self.m_whitelist_entries.get(f, [])
+            wl_match = None
+            for entry in entries:
+                if entry.package() == pkg.name:
+                    wl_match = entry
+                    break
+            else:
+                # no whitelist entry exists for this file
+                printError(pkg, self.m_error_map['unauthorized'], f)
+                continue
+
+            # for the case that there's no match of digests, remember the most
+            # recent digest verification result for diagnosis output towards
+            # the user
+            diag_results = None
+
+            # check the newest (bottom) entry first it is more likely to match
+            # what we have
+            for audit in reversed(wl_match.audits()):
+                digest_matches, results = audit.compareDigests(pkg)
+
+                if digest_matches:
+                    break
+
+                if not diag_results:
+                    diag_results = results
+            else:
+                # none of the digest entries matched
+                self._printVerificationResults(diag_results)
+                printError(pkg, self.m_error_map['changed'], f)
+                continue
+
+    def _printVerificationResults(self, verification_results):
+        """For the case of changed file digests this function prints the
+        encountered and expected digests and paths for diagnostic purposes."""
+
+        for result in verification_results:
+            if result.matches():
+                continue
+
+            print("{path}: expected {alg} digest {expected} but encountered 
{encountered}".format(
+                path=result.path(), alg=result.algorithm(),
+                expected=result.expected(), encountered=result.encountered()
+            ), file=sys.stderr)


Reply via email to