Table of Contents

1) Introduction

TLP is a utility for saving laptop battery power when running Linux (note: the TLP acronym has no special meaning). In version 1.9.0 of TLP a profiles daemon similar to GNOME’s power profiles daemon has been added to the project, providing a D-Bus API for controlling some of TLP’s settings.

Our SUSE TLP package maintainer asked us for a review of the changes contained in the new TLP release, leading us to discover issues in the Polkit authentication logic used in TLP’s profiles daemon, which allow a complete authentication bypass. While looking into the daemon we also found some additional security problems in the area of local Denial-of-Service (DoS).

We reported the issues to upstream in December and performed coordinated disclosure. TLP release 1.9.1 contains fixes for the issues described below. This report is based on TLP 1.9.0.

The next section provides a quick overview of the TLP power daemon. Section 3 discusses the security issues we discovered in detail. Section 4 looks into the CVEs we assigned. Section 5 provides a summary of the coordinated disclosure process we followed for these findings.

2) Overview of the TLP Daemon

The new TLP power daemon is implemented in a Python script of moderate size. The daemon runs with full root privileges and accepts D-Bus client connections from arbitrary users. For authorization of clients a Polkit policy defines a couple of actions which are checked in the daemon’s _check_polkit_auth() function. Some of these actions are allowed for local users in an active session without providing further credentials, others require admin credentials.

3) Security Issues

3.1 Polkit Authorization Check can be Bypassed

The check_polkit_auth() function relies on Polkit’s “unix-process” subject in an unsafe way. The function obtains the caller’s PID and passes this information to the Polkit daemon for authorization, which is inherently subject to a race condition: at the time the Polkit daemon looks up the provided PID, the process can already have been replaced by a different one with higher privileges than the D-Bus client actually has.

As a result of this, the Polkit authorization check in the TLP power daemon can be bypassed by local users, allowing them to arbitrarily control the power profile in use as well as the daemon’s log settings.

This is a well-known issue when using the “unix-process” Polkit subject which was assigned CVE-2013-4288 in the past. For this reason the subject has been marked as deprecated in Polkit. The “unix-process” subject is seeing new use these days, however, when combined with the use of Linux PID file descriptors, which are not affected by the race condition.

Upstream Bugfix

We suggested to upstream to switch to Polkit’s D-Bus “system bus name” subject instead, which is a robust way to authenticate D-Bus clients based on the UNIX domain socket the client uses to connect to the bus. This is what upstream did in commit 08aa9cd.

3.2 Predictable Cookie Values in HoldProfile Method Allow to Release Holds

The D-Bus methods “HoldProfile” and “ReleaseProfile” can be used by locally logged-in users without admin authentication and allow to establish a “profile hold”, preventing the profile from being automatically switched until it is released again.

The “HoldProfile” method returns a cookie value to the caller which needs to be presented to the “ReleaseProfile” method again to release it. This cookie value is a simple integer which starts counting at zero and is incremented for each call to “HoldProfile”. This makes the cookie value predictable and allows other, unrelated users or applications to release an active profile hold by trying to guess the cookie value in use.

Upstream Bugfix

We suggested to upstream to make the cookie value unpredictable by generating a random number. This is what upstream did in commit a88002e.

As described in the previous section, the “ReleaseProfile” D-Bus method expects an integer cookie parameter as input. The Python D-Bus framework used to implement the method allows clients to pass non-integer types as cookie, however, which causes an exception to be thrown in the daemon. This does not lead to the daemon exiting, however, since the framework catches the exception.

The issue can be reproduced via the following command line:

user$ dbus-send --system --dest=org.freedesktop.UPower.PowerProfiles \
      --type=method_call --print-reply /org/freedesktop/UPower/PowerProfiles \
      org.freedesktop.UPower.PowerProfiles.ReleaseProfile string:test
Error org.freedesktop.DBus.Python.ValueError: Traceback (most recent call
last):
  File "/usr/lib/python3.13/site-packages/dbus/service.py", line 712, in
_message_cb
    retval = candidate_method(self, *args, **keywords)
  File "/usr/sbin/tlp-pd", line 223, in ReleaseProfile
    cookie = int(cookie)
ValueError: invalid literal for int() with base 10: dbus.String('test')

Upstream Bugfix

While this is not strictly a security issue, we still suggested to make the daemon more robust by actively catching type mismatch issues for the cookie input parameter. Upstream followed this suggestion and implemented it in the same commit as above which introduces unpredictable cookie values.

3.4 Unlimited Number of Profile Holds Provides DoS Attack Surface

The profile hold mechanism described in section 3.2 allows local users in an active session to create an unlimited number of profile holds without admin authentication. This can lead to resource exhaustion in the TLP power daemon, since an integer is entered into a Python dictionary along with arbitrary strings reason and application_id which are also supplied by the client. This API thus offers Denial-of-Service attack surface.

We found a similar issue in GNOME’s power profile daemon some years ago, but GNOME upstream disagreed with our analysis at the time, which is why SUSE distributions are applying a custom patch to limit the number parallel profile holds.

Upstream Bugfix

We asked upstream whether there are any valid use cases for supporting a large number of profile holds in parallel, and it turns out that the typical use case is only to support a single profile hold at any given time. Thus upstream agreed to restrict the number of profile holds to a maximum of 16, which is implemented in commit 6a637c9.

4) CVE Assignment

We assigned CVE-2025-67859 to track issue 3.1 (Polkit authentication bypass). Issues 3.2 (predictable cookie values) and 3.4 (unlimited number of profile holds) would formally also justify CVE assignments; their severity is low, however, and we agreed with upstream to focus on the main aspect of the Polkit authentication bypass.

5) Coordinated Disclosure

We reached out to the upstream author on December 16 with details about the issues and offered coordinated disclosure. Upstream confirmed the issues and accepted coordinated disclosure. We discussed patches and further details over the course of the following two weeks. Due to the approaching Christmas holiday season we decided to set the general publication date to January 7.

We want to express our thanks to the TLP upstream author for the smooth cooperation in handling these issues.

6) Timeline

2025-12-16 We reached out to the upstream developer by email providing a detailed report and offered coordinated disclosure.
2025-12-17 We received a reply discussing details of the report. Coordinated disclosure was established with a preliminary publication date set to 2026-01-27.
2025-12-20 We received a set of patches from upstream for review. 2026-01-07 was suggested as new publication date.
2025-12-23 We provided positive feedback on the patches and agreed to the new publication date. We also pointed out the additional problem of the unlimited number of profile holds (issue 3.4).
2025-12-25 We received a follow-up patch from upstream limiting the number of profile holds.
2025-12-29 We reviewed the follow-up patch and provided positive feedback to upstream.
2025-01-07 Upstream published bugfix release 1.9.1 as planned.
2025-01-07 Publication of this report.

7) References