Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rpmlint for openSUSE:Factory checked in at 2026-07-02 20:06:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rpmlint (Old) and /work/SRC/openSUSE:Factory/.rpmlint.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rpmlint" Thu Jul 2 20:06:37 2026 rev:542 rq:1362481 version:2.9.0+git20260629.bd947d09 Changes: -------- --- /work/SRC/openSUSE:Factory/rpmlint/rpmlint.changes 2026-06-17 16:18:00.026262920 +0200 +++ /work/SRC/openSUSE:Factory/.rpmlint.new.1982/rpmlint.changes 2026-07-02 20:07:39.209638146 +0200 @@ -1,0 +2,20 @@ +Mon Jun 29 14:54:17 UTC 2026 - Wolfgang Frisch <[email protected]> + +- Update to version 2.9.0+git20260629.bd947d09: + * sysctl-whitelist: adjusted 50-coredump.conf digest for systemd - temporary revert (bsc#1267504) + * dbus-services: add txnupd-maintenance-tools service (bsc#1268577) + * test: add test coverage for new varlink related whitelisting features + * configs/openSUSE: add Varlink whitelisting restriction + * FileDigestCheck: add SocketUnitDigester + * FileDigestCheck: move digester types into utility module + * FileDigestCheck: support 'ContentCheck' configuration setting + * dbus-services: added kdeplasma6-addons kameleon.qmk whitelisting (bsc#1267818) + +------------------------------------------------------------------- +Mon Jun 22 08:17:33 UTC 2026 - Wolfgang Frisch <[email protected]> + +- Update to version 2.9.0+git20260622.4dc32f49: + * sysctl-whitelist: adjusted 50-coredump.conf digest for systemd (bsc#1267504) + * configs/openSUSE/users-groups.toml: Replace group 'singularity' by 'apptainer' + +------------------------------------------------------------------- Old: ---- rpmlint-2.9.0+git20260528.2490edb3.tar.xz New: ---- rpmlint-2.9.0+git20260629.bd947d09.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rpmlint.spec ++++++ --- /var/tmp/diff_new_pack.4izltp/_old 2026-07-02 20:07:40.729690684 +0200 +++ /var/tmp/diff_new_pack.4izltp/_new 2026-07-02 20:07:40.733690822 +0200 @@ -23,7 +23,7 @@ %define name_suffix -%{flavor} %endif Name: rpmlint%{name_suffix} -Version: 2.9.0+git20260528.2490edb3 +Version: 2.9.0+git20260629.bd947d09 Release: 0 Summary: RPM file correctness checker License: GPL-2.0-or-later ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.4izltp/_old 2026-07-02 20:07:40.905696767 +0200 +++ /var/tmp/diff_new_pack.4izltp/_new 2026-07-02 20:07:40.917697182 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/rpm-software-management/rpmlint.git</param> - <param name="changesrevision">2490edb3f69bfb2c1df3829fe5f091651fde1f6f</param></service></servicedata> + <param name="changesrevision">bd947d09488873488dc1bd4174f143077694bf2d</param></service></servicedata> (No newline at EOF) ++++++ rpmlint-2.9.0+git20260528.2490edb3.tar.xz -> rpmlint-2.9.0+git20260629.bd947d09.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/dbus-services.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/dbus-services.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/dbus-services.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/dbus-services.toml 2026-06-29 16:53:33.000000000 +0200 @@ -1616,8 +1616,8 @@ [[FileDigestGroup]] package = "kdeplasma6-addons" -note = "A small helper that allows to change LED colors via /sys/class/led" -bug = "bsc#1226306" +note = "A small helper that allows to change LED colors" +bugs = [ "bsc#1226306", "bsc#1267818" ] type = "dbus" [[FileDigestGroup.digests]] path = "/usr/share/dbus-1/system.d/org.kde.kameleonhelper.conf" @@ -1627,6 +1627,14 @@ path = "/usr/share/dbus-1/system-services/org.kde.kameleonhelper.service" digester = "shell" hash = "85e17072d140148cf4cb4186389f5dd6a24c56fbae76c5392504b9062560e0f6" +[[FileDigestGroup.digests]] +path = "/usr/share/dbus-1/system-services/org.kde.kameleon.qmk.helper.service" +digester = "shell" +hash = "7d11312252c8435071f453ee84a479366869bfeba72adb712f1bade9107e60ac" +[[FileDigestGroup.digests]] +path = "/usr/share/dbus-1/system.d/org.kde.kameleon.qmk.helper.conf" +digester = "xml" +hash = "fac2d51f124edeb87fb097aaaf53618744810d5ddcfc0fbab0232d2c55b81b30" [[FileDigestGroup]] package = "supergfxctl" @@ -1813,3 +1821,13 @@ path = "/usr/share/dbus-1/system.d/com.presire.qsnapper.Operations.conf" digester = "xml" hash = "4ebd3fcf900e113e6d7a9a1e95ff7b32c4ab177723b8660030917313113a58ea" + +[[FileDigestGroup]] +package = "txnupd-maintenance-tools" +note = "forwards signals from transactional-update to a session D-Bus daemon" +bug = "bsc#1268577" +type = "dbus" +[[FileDigestGroup.digests]] +path = "/usr/share/dbus-1/system.d/org.opensuse.tukit.Updated.conf" +digester = "xml" +hash = "abf27e413feacafdb95e5b48ae2d75f501d506feb6a0a46a96fab338bde1fd13" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/opensuse.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/opensuse.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/opensuse.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/opensuse.toml 2026-06-29 16:53:33.000000000 +0200 @@ -310,6 +310,10 @@ "systemd-tmpfile-entry-unauthorized", "world-writable-mismatched-attrs", "world-writable-unauthorized-file", + "varlink-file-digest-mismatch", + "varlink-file-ghost", + "varlink-file-unauthorized", + "varlink-file-symlink", "zypperplugin-file-digest-mismatch", "zypperplugin-file-ghost", "zypperplugin-file-unauthorized" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/scoring.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/scoring.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/scoring.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/scoring.toml 2026-06-29 16:53:33.000000000 +0200 @@ -98,6 +98,10 @@ systemd-tmpfile-parse-error = 10 systemd-tmpfile-symlink = 10 missing-hash-section = 10000 +varlink-file-digest-mismatch = 10 +varlink-file-ghost = 10 +varlink-file-unauthorized = 10 +varlink-file-symlink = 10 zypperplugin-file-digest-mismatch = 10 zypperplugin-file-ghost = 10 zypperplugin-file-unauthorized = 10 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/security.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/security.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/security.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/security.toml 2026-06-29 16:53:33.000000000 +0200 @@ -18,6 +18,24 @@ "/etc/dbus-1/system.d/" ] +[FileDigestLocation.varlink] +# At the moment there don't exist any symlinked socket units in Factory so +# let's be restrictive here. +FollowSymlinks = false +# We are only looking for *.socket files in system service locations that +# contain a "FileDescriptorName=varlink" directive. The ContentCheck will take +# care of the latter limitation. +Locations = [ + "/etc/systemd/system/", + "/run/systemd/system/", + "/usr/lib/systemd/system/", + "/usr/local/lib/systemd/system/", +] +NamePatterns = [ + "*.socket", +] +ContentCheck = "VarlinkServiceCheck" + [FileDigestLocation.polkit] FollowSymlinks = false Locations = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/sysctl-whitelist.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/sysctl-whitelist.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/sysctl-whitelist.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/sysctl-whitelist.toml 2026-06-29 16:53:33.000000000 +0200 @@ -52,7 +52,7 @@ package = "systemd" type = "sysctl" note = "sets core pattern, core pipe limit and suid_dumpable" -bugs = ["bsc#1174722", "bsc#1226865", "bsc#1243959"] +bugs = ["bsc#1174722", "bsc#1226865", "bsc#1243959", "bsc#1267504"] [[FileDigestGroup.digests]] path = "/usr/lib/sysctl.d/50-coredump.conf" digester = "shell" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/users-groups.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/users-groups.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/users-groups.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/users-groups.toml 2026-06-29 16:53:33.000000000 +0200 @@ -2,6 +2,7 @@ 'aegis', 'alloy', 'antivir', + 'apptainer', 'arangodb', 'at', 'audio', @@ -180,7 +181,6 @@ 'shadow', 'shibd', 'signaling', - 'singularity', 'siproxd', '_sks', 'slurm', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/varlink-whitelist.toml new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/varlink-whitelist.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/configs/openSUSE/varlink-whitelist.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/rpmlint-2.9.0+git20260629.bd947d09/configs/openSUSE/varlink-whitelist.toml 2026-06-29 16:53:33.000000000 +0200 @@ -0,0 +1,9 @@ +[[FileDigestGroup]] +package = "rpmlint-integration-test" +type = "varlink" +note = "valid test whitelisting entry for the OBS integration test package" +bug = "bsc#1188704" +[[FileDigestGroup.digests]] +path = "/usr/lib/systemd/system/test.socket" +digester = "systemd-socket" +hash = "ff7a1f7581f81790cc063071026c4deaf4eafa6b0658217250db8e420c9064bc" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/rpmlint/checks/FileDigestCheck.py new/rpmlint-2.9.0+git20260629.bd947d09/rpmlint/checks/FileDigestCheck.py --- old/rpmlint-2.9.0+git20260528.2490edb3/rpmlint/checks/FileDigestCheck.py 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/rpmlint/checks/FileDigestCheck.py 2026-06-29 16:53:33.000000000 +0200 @@ -2,124 +2,22 @@ from fnmatch import fnmatch import hashlib from pathlib import Path -import re import stat -import xml.etree.ElementTree as ET from rpmlint.checks.AbstractCheck import AbstractCheck +import rpmlint.filedigestcheck as check_utils DEFAULT_DIGEST_ALG = 'sha256' - -# We support different "filters" for calculating file digests for whitelisting -# purposes. The reasoning behind this is that we want to detect *functional* -# changes to certain files but we don't want to be bothered by any other -# changes like changes in comments, copyright headers, whitespace and so on. -# -# To avoid such bogus changes showing up in the checker we can filter the -# target files depending on their file type. Currently we have two common -# special cases: -# -# - shell-like files (configuration files, shell scripts, Python -# scripts, Perl scripts etc) that can contain empty lines or comments -# introduced by '#'. -# - XML files for things like D-Bus configuration files or polkit policies. -# Here also XML style comments and whitespace can occur. -# -# The filter classes help in calculating a file digest for a filtered version -# of the target file that is normalized and therefore stable against the -# outlined kinds of changes. Since this makes getting the right whitelisting -# digests hard, a small companion tool in tools/get_whitelisting_digest.py -# exists. - - -class DefaultDigester: - """This class performs the default digest calculation of arbitrary file - contents as is.""" - - def __init__(self, path, halg): - self.path = path - self.halg = halg - - def parse_content(self): - # simply generate chunks on binary level - with open(self.path, 'rb') as fd: - while True: - chunk = fd.read(4096) - if not chunk: - break - yield chunk - - def get_digest(self): - """This returns the hash digest of the *un*filtered input file.""" - hasher = hashlib.new(self.halg) - - for chunk in self.parse_content(): - hasher.update(chunk) - - return hasher.hexdigest() - - -class ShellDigester(DefaultDigester): - """This class performs digest calculation of shell style configuration - files or scripts. Certain aspects like comments and whitespace will be - filtered out of the digest calculation.""" - - def parse_content(self): - # generate filtered lines - with open(self.path) as fd: - for line_nr, line in enumerate(fd): - stripped = line.strip() - if not stripped: - # skip empty lines - continue - elif line_nr == 0 and stripped.startswith('#!'): - # keep shebang lines mostly intact, - # but ignore minor interpreter versions - line = re.sub(r'\bpython3\.\d+\b', 'python3', line) - elif stripped.startswith('#'): - # skip comments - # NOTE: we don't strip trailing comments like in - # 'if [ 5 -eq $NUM ]; then # compare to 5' - # because the danger would be too high to remove actual - # content instead of just comments - continue - - # don't use the completely stripped version, in Python for - # example the indentation is part of the syntax and changes - # here could change the meaning - yield (line.rstrip() + '\n').encode() - - -class XmlDigester(DefaultDigester): - """This class performs digest calculation of XML configuration files. - Certain aspects like comments and whitespace will be filtered out of the - digest calculation.""" - - def parse_content(self): - # NOTE: the ElementTree is not robust against malicious input. Rpmlint - # processes a lot of untrusted input. In the OBS context this only - # happens within the virtual machines where packagers can manipulate a - # lot of things anyway so it should be okay. - - # this returns a canonicalized form of the XML without comments or - # anything. It is missing the XML preamble and DOCTYPE declaration, - # though. It's as good as I could get it to be without parsing XML on - # foot or relying on third party XML modules. - - # chunked / line wise processing is likely impossible for XML so - # return the whole bunch - yield ET.canonicalize(from_file=self.path, strip_text=True).encode() - - # These values can be used in the individual "digests" entries of a # whitelisting entry for a given digest group. For example: # { path = "/some/path.xml", algorithm = "sha256", digester = "xml", hash = "..." } DIGESTERS = { - 'default': DefaultDigester, - 'shell': ShellDigester, - 'xml': XmlDigester, + 'default': check_utils.DefaultDigester, + 'shell': check_utils.ShellDigester, + 'xml': check_utils.XmlDigester, + 'systemd-socket': check_utils.SocketUnitDigester } @@ -147,6 +45,7 @@ # make sure these keys always exists config.setdefault('NamePatterns', []) config.setdefault('FollowSymlinks', False) + config.setdefault('ContentCheck', None) config['type'] = check_type self.checks.append(config) self.known_check_types[check_type] = config @@ -406,6 +305,12 @@ digest_hint += f' of resolved path {pkgfile.name}' for dtype, digester in DIGESTERS.items(): + # lex systemd-socket: avoid spamming the output (and breaking + # expected test case output) with systemd-socket digests we don't + # need. + if dtype == 'systemd-socket' and not path.endswith('.socket'): + continue + try: digest = self._calc_digest(digester, pkgfile, DEFAULT_DIGEST_ALG) except Exception: @@ -657,6 +562,30 @@ return True return False + def has_restricted_content(self, check, pkg, path): + """Check whether the given path, which is located in a restricted + location, contains data which makes it subject to our whitelisting + restriction.""" + content_check = check['ContentCheck'] + if not content_check: + # there is no content check type declared in the configuration, + # this means all files present in the restricted location are + # covered by the check. + return True + + pkgfile = self._resolve_links(pkg, path) + if pkgfile is None: + # some error in link resolution, later checks will complain more + # explicitly about this. + return True + + try: + ContentChecker = getattr(check_utils, content_check) + except AttributeError: + raise Exception(f'No matching type for ContentCheck={content_check} found in rpmlint.filedigestcheck') + checker = ContentChecker() + return checker.is_restricted(pkgfile.path) + def check_binary(self, pkg): """Entry point for digest checks. Check that all files in restricted locations are covered by a file digest group in which all files have @@ -681,6 +610,10 @@ elif stat.S_ISLNK(pkgfile.mode) and not check['FollowSymlinks']: if not self._is_symlink_allowed(pkg, path): self.output.add_info('E', pkg, f'{check_type}-file-symlink', path) + elif not self.has_restricted_content(check, pkg, path): + # the path is in a restricted location but the content is not + # relevant to us. + continue else: file_list = restricted_paths.setdefault(check_type, []) file_list.append(path) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/rpmlint/descriptions/FileDigestCheck.toml new/rpmlint-2.9.0+git20260629.bd947d09/rpmlint/descriptions/FileDigestCheck.toml --- old/rpmlint-2.9.0+git20260528.2490edb3/rpmlint/descriptions/FileDigestCheck.toml 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/rpmlint/descriptions/FileDigestCheck.toml 2026-06-29 16:53:33.000000000 +0200 @@ -38,6 +38,11 @@ sysctl-file-ghost="This package installs a sysctl.d drop-in configuration file as a %ghost file. #GHOST_ENCOUNTERED_TEXT#" sysctl-file-symlink="Symlinks are not allowed for sysctl.d drop-in configuration files" +varlink-file-digest-mismatch="A systemd socket unit declaring a Varlink service changed in content. Packaging varlink services #SUFFIX#. #REVIEW_NEEDED_TEXT#" +varlink-file-ghost="This package installs a systemd socket unit declaring a Varlink service as a %ghost file. #GHOST_ENCOUNTERED_TEXT#" +varlink-file-symlink="Symlinks are not allowed for systemd socket units declaring Varlink services" +varlink-file-unauthorized="Packaging new Varlink services #SUFFIX#. #REVIEW_NEEDED_TEXT#" + zypperplugin-file-digest-mismatch="A whitelisting related zypper plugin file changed in content. Packaging zypper plugins #SUFFIX#. #REVIEW_NEEDED_TEXT#" zypperplugin-file-unauthorized="Packaging new zypper plugins #SUFFIX#. #REVIEW_NEEDED_TEXT#" zypperplugin-file-ghost="This package installs a zypper plugin as a %ghost file. #GHOST_ENCOUNTERED_TEXT#" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/rpmlint/filedigestcheck.py new/rpmlint-2.9.0+git20260629.bd947d09/rpmlint/filedigestcheck.py --- old/rpmlint-2.9.0+git20260528.2490edb3/rpmlint/filedigestcheck.py 1970-01-01 01:00:00.000000000 +0100 +++ new/rpmlint-2.9.0+git20260629.bd947d09/rpmlint/filedigestcheck.py 2026-06-29 16:53:33.000000000 +0200 @@ -0,0 +1,223 @@ +import configparser + +# This module contains helper types used by the FileDigestCheck to implement +# configuration-specific logic. + +# ContentCheck types +# +# The following types relate to the "ContentCheck" configuration setting, +# which allows to specify a class name in this module which will be invoked to +# determine whether the whitelisting restriction should be applied to a +# specific file. A type needs to provide a is_restricted(self, path: str) +# member function to be usable as "ContentCheck". + + +def _open_socket_config(path): + """Opens a systemd .socket unit in `path` as an INI configuration file and + returns the ConfigParser instance resulting from it. On error None is + returned.""" + # systemd services files are not 100 % compatible with regular INI + # files, thus we make python's configparser a bit more relaxed. + config = configparser.ConfigParser( + interpolation=None, + strict=False + ) + + # by default option keys are converted to lower-case, we don't want that + config.optionxform = lambda opt: opt + + read = config.read(path) + if len(read) == 1: + return config + + return None + + +class VarlinkServiceCheck: + """Checks a systemd .socket unit file whether it refers to a Varlink + service.""" + + def is_restricted(self, path: str): + + config = _open_socket_config(path) + + if not config: + # failed to parse the file, assume it is restricted + return True + + try: + socket_section = config['Socket'] + except KeyError: + # no socket section in a socket unit? not Varlink anyway. + return False + + try: + file_descriptor_name = socket_section['FileDescriptorName'] + except KeyError: + # no varlink service + return False + + # this is more of a convention, not a hard requirement, but so far all + # Varlink services we have use this scheme. + return file_descriptor_name == 'varlink' + +# Digester types + +# We support different "filters" for calculating file digests for whitelisting +# purposes. The reasoning behind this is that we want to detect *functional* +# changes to certain files but we don't want to be bothered by any other +# changes like changes in comments, copyright headers, whitespace and so on. +# +# To avoid such bogus changes showing up in the checker we can filter the +# target files depending on their file type. Currently we have two common +# special cases: +# +# - shell-like files (configuration files, shell scripts, Python +# scripts, Perl scripts etc) that can contain empty lines or comments +# introduced by '#'. +# - XML files for things like D-Bus configuration files or polkit policies. +# Here also XML style comments and whitespace can occur. +# +# The filter classes help in calculating a file digest for a filtered version +# of the target file that is normalized and therefore stable against the +# outlined kinds of changes. Since this makes getting the right whitelisting +# digests hard, a small companion tool in tools/get_whitelisting_digest.py +# exists. + + +class DefaultDigester: + """This class performs the default digest calculation of arbitrary file + contents as is.""" + + def __init__(self, path, halg): + self.path = path + self.halg = halg + + def parse_content(self): + # simply generate chunks on binary level + with open(self.path, 'rb') as fd: + while True: + chunk = fd.read(4096) + if not chunk: + break + yield chunk + + def get_digest(self): + """This returns the hash digest of the *un*filtered input file.""" + import hashlib + hasher = hashlib.new(self.halg) + + for chunk in self.parse_content(): + hasher.update(chunk) + + return hasher.hexdigest() + + +class ShellDigester(DefaultDigester): + """This class performs digest calculation of shell style configuration + files or scripts. Certain aspects like comments and whitespace will be + filtered out of the digest calculation.""" + + def parse_content(self): + import re + + # generate filtered lines + with open(self.path) as fd: + for line_nr, line in enumerate(fd): + stripped = line.strip() + if not stripped: + # skip empty lines + continue + elif line_nr == 0 and stripped.startswith('#!'): + # keep shebang lines mostly intact, + # but ignore minor interpreter versions + line = re.sub(r'\bpython3\.\d+\b', 'python3', line) + elif stripped.startswith('#'): + # skip comments + # NOTE: we don't strip trailing comments like in + # 'if [ 5 -eq $NUM ]; then # compare to 5' + # because the danger would be too high to remove actual + # content instead of just comments + continue + + # don't use the completely stripped version, in Python for + # example the indentation is part of the syntax and changes + # here could change the meaning + yield (line.rstrip() + '\n').encode() + + +class XmlDigester(DefaultDigester): + """This class performs digest calculation of XML configuration files. + Certain aspects like comments and whitespace will be filtered out of the + digest calculation.""" + + def parse_content(self): + import xml.etree.ElementTree as ET + # NOTE: the ElementTree is not robust against malicious input. Rpmlint + # processes a lot of untrusted input. In the OBS context this only + # happens within the virtual machines where packagers can manipulate a + # lot of things anyway so it should be okay. + + # this returns a canonicalized form of the XML without comments or + # anything. It is missing the XML preamble and DOCTYPE declaration, + # though. It's as good as I could get it to be without parsing XML on + # foot or relying on third party XML modules. + + # chunked / line wise processing is likely impossible for XML so + # return the whole bunch + yield ET.canonicalize(from_file=self.path, strip_text=True).encode() + + +class SocketUnitDigester(DefaultDigester): + + # [Socket] section configuration keys to include in the digest + # calculation. + # + # see systemd.socket(5) man page for reference. + # + # we're only interested in settings related to API / permissions / network + # entry points. + KEYS_TO_HASH = { + 'ListenStream', + 'ListenDatagram', + 'ListenSequentialPacket', + 'ListenFIFO', + 'ListenSpecial', + 'ListenNetlink', + 'ListenMessageQueue', + 'SocketProtocol', + 'BindToDevice', + 'SocketUser', + 'SocketGroup', + 'SocketMode', + 'DirectoryMode', + 'PassSecurity', + 'AcceptFileDescriptors', + 'ExecStartPre', + 'ExecStartPost', + 'ExecStopPre', + 'ExecStopPost', + 'FileDescriptorName', + 'PassFileDescriptorsToExec' + } + + def parse_content(self): + config = _open_socket_config(self.path) + + if not config: + # shouldn't happen since VarlinkServiceCheck above should have + # already read the file successfully before. + raise Exception(f'failed to parse {self.path}') + + try: + socket_section = config['Socket'] + except KeyError: + # VarlinkServiceCheck must have seen a [Socket] section so this + # shouldn't happen. + raise Exception(f'[Socket] section in {self.path} disappeared?') + + # yield all key/value pairs we're interested in for hashing + for key in socket_section: + if key in self.KEYS_TO_HASH: + value = socket_section[key] + yield f'{key}={value}\n'.encode() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/test/configs/digests_varlink.config new/rpmlint-2.9.0+git20260629.bd947d09/test/configs/digests_varlink.config --- old/rpmlint-2.9.0+git20260528.2490edb3/test/configs/digests_varlink.config 1970-01-01 01:00:00.000000000 +0100 +++ new/rpmlint-2.9.0+git20260629.bd947d09/test/configs/digests_varlink.config 2026-06-29 16:53:33.000000000 +0200 @@ -0,0 +1,20 @@ +[FileDigestLocation.varlinktest] +FollowSymlinks = false +Locations = [ + "/sockets/", +] +NamePatterns = [ + "*.socket", +] +ContentCheck = "VarlinkServiceCheck" + +[[FileDigestGroup]] +package = "socketpkg" +type = "varlinktest" +note = "tests a package which as a systemd.socket unit" +bug = "bsc#4321" +[[FileDigestGroup.digests]] +path = "/sockets/the.socket" +algorithm = "sha256" +digester = "systemd-socket" +hash = "7978041027394b996610b0de563a83fea8ad425c1f6837e57c1b65863dac9933" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/test/test_file_digest.py new/rpmlint-2.9.0+git20260629.bd947d09/test/test_file_digest.py --- old/rpmlint-2.9.0+git20260528.2490edb3/test/test_file_digest.py 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/test/test_file_digest.py 2026-06-29 16:53:33.000000000 +0200 @@ -406,6 +406,60 @@ assert len(output.results) == 1 +def test_varlink_content_check(): + # here we want to test whether the VarlinkServiceCheck ContentCheck + # correctly identifies socket units declaring Varlink sockets. + UNAFFECTED_SOCKET_DATA = '[Socket]\nThis=stuff\n' + + output, test = get_digestcheck('digests_varlink.config') + with FakePkg('defaultpkg') as pkg: + pkg.add_file_with_content('/sockets/harmless.socket', UNAFFECTED_SOCKET_DATA) + test.check(pkg) + assert len(output.results) == 0 + + # only this config should be complained about since if contains a varlink + # FileDescriptorName + RESTRICTED_SOCKET_DATA = '[Socket]\nFileDescriptorName=varlink\n' + + output, test = get_digestcheck('digests_varlink.config') + with FakePkg('defaultpkg') as pkg: + pkg.add_file_with_content('/sockets/evil.socket', RESTRICTED_SOCKET_DATA) + test.check(pkg) + assert len(output.results) == 1 + + +def test_varlink_digester(): + # here we want to check the SocketUnitDigester: + # - reject a mismatching digest + # - accept a matching digest + # - accept a file which has only changes in uninteresting fields + + MISMATCHING_SOCKET_DATA = '[Socket]\nFileDescriptorName=varlink\nSocketMode=0606\n' + + output, test = get_digestcheck('digests_varlink.config') + with FakePkg('socketpkg') as pkg: + pkg.add_file_with_content('/sockets/the.socket', MISMATCHING_SOCKET_DATA) + test.check(pkg) + assert len(output.results) == 1 + + MATCHING_SOCKET_DATA = '[Socket]\nFileDescriptorName=varlink\nSocketMode=0777\n' + + output, test = get_digestcheck('digests_varlink.config') + with FakePkg('socketpkg') as pkg: + pkg.add_file_with_content('/sockets/the.socket', MATCHING_SOCKET_DATA) + test.check(pkg) + assert len(output.results) == 0 + + # this adds Backlog=100, an uninteresting key, should yield the same digest + SIMILAR_SOCKET_DATA = '[Socket]\nFileDescriptorName=varlink\nSocketMode=0777\nBacklog=100\n' + + output, test = get_digestcheck('digests_varlink.config') + with FakePkg('socketpkg') as pkg: + pkg.add_file_with_content('/sockets/the.socket', SIMILAR_SOCKET_DATA) + test.check(pkg) + assert len(output.results) == 0 + + @pytest.mark.parametrize('package', ['binary/pam-module']) def test_pam_modules(tmp_path, package, digestcheck): output, test = get_digestcheck('digests_pam.config') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rpmlint-2.9.0+git20260528.2490edb3/tools/get_whitelisting_digest.py new/rpmlint-2.9.0+git20260629.bd947d09/tools/get_whitelisting_digest.py --- old/rpmlint-2.9.0+git20260528.2490edb3/tools/get_whitelisting_digest.py 2026-05-28 14:31:50.000000000 +0200 +++ new/rpmlint-2.9.0+git20260629.bd947d09/tools/get_whitelisting_digest.py 2026-06-29 16:53:33.000000000 +0200 @@ -7,6 +7,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import rpmlint.checks.FileDigestCheck as fdc # noqa: E402 +import rpmlint.filedigestcheck as fdc_utils # noqa: E402 parser = argparse.ArgumentParser( description='This tool helps calculating whitelisting digests for the FileDigestCheck' @@ -23,7 +24,7 @@ print('Cannot output result data for default digester (which is binary)', file=sys.stderr) sys.exit(1) -Digester = fdc.DIGESTERS.get(args.filter, fdc.DefaultDigester) +Digester = fdc.DIGESTERS.get(args.filter, fdc_utils.DefaultDigester) digester = Digester(args.FILE, fdc.DEFAULT_DIGEST_ALG)
