SUSE Security Team Spotlight Summer 2025
#spotlightTable of Contents
- 1) Introduction
- 2) systemd v258: Local Root Exploit in new systemd-machined API found in Release Candidates
- 3) logrotate: Issues in drop-in Configuration Files
- 4) GNOME 49: D-Bus and Polkit Changes in new Major Version Release
- 5) Kea DHCP: Follow-Up Review of Network Attack Surface
- 6) chrony: Issues in chronyc Socket Creation
- 7) pwaccessd: New Varlink Service for Reading User Account Information
- 8) sysextmgr: New Varlink Service for Managing systemd-sysext Images
- 9) bash-git-prompt: Predictable Temporary File Name Offers Local Attack Surface (CVE-2025-61659)
- 10) steam-powerbuttond: Insecure Operation in Home Directories
- 11) Conclusion
1) Introduction
Autumn is already palpable for many of us these days and this means it is time to take a look back at what happened in our team during the summer months. We have not published any dedicated security reports during that time; instead we have all the more to cover in this edition of the spotlight series which discusses code review efforts that did not lead to major findings or otherwise did not qualify for a dedicated report.
This is also the first anniversary of the spotlight series, which we started in August 2024 with the first summer spotlight edition. We are happy to provide our readers with interesting content about the daily work in our team and are looking forward to more anniversaries to come.
In this issue we will cover a local root exploit we discovered in systemd
v258-rc4 before it became part of a stable release, problems
found in logrotate drop-in configuration files, changes
in D-Bus configuration files related to the GNOME version 49
release, and a follow-up code review of the Kea DHCP server
suite. Furthermore we found a symlink attack issue in
chronyc
, proactively reviewed new Varlink
services developed by fellow SUSE engineers and discovered a
local privilege escalation issue in bash-git-prompt. Finally we
will talk about a problematic script used on Steam Deck
devices.
2) systemd v258: Local Root Exploit in new systemd-machined API found in Release Candidates
At the beginning of August one of our systemd maintainers asked us to review
D-Bus and Polkit API changes in a release candidate of
systemd 258. This major version update of systemd contains many API additions
e.g. in systemd-resolved
, systemd-homed
, systemd-machined
and
systemd-nsresourced
.
While looking into these changes we found an issue in systemd-machined
. This
daemon can be used to manage virtual machines and containers alike.
In upstream commit adaff8eb35d a new Polkit
action “org.freedesktop.machine1.register-machine” has been added, which was
accessible to locally logged in users without authentication (Polkit yes
setting). The purpose of this new API is to allow users to register existing
containers with systemd-machined
, that have been created by other means.
There exist two D-Bus methods which employ this Polkit action: “RegisterMachine” and “RegisterMachineWithNetwork”. Both accept a rather long list of parameters to describe the container which is supposed to be registered with the daemon. The following command line performs an example registration of a fake container:
$ gdbus call -y -d org.freedesktop.machine1 -o /org/freedesktop/machine1 \
-m org.freedesktop.machine1.Manager.RegisterMachineWithNetwork \
mymachine '[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]' myservice container \
$$ $PWD '[1, 2, 3]'
Among these parameters is the process ID (PID) of the leader process of the
container. In this example $$
, i.e. the shell’s own PID, is passed as leader
PID. The release candidate implementation of systemd-machined
failed to
verify whether this process is owned by the caller and an actual member of an
unprivileged user namespace belonging to a container.
The first problematic aspect we noticed about this was that systemd-machined
can send SIGTERM
to the process group the given leader PID belongs to (e.g.
when registering a new container using the same name), allowing a trivial
local Denial-of-Service against arbitrary other processes. Far more
problematic was something else that we noticed: the unprivileged user was able
to enter a shell in such a crafted container, like this:
user$ machinectl shell mymachine
# full root privileges, this happens in the actual host's file system
container-root# touch /evil
Since the leader PID in this case is a process belonging to the host’s initial namespaces, the root shell for the “container” is actually a root shell in the host itself, giving full root privileges over the system.
This problem is found in all release candidates of systemd v258. We reported the problem privately to systemd security, and upstream developed bugfixes right away while still in the RC phase. The local root exploit was never present in any stable release version and thus end users are not affected by the problem, which is also why no CVE was assigned.
The Bugfix
To address the issue, systemd-machined
now verifies that the leader PID
specified by the client is actually owned by the caller. Furthermore the
authentication requirements for Polkit action “register-machine” have been
raised to auth_admin_keep
even for local users.
While writing this very summary we noticed that one aspect of the issue had
been overlooked and was not fixed for the stable release: the verification of
the user namespace membership of the target process. Thus it is still possible
to gain a root shell this way, but only after authenticating as admin, which
means the caller already needs admin privileges to trigger the exploit. This
aspect has now been addressed for future releases by
upstream, which is important, because upstream intends to relax the
authentication requirements for this action to yes
again at a later time.
Increase in Complexity in systemd
With this version of systemd we are seeing a noticeable increase in complexity
in the implementation of a number of systemd components. In the area of
container management the complexity is pretty much by design, given the
intricacy of the different namespace mechanisms playing together, partly
under the control of unprivileged users. There is also the addition of
Varlink for Inter-Process-Communication, however, which
means that two different interfaces for D-Bus and Varlink now exist in parallel
for some services. This is also the case for systemd-machined
.
While the D-Bus and Varlink interfaces usually call into shared functions for most of the business logic and share the same Polkit actions, there is necessarily a certain amount of redundancy in parsing and evaluation of input parameters. As a result this also increases the burden on code reviewers which now need to keep track of two different entry paths to the same logic.
We are not yet completely finished with reviewing all notable changes in systemd v258 but intend to complete the effort within the next couple of weeks. We are happy that our review efforts already prevented a local root exploit in software as widespread as systemd from ending up in production environments.
3) logrotate: Issues in drop-in Configuration Files
Missing su <user> <group>
Directives
Recently we noticed that there exist a number of packages in openSUSE
Tumbleweed which trigger a
“logrotate-user-writable-log-dir” rpmlint
diagnostic. This diagnostic is emitted when a
package contains a logrotate drop-in configuration file (e.g. in
/etc/logrotate.d
) which points the logrotate daemon to a log directory which
is controlled by non-root accounts, where it will operate with full root
privileges.
Operating as root
in locations controlled by other users is generally very
difficult to get right and can easily lead to privilege escalation from a
service user to root
e.g. via symlink attacks. logrotate
offers a su <user> <group>
syntax to
instruct the daemon to perform a privilege drop to the user owning the
directory to avoid any security implications.
To start with, we had a look at the implementation of the logrotate daemon, to judge what the impact would be, when a rogue service user account tries to perform an attack against logrotate when it starts rotating logs in a directory controlled by the compromised user. The results are as follows:
- the daemon performs a sanity check on the directory to operate on and rejects any log directories which are writable by world or a non-root group. This does not include the case where the log directory is owned by a non-root user, however.
- the system calls used by logrotate always include safe flags for opening log files which will prevent trivial symlink attacks by service users from succeeding. There could still be more intricate attacks when a parent directory of the log directory is also owned by a non-root user account. This is not a common setup, however, and we could not find any package where this is the case.
In summary we believe that there are no overly dangerous situations that can
result from a missing su <user> <group>
directive in affected logrotate
configuration files. Still we decided that it will be better to fix existing
packages and enforce that packages emitting this rpmlint diagnostic are not
allowed into openSUSE in the future. To this end we fixed a couple of
openSUSE-specific logrotate drop-in configuration files as well as an upstream
configuration file in Munge.
Problems with Scripts Embedded in Configuration
While looking into the credentials mismatch issue we noticed that logrotate
can end up in even more complex usage scenarios. The configuration file format
allows shell scripts to be embedded that will be executed after rotating
logfiles, for example. These scripts always run with full root
privileges,
independently of an existing su <user> <group>
directive. The likeliness of
security issues is higher in this case and issues are harder to detect, since
this is package-specific code possibly running as root
in untrusted
directories.
While exploring all embedded scripts found in logrotate drop-in configuration files in openSUSE Tumbleweed we found out that in most cases such scripts are only used to restart a systemd service or to send a signal to a daemon running in the background. In a few cases the scripts have been problematic, as is described in the following sub-sections.
python-mailman (CVE-2025-53882)
In the python-mailman package we found two problems in the embedded shell script, which consisted of these two lines:
/bin/kill -HUP $(</run/mailman/master.pid) 2>/dev/null || true
@BINDIR@/mailman reopen >/dev/null 2>&1 || true
For one, SIGHUP
was sent to a PID obtained from /run/mailman/master.pid
,
which is under the control of the mailman
service user. This would allow a
compromised mailman
user to direct SIGHUP
to arbitrary processes in the
system.
Furthermore the command line /usr/bin/mailman reopen
was executed with full
root privileges, which results in output like this:
Usage: mailman [OPTIONS] COMMAND [ARGS]...
Try 'mailman -h' for help.
Error: If you are sure you want to run as root, specify --run-as-root.
This shows that the intended reopen of logfiles doesn’t work as expected.
Otherwise one might think that nothing harmful happens. This is not true,
however. This invocation of mailman
still leads to the full initialization
of the logging system and all the logfiles in /var/log/mailman
are
created, if not already existing, with full root privileges. Symbolic links
are followed, if necessary.
This means a compromised mailman
user can e.g. create a symlink
/var/log/mailman/bounce.log
→ /etc/evil-file
. After the logrotate script
runs /etc/evil-file
will be created. The files will be created with
root-ownership, so the only impact of this should be the creation of new empty
files owned by root
in the system. This can still have security impact when
such empty state files control sensitive settings of other programs in the
system.
To fix this issue the sending of SIGHUP
was completely dropped and the
reopen command is invoked via sudo
as the dedicated mailman
service user
and group. The logrotate drop-in configuration file containing the
problematic script is specific to openSUSE, thus we assigned a CVE for this
issue to make our users aware.
sssd
The sssd package has a very similar issue in its
example logrotate configuration, where a SIGHUP
signal is sent to a PID controlled by the sssd
service user:
/bin/kill -HUP `cat @pidpath@/sssd.pid 2>/dev/null` 2> /dev/null || true
We created a public upstream GitHub issue to make the developers aware of the problem. There is no fix available yet for the issue.
Icinga2
In our icinga2 package there is yet another instance of sending a signal
(SIGUSR1
) to a PID controlled by the unprivileged icinga
service user:
/bin/kill -USR1 $(cat /run/icinga2/icinga2.pid 2> /dev/null) 2>/dev/null || true
We wanted to change that into a systemctl reload icinga2.service
instead,
only to find out that upstream’s reload script is affected by the same
issue. There is no fix available for this issue yet.
exim (CVE-2025-53881)
Our exim package contained a problematic prerotate
shell script in its
logrotate configuration which allows escalation from
the mail
user/group to root
, when it runs. The
shell script is rather complex and tries to generate a statistics report
creating temporary files as root
in the log directory owned by the
unprivileged mail
user.
To fix this, the script has been adjusted to use a private temporary directory for the report, instead. An update containing the fix will soon be available for openSUSE Tumbleweed.
This again is an openSUSE specific logrotate configuration file, thus we assigned a CVE to mark the problem.
Possible Improvements in logrotate
The issues we uncovered show also room for improvement in logrotate itself to
prevent such situations in the first place. For one, the daemon could refuse
to work on directories owned by non-root users, like it does for
world-writable directories. Furthermore scripts could be executed using the
same su <user> <group>
credentials that are used for rotating the logs.
We did not reach out to upstream about these suggestions yet, but will keep you informed about any developments in this area.
4) GNOME 49: D-Bus and Polkit Changes in new Major Version Release
GNOME 49 was recently released and our GNOME maintainers asked us to look into a number of D-Bus and Polkit changes that appeared in related packages. We encountered nothing too exciting this time:
- GDM: Two changes appeared in GNOME’s display manager:
- Some polkit actions are now tied to the
gdm
group instead of to thegdm
user. This is related to the display manager now using dynamic user accounts. - The
gdm
group is now allowed to access smart cards managed bypcscd
. This is supposed to fix a bug report where smart cards could not be accessed by GDM. Why this bug never occurred before is not completely clear, the Polkit settings are acceptable in any case.
- Some polkit actions are now tied to the
- gnome-initial-setup: This package received the same
change as GDM, Polkit actions are now tied to the
gdm
group, not the user. - gnome-remote-desktop: This is the same as in
gnome-initial-setup, Polkit actions are now tied to the
gdm
group instead of the user. - mutter: This part of GNOME (a Wayland compositor
and X11 window manager) now contains a
backlight-helper
. Locally logged in regular users are allowed to execute this program withroot
privileges to control the backlight of mobile devices. We have seen this helper program before in thegnome-settings-daemon
package. It is a minimal C program consisting of 200 lines of code and we could not find any issues in it.
5) Kea DHCP: Follow-Up Review of Network Attack Surface
Earlier this year we reported a number of local security issues pertaining to the REST API in Kea DHCP. In a follow-up review we focused on the network attack surface, which usually is the more interesting part when dealing with a DHCP server suite. Alas, while looking at the network logic we stumbled over another minor local security issue regarding a temporary change to the process’s umask. Upstream addressed the problem in the meantime.
Following the actual network processing logic in Kea’s code base is no easy task. The C++ coding style uses a high level of abstraction which leads to many indirections. Untrusted data received from network peers travels far in the code without clear logical boundaries where data is verified before further processing takes place. The code base contains a lot of comments, which usually is a good thing, but in this instance it felt nearly too verbose to us, making it hard to find the relevant bits.
On the positive side of things Kea is already a matured project and there were no easy pickings to be found. Upstream also integrated AFL fuzzing into their testing infrastructure, which should allow them to find network security issues proactively. Consequently we have been unable to find any security issues in the network processing in Kea.
Kea offers advanced features like configuring custom behaviour depending on specific DHCP header fields. This naturally comes with quite some additional complexity. In this light we believe Kea is well suited for large organizations, but we would recommend a simpler DHCP server implementation for small environments where such features are not needed, to reduce attack surface.
6) chrony: Issues in chronyc Socket Creation
This finding resulted from our logrotate configuration
file investigation discussed above. chrony is the
default NTP time synchronization program used in openSUSE and a number of
other Linux distributions. It ships a logrotate drop-in configuration
file that contains this postrotate
shell code:
postrotate
/usr/bin/chronyc cyclelogs > /dev/null 2>&1 || true
endscript
chronyc
is the client utility used to talk to the chronyd
daemon
component. The communication mechanism used for this is a UNIX domain socket
placed in /run/chrony/chronyd.sock
. chronyc
is invoked as root
in the
logrotate context above. At first we believed this should not be a problem,
since any privileged process should be allowed to talk to chronyd
. While
looking at the strace
output of the command line above the following system
call caught our attention, however:
chmod("/run/chrony/chronyc.6588.sock", 0666) = 0
The /run/chrony
directory is owned by the chrony
service user:
drwxr-x--- 3 chrony chrony 100 Sep 25 09:45 .
These are the same credentials used by the chronyd
daemon. When root
performs the chmod
call above, then a compromised chrony
service user has
an opportunity to perform a symlink attack, directing the chmod()
operation
to arbitrary files on the system, making them world-writable, thus making
possible a chrony
to root
privilege escalation. A couple of years ago we
found a somewhat similar symlink attack in the area
of the pidfile creation performed by the daemon.
We approached upstream about the issue on July 15 by creating a private
issue in their GitLab project. The bugfix
turned out rather complex. The problem here is that the UNIX domain socket
used by chrony is datagram-oriented (SOCK_DGRAM
). This means there is no
connection established between client and server. For the server being able to
send back data to the client, the client needs to bind its socket into the
file system as well and grant the server-side access to it. On Linux an
autobind feature exists for Unix domain sockets, which will automatically
assign an abstract address to the client socket, which is not visible in the
file system. This feature is not available on other UNIX systems, however,
that chrony also intends to support.
For these reasons the upstream approach to fix this involves the creation of
an unpredictably named sub-directory in /run/chrony
to place the client-end
socket into. The directory is only writable for the client and the
unpredictable directory name is not known in advance, thus no symlinks can be
placed into the path anymore.
7) pwaccessd: New Varlink Service for Reading User Account Information
A fellow SUSE engineer recently finished development on
pwaccessd
, a daemon providing user account information
via Varlink. This novel approach to providing account information allows, for
example, to grant regular users access to their own shadow entry, which would
otherwise only be accessible to root
.
At the end of June we have been asked to review the new daemon for its security. We had a couple of hardening recommendations and found an instance of possible log spoofing, but have otherwise been satisfied with the implementation. Bugfixes and improvements have been incorporated and the new service is now ready to be used in production.
8) sysextmgr: New Varlink Service for Managing systemd-sysext Images
sysextmgr is another new Varlink service developed by SUSE, which this time helps with the management of systemd-sysext images on openSUSE MicroOS. We noticed the addition of this service to openSUSE via our monitoring of newly added systemd services in the distribution. While looking into the Varlink API we discovered a number of issues in the service like Denial-of-Service attack surface and some minor symlink issues. The issues could be resolved quickly and we are now happy with the state of the service.
9) bash-git-prompt: Predictable Temporary File Name Offers Local Attack Surface (CVE-2025-61659)
Our team is currently undertaking an effort to have a look at all kinds of
shell related drop-in code like command-specific shell completion support and
files installed into /etc/profile.d
to manipulate the shell environment.
Any packages can install such files and they can easily lead to security
problems when things are not done right.
The amount of such files in a complete Linux distribution is huge, naturally,
thus this is a long-term task that will require time to produce a complete
list of findings. A first finding in the
bash-git-prompt
package already resulted from this, however.
This package installs shell code into /etc/profile.d/bash-git-prompt.sh
which enables an interactive Git prompt which will be displayed as soon as the
Bash shell enters a Git repository. This prompt contains information about
the current repository, the number of modified files and other things that can
be configured by users. The prompt feature using default settings becomes
active as soon as the package is installed.
While looking into the shell code that implements all this we noticed the use
of a predictable temporary file in
/tmp/git-index-private$$
. bash-git-prompt
copies the Git index file found
in the current Git repository to this location. It turns out that this copy
operation happens every time the interactive shell user enters a new command
while being located in a Git repository. The temporary file is soon deleted
again when the Git bash prompt has been fully rendered by the program.
Since an interactive bash shell session is a long-lived process it is rather simple for other users in the system to pre-create the temporary file in question and cause all kinds of issues:
- Denial-of-Service: by blocking the path, the Git prompt setup will fail to complete and the prompt will be broken. By placing a FIFO named pipe in this location the victim’s shell will even lock up completely.
- information leak: the copy of the Git index is made using the umask of
the shell. When the default umask
022
is used, then the copy of the Git index becomes world-readable in/tmp
. If the victim’s Git repository contains non-public data then part of that data (e.g. file names of pending change sets) leaks to other users in the system. - integrity violation: when a local attacker places crafted data in the
location of the temporary file and denies write access, then
bash-git-prompt
fails to write the desired Git index data to this location, but will not stop execution despite this error. The crafted Git index data will be fed to various invocations of thegit
command line utility, possibly leading to a crafted bash prompt or even leading to some forms of code execution. To determine the full extent of this, a low level analysis of the handling of the binary Git index format would be necessary.
The problem was discovered independently a while ago already, which is why
there exists a public GitHub issue for it. An upstream
author attempted to fix the issue, but rolled back the changes due to a
regression and nothing happened since. The issue was introduced via commit
38f7dbc0bb8 in bash-git-prompt
version 2.6.1. We
added a simple patch to our packaging of bash-git-prompt
which should address all issues for users of openSUSE.
At the end of September we requested a CVE from Mitre to track this issue and they assigned CVE-2025-61659.
10) steam-powerbuttond: Insecure Operation in Home Directories
Our team’s monitoring of newly added systemd services in openSUSE led us to steam-powerbuttond. It derives from a script found on the SteamOS Linux distribution for use on Steam Deck gaming devices.
The main component of this package is a Python script which runs as a systemd
service with full root privileges. This script contains various security
issues. During startup the script attempts to
determine who “the first user” in the system is, by parsing the output of who
| head -1
. This user’s home directory is then used for operations later on,
when a power button press event is detected. After processing the event, the
file /home/{user}/.steam/steam.pid
is read and used for accessing
/proc/{pid}/cmdline
.
This logic leads to various possible issues, ranging from the the wrong user being selected initially, to denial-of-service when unexpected file content is placed in the unprivileged user’s home directory. We contacted one of the original upstream authors about this and offered coordinated disclosure. It turned out that the project is not supposed to be used anymore, however, and as a result the GitHub repository has been archived by the maintainer.
The openSUSE steam-powerbuttond
package is now waiting to be replaced by a
new script that is supposed to be found in SteamOS.
11) Conclusion
This edition of the SUSE security team spotlight was quite packed with topics. We hope this can give you an insight into all the different kind of activities we end up in on our mission to improve the security of open source software, in the Linux ecosystem in general and openSUSE in particular. We’re looking forward to the next issue of the spotlight series in about three months from now.