Hello community, here is the log from the commit of package rpmlint for openSUSE:Factory checked in at 2020-08-03 14:12:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rpmlint (Old) and /work/SRC/openSUSE:Factory/.rpmlint.new.3592 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rpmlint" Mon Aug 3 14:12:32 2020 rev:341 rq:823733 version:1.11 Changes: -------- --- /work/SRC/openSUSE:Factory/rpmlint/rpmlint-tests.changes 2020-03-03 10:13:55.206362333 +0100 +++ /work/SRC/openSUSE:Factory/.rpmlint.new.3592/rpmlint-tests.changes 2020-08-03 14:12:46.248341737 +0200 @@ -1,0 +2,6 @@ +Fri Jul 31 10:28:58 UTC 2020 - [email protected] + +- Update of rpmlint-tests to version 84.87+git20200724.ef05f7e: + * use /usr/bin/su instead of /bin/su since the latter is no longer allowed + +------------------------------------------------------------------- --- /work/SRC/openSUSE:Factory/rpmlint/rpmlint.changes 2020-07-29 17:16:57.124387372 +0200 +++ /work/SRC/openSUSE:Factory/.rpmlint.new.3592/rpmlint.changes 2020-08-03 14:12:47.236342728 +0200 @@ -1,0 +2,9 @@ +Fri Jul 31 10:29:00 UTC 2020 - [email protected] + +- Update of rpmlint-checks to version master: + * Introduce new metadata whitelist type and related checks. Device files and + world-writeable files will now be covered by new whitelists from + rpmlint-security-whitelistings. +- config: Enable new CheckWorldWritable and CheckDeviceFiles + +------------------------------------------------------------------- Old: ---- rpmlint-tests-84.87+git20200221.3ea152b.tar.xz New: ---- rpmlint-tests-84.87+git20200724.ef05f7e.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rpmlint-tests.spec ++++++ --- /var/tmp/diff_new_pack.exRsbc/_old 2020-08-03 14:12:48.340343835 +0200 +++ /var/tmp/diff_new_pack.exRsbc/_new 2020-08-03 14:12:48.344343839 +0200 @@ -24,7 +24,7 @@ BuildRequires: rpmlint-mini Name: rpmlint-tests -Version: 84.87+git20200221.3ea152b +Version: 84.87+git20200724.ef05f7e Release: 0 Summary: rpmlint regression tests License: SUSE-Public-Domain ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.exRsbc/_old 2020-08-03 14:12:48.424343919 +0200 +++ /var/tmp/diff_new_pack.exRsbc/_new 2020-08-03 14:12:48.424343919 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/openSUSE/rpmlint-tests.git</param> - <param name="changesrevision">3ea152ba41f080462891f99711fee3712c56c8c7</param></service><service name="tar_scm"> + <param name="changesrevision">3d948bb4c8be26e2ec8922d4c3430b0e0451994b</param></service><service name="tar_scm"> <param name="url">https://github.com/openSUSE/rpmlint-checks.git</param> - <param name="changesrevision">00c0040faa30370f367de0d2bec3e7449db8c44b</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">9db2d998028dac60a5c5e16af303693b158b7272</param></service></servicedata> \ No newline at end of file ++++++ config ++++++ --- /var/tmp/diff_new_pack.exRsbc/_old 2020-08-03 14:12:48.444343939 +0200 +++ /var/tmp/diff_new_pack.exRsbc/_new 2020-08-03 14:12:48.444343939 +0200 @@ -28,6 +28,8 @@ addCheck("CheckSUIDPermissions") # polkit-default-privs would need to be installed always addCheck("CheckPolkitPrivs") +addCheck("CheckWorldWritable") +addCheck("CheckDeviceFiles") addCheck("CheckDBUSServices") addCheck("CheckDBusPolicy") addCheck("CheckFilelist") ++++++ 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 2020-03-31 11:35:13.000000000 +0200 +++ new/rpmlint-checks-master/CheckCronJobs.py 2020-07-31 10:46:46.000000000 +0200 @@ -4,40 +4,20 @@ # Purpose : Enforce Whitelisting for cron jobs in /etc/cron.* directories ############################################################################# -import os - -import AbstractCheck -import Config import Whitelisting +from WhitelistingCheckBase import WhitelistingCheckBase -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): +class CronCheck(WhitelistingCheckBase): 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 + super().__init__("CheckCronJobs", "cron-whitelist.json") - self.m_check_configured = whitelist_path is not None + def setupChecker(self, whitelist_path): - if not self.m_check_configured: - return - - parser = Whitelisting.WhitelistParser(whitelist_path) + parser = Whitelisting.DigestWhitelistParser(whitelist_path) whitelist_entries = parser.parse() - self.m_wl_checker = Whitelisting.WhitelistChecker( + return Whitelisting.DigestWhitelistChecker( whitelist_entries, restricted_paths=( "/etc/cron.d/", "/etc/cron.hourly/", "/etc/cron.daily/", @@ -50,51 +30,24 @@ } ) - 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 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)) +Whitelisting.registerErrorDetails(( + ( + 'cronjob-unauthorized-file', + """A cron job file is installed by this package. {review_needed_text}""" + ), + ( + 'cronjob-changed-file', + """A cron job or cron job related file installed by this package changed + in content. {followup_needed_text}""" + ), + ( + '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.""" + ) +)) 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 2020-03-31 11:35:13.000000000 +0200 +++ new/rpmlint-checks-master/CheckDBUSServices.py 2020-07-31 10:46:46.000000000 +0200 @@ -51,19 +51,16 @@ check = DBUSServiceCheck() if Config.info: - for _id, desc in ( + Whitelisting.registerErrorDetails(( ( '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.""" + """The package installs a DBUS system service file. + {review_needed_text}""" ), ( '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.""" + {ghost_encountered_text} + """ ) - ): - addDetails(_id, desc.format(url=Whitelisting.AUDIT_BUG_URL)) + )) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-checks-master/CheckDeviceFiles.py new/rpmlint-checks-master/CheckDeviceFiles.py --- old/rpmlint-checks-master/CheckDeviceFiles.py 1970-01-01 01:00:00.000000000 +0100 +++ new/rpmlint-checks-master/CheckDeviceFiles.py 2020-07-31 10:46:46.000000000 +0200 @@ -0,0 +1,49 @@ +# vim: sw=4 ts=4 sts=4 et : +############################################################################# +# Author : Matthias Gerstner +# Purpose : Enforce Whitelisting for device files +############################################################################# + +import Whitelisting +from WhitelistingCheckBase import WhitelistingCheckBase + + +class DeviceFilesCheck(WhitelistingCheckBase): + + def __init__(self): + super().__init__("CheckDeviceFiles", "device-files-whitelist.json") + + def setupChecker(self, whitelist_path): + + parser = Whitelisting.MetaWhitelistParser(whitelist_path) + whitelist_entries = parser.parse() + return Whitelisting.MetaWhitelistChecker( + whitelist_entries, + error_map={ + "unauthorized": "device-unauthorized-file", + "mismatch": "device-mismatched-attrs", + }, + # we are interested in any device files + restricted_types=("c", "b"), + # regardless the mode we want to catch all device files + restricted_mode=0o7777 + ) + + +# needs to be instantiated for the check to be registered with rpmlint +check = DeviceFilesCheck() + +Whitelisting.registerErrorDetails(( + ( + 'device-unauthorized-file', + """A device file is installed by this package. + {review_needed_text}""" + ), + ( + 'device-mismatched-attrs', + """A device file doesn't match the expected file properties. + 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.""" + ) +)) 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 2020-03-31 11:35:13.000000000 +0200 +++ new/rpmlint-checks-master/CheckPAMModules.py 2020-07-31 10:46:46.000000000 +0200 @@ -44,19 +44,14 @@ if Config.info: - for _id, desc in ( + Whitelisting.registerErrorDetails(( ( '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}""" + """The package installs a PAM module. {review_needed_text}""" ), ( '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.""" + """The package installs a PAM module as %ghost file. + {ghost_encountered_text}""" ) - ): - 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 2020-03-31 11:35:13.000000000 +0200 +++ new/rpmlint-checks-master/CheckPolkitPrivs.py 2020-07-31 10:46:46.000000000 +0200 @@ -52,11 +52,11 @@ for filename in POLKIT_RULES_WHITELIST: if not os.path.exists(filename): continue - parser = Whitelisting.WhitelistParser(filename) + parser = Whitelisting.DigestWhitelistParser(filename) res = parser.parse() rules_entries.update(res) - self.m_rules_checker = Whitelisting.WhitelistChecker( + self.m_rules_checker = Whitelisting.DigestWhitelistChecker( rules_entries, restricted_paths=( "/etc/polkit-1/rules.d/", "/usr/share/polkit-1/rules.d/" @@ -196,57 +196,56 @@ check = PolkitCheck() -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)) +Whitelisting.registerErrorDetails(( + ( + '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.""" + ) +)) 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 2020-03-31 11:35:13.000000000 +0200 +++ new/rpmlint-checks-master/CheckSUIDPermissions.py 2020-07-31 10:46:46.000000000 +0200 @@ -8,7 +8,7 @@ from __future__ import print_function -from Filter import printWarning, printError, printInfo, addDetails +from Filter import printWarning, printError, printInfo import AbstractCheck import Whitelisting import os @@ -18,15 +18,10 @@ import stat _permissions_d_whitelist = ( - "lprng", - "lprng.paranoid", - "mail-server", - "mail-server.paranoid", "postfix", "postfix.paranoid", "sendmail", "sendmail.paranoid", - "squid", "texlive", "texlive.texlive", "otrs", # bsc#1118049 @@ -194,12 +189,6 @@ 'pie executable' not in pkgfile.magic): printError(pkg, 'non-position-independent-executable', f) - if mode & stat.S_IWOTH: - need_verifyscript = True - printError(pkg, 'permissions-world-writable', - '%(file)s is packaged with world writable permissions (0%(mode)o)' % - {'file': f, 'mode': mode}) - script = pkg[rpm.RPMTAG_POSTIN] or pkg.scriptprog(rpm.RPMTAG_POSTINPROG) found = False if script: @@ -247,90 +236,73 @@ check = SUIDCheck() -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 - /usr/share/permissions/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)) +Whitelisting.registerErrorDetails(( + ( + 'permissions-unauthorized-file', + """{review_needed_text}""" + ), + ( + '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', + """{review_needed_text}""" + ), + ( + 'permissions-directory-setuid-bit', + """{review_needed_text}""" + ), + ( + '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 + /usr/share/permissions/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. + {ghost_encountered_text}""" + ) +)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-checks-master/CheckWorldWritable.py new/rpmlint-checks-master/CheckWorldWritable.py --- old/rpmlint-checks-master/CheckWorldWritable.py 1970-01-01 01:00:00.000000000 +0100 +++ new/rpmlint-checks-master/CheckWorldWritable.py 2020-07-31 10:46:46.000000000 +0200 @@ -0,0 +1,50 @@ +# vim: sw=4 ts=4 sts=4 et : +############################################################################# +# Author : Matthias Gerstner +# Purpose : Enforce Whitelisting for world writable files +############################################################################# + +import Whitelisting +from WhitelistingCheckBase import WhitelistingCheckBase + + +class WorldWritableCheck(WhitelistingCheckBase): + + def __init__(self): + super().__init__("CheckWorldWritable", "world-writable-whitelist.json") + + def setupChecker(self, whitelist_path): + + parser = Whitelisting.MetaWhitelistParser(whitelist_path) + whitelist_entries = parser.parse() + return Whitelisting.MetaWhitelistChecker( + whitelist_entries, + error_map={ + "unauthorized": "world-writable-unauthorized-file", + "mismatch": "world-writable-mismatched-attrs", + }, + # we're only interested in directories, regular files, pipes or + # sockets. + # devices are handled by the DeviceFileChecker. Symlinks are + # always world-writable. + restricted_types=("-", "f", "d", "s", "p"), + # we're interested in any world-writable files + restricted_mode=0o0002, + ) + + +# needs to be instantiated for the check to be registered with rpmlint +check = WorldWritableCheck() + +Whitelisting.registerErrorDetails(( + ( + 'world-writable-unauthorized-file', + """A world-writable file is installed by this package. + {review_needed_text}""" + ), + ( + 'world-writable-mismatched-attrs', + """A world-writable file doesn't match the expected file + properties. {followup_needed_text}""" + ) +)) 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 2020-03-31 11:35:13.000000000 +0200 +++ new/rpmlint-checks-master/Whitelisting.py 2020-07-31 10:46:46.000000000 +0200 @@ -6,18 +6,43 @@ import os import sys -import json import hashlib +import json +import stat import traceback -from Filter import printError - AUDIT_BUG_URL = "https://en.opensuse.org/openSUSE:Package_security_guidelines#audit_bugs" +REVIEW_NEEDED_TEXT = """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.""".format(url=AUDIT_BUG_URL) +FOLLOWUP_NEEDED_TEXT = """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.""" +GHOST_ENCOUNTERED_TEXT = """This is not allowed, since it is impossible to + review. Please refer to {url} for more information.""" + + +def registerErrorDetails(details): + """details is expected to be a sequence of (id, description) pairs, where + id is the error id like 'cronjob-unauthorized-file' and description is a + human readable text describing the situation. The text may contain + placeholders that will be replaced by the constants above.""" + from Filter import addDetails + + for _id, desc in details: + addDetails( + _id, + desc.format( + url=AUDIT_BUG_URL, + review_needed_text=REVIEW_NEEDED_TEXT, + followup_needed_text=FOLLOWUP_NEEDED_TEXT, + ghost_encountered_text=GHOST_ENCOUNTERED_TEXT)) class DigestVerificationResult(object): """This type represents the result of a digest verification as returned - from AuditEntry.compareDigests().""" + from DigestAuditEntry.compareDigests().""" def __init__(self, path, alg, expected, encountered): @@ -44,7 +69,7 @@ return self.m_encountered -class AuditEntry(object): +class AuditEntryBase(object): """This object represents a single audit entry as found in a whitelisting entry like: @@ -63,7 +88,6 @@ self.m_bug = bug self._verifyBugNr() self.m_comment = "" - self.m_digests = {} def bug(self): return self.m_bug @@ -74,6 +98,29 @@ def comment(self): return self.m_comment + 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 _verifyPath(self, path): + if not path.startswith(os.path.sep): + raise Exception("Bad whitelisting path " + path) + + +class DigestAuditEntry(AuditEntryBase): + + def __init__(self, bug): + + super().__init__(bug) + self.m_digests = {} + def setDigests(self, digests): for path, digest in digests.items(): self._verifyPath(path) @@ -81,6 +128,9 @@ self.m_digests = digests + def paths(self): + return self.digests().keys() + def digests(self): """Returns a dictionary specifying file paths and their whitelisted digests. The digests are suitable for the @@ -163,17 +213,6 @@ 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 @@ -189,9 +228,135 @@ 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 MetaAuditEntry(AuditEntryBase): + + def __init__(self, bug): + + super().__init__(bug) + self.m_meta = {} + + def paths(self): + return self.meta().keys() + + def setMeta(self, meta): + for path, data in meta.items(): + self._verifyPath(path) + self._verifyMetaData(path, data) + + self.m_meta = meta + + def meta(self): + """Returns a dictionary specifying file paths and their whitelisted + metadata attributes: + + "type": one of 'c', 'd', 's' or '-' + "mode": integer defining the file mode + "owner": tuple of (user, group) defining the ownership + "dev": tuple of (minor, major) integers defining device file numbers + """ + return self.m_meta + + def _verifyMetaData(self, path, data): + """Verify and CONVERT metadata.""" + + req_fields = ("type", "mode", "owner") + + for field in req_fields: + if field not in data: + raise Exception("Missing required setting '{}' for path {}".format(field, path)) + + _type = data["type"] + + if _type not in ("c", "d", "s", "-"): + raise Exception("Unexpected type '{}' for path {}".format(_type, path)) + + try: + data["mode"] = int(data["mode"], 8) + + if data["mode"] > 0o7777: + raise ValueError("octal mode too large") + except ValueError: + raise Exception("Bad 'mode' for path " + path) + + if _type == "c" and "dev" not in data: + raise Exception("Missing 'dev' for path " + path) + elif _type != "c" and "dev" in data: + raise Exception("Unsuitable 'dev' specification for path " + path) + + if "dev" in data: + try: + major, minor = data["dev"].split(",") + data["dev"] = int(major), int(minor) + except Exception as e: + raise Exception("Bad 'dev' specification for path {}: {}".format(path, str(e))) + + try: + user, group = data["owner"].split(":") + data["owner"] = user, group + except Exception as e: + raise Exception("Bad 'owner' specification for path {}: {}".format(path, str(e))) + + def _isWeakerMode(self, ours, theirs): + """Checks whether the mode @theirs only grants less permissions than + what @ours would grant.""" + + if (ours & stat.S_ISVTX) and not (theirs & stat.S_ISVTX): + # if it's the sticky bit that's missing then we can't consider the + # encountered mode weaker. The sticky bit might be necessary to + # protect shared world-writable directories. + return False + + # otherwise if there's no extra bit in their mode then it should be + # weaker or equal to ours, security wise + return (ours | theirs) == ours + + def compareMeta(self, pkg, path, their_meta): + our_meta = self.m_meta.get(path) + warning = "" + + their_mode_str = stat.filemode(their_meta.mode) + their_type = their_mode_str[0] + + if their_type != our_meta["type"]: + msg = "type mismatch, expected type {} but encountered type {}".format( + our_meta["type"], their_type + ) + return (False, msg) + + their_mode = stat.S_IMODE(their_meta.mode) + + if their_mode != our_meta["mode"]: + + if self._isWeakerMode(our_meta["mode"], their_mode): + # if there are no extra bits set then we can accept it + # anyways, however we should still warn that something is + # unexpected. + warning = "mode doesn't match but grants less permissions than expected" + else: + msg = "mode mismatch, expected mode {} but encountered mode {}".format( + stat.filemode(our_meta["mode"])[1:], stat.filemode(their_meta.mode)[1:] + ) + return (False, msg) + + if their_meta.user != our_meta["owner"][0] or their_meta.group != our_meta["owner"][1]: + msg = "ownership mismatch, expected {} but encountered {}".format( + ':'.join(our_meta["owner"]), ':'.join(their_meta.user, their_meta.group) + ) + return (False, msg) + + if their_type in ("c", "b"): + their_rdev = their_meta.rdev + their_major, their_minor = os.major(their_rdev), os.minor(their_rdev) + our_major, our_minor = our_meta["dev"] + + if their_major != our_major or their_minor != our_minor: + msg = "device node mismatch, expected {} but encountered {}".format( + ','.join(our_major, our_minor), ','.join(their_major, their_minor) + ) + return (False, msg) + + return (True, warning) class WhitelistEntry(object): @@ -257,7 +422,7 @@ # soft error, continue parsing continue for a in entry.audits(): - for path in a.digests(): + for path in a.paths(): entries = ret.setdefault(path, []) entries.append(entry) except Exception as e: @@ -294,12 +459,25 @@ return ret + def _getErrorPrefix(self): + return self.m_path + ": ERROR: " + + def _getWarnPrefix(self): + return self.m_path + ": WARN: " + + +class DigestWhitelistParser(WhitelistParser): + + def __init__(self, wl_path): + + super().__init__(wl_path) + 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) + ret = DigestAuditEntry(bug) comment = data.get("comment", None) if comment: @@ -308,28 +486,49 @@ digests = data.get("digests", {}) if not digests: - raise Exception(self._getErrorPrefix() + "no 'digests' entry for '{}'".format(bug)) + raise Exception(self._getErrorPrefix() + "missing 'digests' 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 MetaWhitelistParser(WhitelistParser): + def __init__(self, wl_path): -class WhitelistChecker(object): - """This type actually compares files found in an RPM against whitelist - entries.""" + super().__init__(wl_path) + + 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 = MetaAuditEntry(bug) + + comment = data.get("comment", None) + if comment: + ret.setComment(comment) + + meta = data.get("meta", {}) + + if not meta: + raise Exception(self._getErrorPrefix() + "missing 'meta' entry for '{}'".format(bug)) + + ret.setMeta(meta) + + return ret + + +class DigestWhitelistChecker(object): + """This type actually compares files found in an RPM against digest + 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(). + from DigestWhitelistParser.parse(). :param restricted_paths: a sequence of path prefixes that will trigger the whitelisting check. All other paths will be ignored. @@ -352,6 +551,21 @@ if req_key not in self.m_error_map: raise Exception("Missing {} error mapping".format(req_key)) + def _isRestrictedPath(self, path): + for restricted in self.m_restricted_paths: + if path.startswith(restricted): + return True + + return False + + def _getWhitelist(self, pkg_name, path): + entries = self.m_whitelist_entries.get(path, []) + for entry in entries: + if entry.package() == pkg_name: + return entry + + return None + def check(self, pkg): """Checks the given RPM pkg instance against the configured whitelist restriction. @@ -360,6 +574,8 @@ Nothing is returned from this function. """ + from Filter import printError + if pkg.isSource(): return @@ -367,24 +583,16 @@ already_tested = set() for f in files: - for restricted in self.m_restricted_paths: - if f.startswith(restricted): - break - else: - # no match + if not self._isRestrictedPath(f): 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: + wl_match = self._getWhitelist(pkg.name, f) + + if not wl_match: # no whitelist entry exists for this file printError(pkg, self.m_error_map['unauthorized'], f) continue @@ -434,3 +642,110 @@ path=result.path(), alg=result.algorithm(), expected=result.expected(), encountered=result.encountered() ), file=sys.stderr) + + +class MetaWhitelistChecker(object): + """This type actually compares files found in an RPM against whitelist + entries.""" + + def __init__(self, whitelist_entries, error_map, restricted_mode, restricted_types): + """Instantiate a properly configured checker. For metadata + restrictions both `restricted_mode` and `restricted_types` need to + match for a check to be triggered. + + :param whitelist_entries: is a dictionary data structure as returned + from MetaWhitelistParser.parse(). + :param error_map: is a specification of rpmlint error labels for + files like "unauthorized" and "mismatch" + { + "unauthorized": "special-file-unauthorized", + "mismatch": "special-file-mismatch" + } + :param restricted_mode: an octal bit mask that specifies file mode + bits that are restricted by this whitelist. + e.g. 0o001 would trigger a check for all files + containing a world executable bit. 0o7777 + would catch any mode. + :param restricted_types: a sequence of file types that are restricted + by this whitelist. E.g. ("f", "s") would + trigger a check for all regular files and + socket files. An entry of "*" will match all + file types. + """ + + self.m_whitelist_entries = whitelist_entries + self.m_error_map = error_map + self.m_restricted_mode = restricted_mode + self.m_restricted_types = restricted_types + + req_error_keys = ("unauthorized", "mismatch") + + 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 _hasRestrictedMeta(self, meta): + + if self.m_restricted_mode == 0o7777: + # all modes should match so ignore it + pass + elif (meta.mode & self.m_restricted_mode) == 0: + # none of the interesting mode bits matches + return False + + if "*" in self.m_restricted_types: + # match all file types + return True + elif stat.filemode(meta.mode)[0] in self.m_restricted_types: + # filemode() returns an ls like string like `-rwx------`. + # we # inspect the type character and compare it against our list + # of restricted file types + return True + + return False + + def _getWhitelist(self, pkg_name, path): + entries = self.m_whitelist_entries.get(path, []) + for entry in entries: + if entry.package() == pkg_name: + return entry + + return None + + 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. + """ + + from Filter import printError + + if pkg.isSource(): + return + + files = pkg.files() + + for f, meta in files.items(): + if not self._hasRestrictedMeta(meta): + continue + + wl_match = self._getWhitelist(pkg.name, f) + + if not wl_match: + # no whitelist entry exists for this file + printError(pkg, self.m_error_map['unauthorized'], f) + continue + + for audit in wl_match.audits(): + res, msg = audit.compareMeta(pkg, f, meta) + + if res: + if msg: + # a warning only message + print("{}: {}".format(f, msg), file=sys.stderr) + break + + print("{}: {}".format(f, msg), file=sys.stderr) + printError(pkg, self.m_error_map['mismatch'], f) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-checks-master/WhitelistingCheckBase.py new/rpmlint-checks-master/WhitelistingCheckBase.py --- old/rpmlint-checks-master/WhitelistingCheckBase.py 1970-01-01 01:00:00.000000000 +0100 +++ new/rpmlint-checks-master/WhitelistingCheckBase.py 2020-07-31 10:46:46.000000000 +0200 @@ -0,0 +1,42 @@ +# vim: sw=4 ts=4 sts=4 et : +############################################################################# +# Author : Matthias Gerstner +# Purpose : Common base class for whitelisting related checks +############################################################################# +import AbstractCheck +import Config + +import os + + +class WhitelistingCheckBase(AbstractCheck.AbstractCheck): + """Base class for rpmlint checks that use the Whitelisting module.""" + + def __init__(self, check_name, whitelist_name): + AbstractCheck.AbstractCheck.__init__(self, check_name) + # 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', []) + + for wd in WHITELIST_DIR: + candidate = os.path.join(wd, whitelist_name) + if os.path.exists(candidate): + whitelist_path = candidate + self.m_check_configured = True + break + else: + self.m_check_configured = False + + if self.m_check_configured: + self.m_wl_checker = self.setupChecker(whitelist_path) + + 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) ++++++ rpmlint-tests-84.87+git20200221.3ea152b.tar.xz -> rpmlint-tests-84.87+git20200724.ef05f7e.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-tests-84.87+git20200221.3ea152b/tests/permissions1.ref new/rpmlint-tests-84.87+git20200724.ef05f7e/tests/permissions1.ref --- old/rpmlint-tests-84.87+git20200221.3ea152b/tests/permissions1.ref 2020-02-21 11:06:10.000000000 +0100 +++ new/rpmlint-tests-84.87+git20200724.ef05f7e/tests/permissions1.ref 2020-07-24 11:46:05.000000000 +0200 @@ -1,12 +1,14 @@ permissions1: W: non-position-independent-executable /bin/ls -permissions1: W: permissions-incorrect /bin/su has mode 0755 but should be 04755 -permissions1: W: permissions-incorrect-owner /bin/su belongs to root:bin but should be root:root +permissions1: W: non-position-independent-executable /usr/bin/su +permissions1: W: non-position-independent-executable /usr/bin/su +permissions1: W: permissions-incorrect /usr/bin/su has mode 0755 but should be 04755 +permissions1: W: permissions-incorrect-owner /usr/bin/su belongs to root:bin but should be root:root permissions1: W: permissions-missing-postin missing %set_permissions /bin/ls in %post -permissions1: W: permissions-missing-postin missing %set_permissions /bin/su in %post +permissions1: W: permissions-missing-postin missing %set_permissions /usr/bin/su in %post permissions1: W: permissions-missing-requires missing 'permissions' in PreReq permissions1: W: permissions-missing-verifyscript missing %verify_permissions -e /bin/ls -permissions1: W: permissions-missing-verifyscript missing %verify_permissions -e /bin/su +permissions1: W: permissions-missing-verifyscript missing %verify_permissions -e /usr/bin/su permissions1: E: permissions-file-setuid-bit (Badness: 10000) /bin/ls is packaged with setuid/setgid bits (04755) permissions1: E: permissions-unauthorized-file (Badness: 10000) /etc/permissions.d/test permissions1: E: permissions-unauthorized-file (Badness: 10000) /usr/share/permissions/permissions.d/test -1 packages and 0 specfiles checked; 3 errors, 8 warnings. +1 packages and 0 specfiles checked; 3 errors, 10 warnings. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-tests-84.87+git20200221.3ea152b/tests/permissions1.spec new/rpmlint-tests-84.87+git20200724.ef05f7e/tests/permissions1.spec --- old/rpmlint-tests-84.87+git20200221.3ea152b/tests/permissions1.spec 2020-02-21 11:06:10.000000000 +0100 +++ new/rpmlint-tests-84.87+git20200724.ef05f7e/tests/permissions1.spec 2020-07-24 11:46:05.000000000 +0200 @@ -23,13 +23,14 @@ install -d -m 755 %buildroot/etc/permissions.d install -d -m 755 %buildroot/usr/share/permissions/permissions.d install -d -m 755 %buildroot/bin +install -d -m 755 %buildroot/usr/bin echo "/bin/foo root:root 4755" > %buildroot/etc/permissions.d/test echo "/bin/foo root:root 4755" > %buildroot/usr/share/permissions/permissions.d/test echo "int main() {}" > xx.c gcc -fno-PIE -O2 xx.c -o %buildroot/bin/ls strip %buildroot/bin/ls -cp /bin/su %buildroot/bin +cp %buildroot/bin/ls %buildroot/usr/bin/su %clean rm -rf %buildroot @@ -39,7 +40,7 @@ %config /etc/permissions.d/test %attr(0644,root,root) /usr/share/permissions/permissions.d/test %attr(4755,root,root) /bin/ls -%attr(0755,root,bin) /bin/su +%attr(0755,root,bin) /usr/bin/su %changelog * Mon Apr 18 2011 [email protected] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-tests-84.87+git20200221.3ea152b/tests/permissions2.spec new/rpmlint-tests-84.87+git20200724.ef05f7e/tests/permissions2.spec --- old/rpmlint-tests-84.87+git20200221.3ea152b/tests/permissions2.spec 2020-02-21 11:06:10.000000000 +0100 +++ new/rpmlint-tests-84.87+git20200724.ef05f7e/tests/permissions2.spec 2020-07-24 11:46:05.000000000 +0200 @@ -21,35 +21,35 @@ %build %install -install -d -m 755 %buildroot/bin -cp /bin/su %buildroot/bin -cp /bin/su %buildroot/bin/foo -printf '\0' >> %buildroot/bin/foo -cp /bin/su %buildroot/bin/bar -printf '\0\0' >> %buildroot/bin/bar +install -d -m 755 %buildroot/usr/bin +cp /bin/su %buildroot/usr/bin +cp /bin/su %buildroot/usr/bin/foo +printf '\0' >> %buildroot/usr/bin/foo +cp /bin/su %buildroot/usr/bin/bar +printf '\0\0' >> %buildroot/usr/bin/bar # postfix and sendmail are allowed to install their own permissions file mkdir -p %buildroot/etc/permissions.d %buildroot/usr/share/permissions/permissions.d -echo "/bin/foo root:root 4755" > %buildroot/etc/permissions.d/postfix -echo "/bin/bar root:root 4755" > %buildroot/usr/share/permissions/permissions.d/sendmail +echo "/usr/bin/foo root:root 4755" > %buildroot/etc/permissions.d/postfix +echo "/usr/bin/bar root:root 4755" > %buildroot/usr/share/permissions/permissions.d/sendmail %clean rm -rf %buildroot %verifyscript -%verify_permissions -e /bin/su -%verify_permissions -e /bin/foo -%verify_permissions -e /bin/bar +%verify_permissions -e /usr/bin/su +%verify_permissions -e /usr/bin/foo +%verify_permissions -e /usr/bin/bar %post -%set_permissions /bin/su -%set_permissions /bin/foo -%set_permissions /bin/bar +%set_permissions /usr/bin/su +%set_permissions /usr/bin/foo +%set_permissions /usr/bin/bar %files %defattr(-,root,root) -%attr(4755,root,root) /bin/su -%attr(4755,root,root) /bin/foo -%attr(4755,root,root) /bin/bar +%attr(4755,root,root) /usr/bin/su +%attr(4755,root,root) /usr/bin/foo +%attr(4755,root,root) /usr/bin/bar %config /etc/permissions.d/postfix %attr(0600,root,root) /etc/permissions.d/postfix %attr(0600,root,root) /usr/share/permissions/permissions.d/sendmail
