-- Leo's gemini proxy

-- Connecting to gemini.ctrl-c.club:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

A simpler overview of polkit


The modern Linux desktop includes a lot of components that provide functionality that is very useful to the average user, such as mounting USB drives with user permissions. Figuring out how these components work can be difficult, however. polkit, formerly known as PolicyKit, is one of these components.


The documentation for polkit is rather poor in my opinion. The man pages are difficult to understand, and the error messages are unhelpful. polkit is often criticized for being too complex, and some Linux users go out of their way to avoid software that relies on polkit. Fortunately, in many popular distros such as Ubuntu and Fedora, polkit is configured to work smoothly, and error messages related to lack of permissions are rare. For those with setups that are not as ordinary, however, polkit can be a source of frustration.


Personally, I dislike polkit because I do not need the fine-grained permissions that it is capable of doing, and it has had security vulnerabilities in the past, including a well-known one named "PwnKit" that existed for many years before it was patched. User groups are more than enough for my use case. Unfortunately, desktop environments often use polkit for features such as mounting USB drives without root permissions, and I do not want to switch from XFCE, which depends on udisks2 for mounting of external drives. Naturally, udisks2 depends on polkit. I am aware that alternatives such as pmount exist, but those alternatives do not integrate well with Thunar, XFCE's file manager. I conceded that polkit is something that I will just have to tolerate.


As someone experimenting with Void Linux and using a setup without systemd or elogind, I ran into permission problems with polkit when trying to mount a USB drive within Thunar as a non-root user. In the process of solving those problems, I learned more about how polkit works, and I want to document what I learned so I can reference it in the future. While I wrote this overview mainly for myself, I hope this overview can help those with a similar setup and make polkit a bit more tolerable.


As this is a simplified overview, this article will only cover the most common scenarios. In particular, I assume that the user uses their password to authenticate, but other authentication methods may work with polkit. I also assume that there are only two categories of users: normal users and administrators. This overview is also written for Debian-based systems with a polkit version of at least 106.


Agents


The polkit(8) man page makes agents seem complicated, but an agent is simply a program which displays the interface that asks the user for their password when performing an action that requires administrator privileges. A common example of an agent is the dialog box that pops up when the system is requesting the user's password. Agents run in the background and only appear when the user's password is required.


Agents often try to match the look and feel of the desktop environment that the user is interacting with. In particular, GNOME and KDE provide their own agents. There is also an agent that provides a command-line interface.


If one tries to start an graphical application that uses polkit and nothing happens, it is possible that there is no graphical agent installed. Try starting the application from the command line and see what happens.


Actions


Programs that use polkit define actions, which are features of a program that not all users should be able to use. These actions are installed along with the program, and they are defined in XML files. Actions defined by the program are located in the /usr/share/polkit-1/actions directory, and they have the .policy file extension.


Each action has a name, such as "org.freedesktop.udisks2.filesystem-mount", which uniquely identifies an action. There are other properties defined for each action, but the most important part of an action's definition resides within the <defaults> tag. That section defines the default permissions for a given action.


In order to understand what is going on within that section, the user has to know what a session is and the various types of sessions.


Sessions


When a user is logged into a computer, they are in a session. A session is essentially a bunch of interactions between the user and the computer over a period of time. The session starts when the user logs in, and it ends when the user logs out.


There are three types of sessions that polkit defines:

An active session is when the user started that session locally and is currently using it.

An inactive session is when the user started that session locally but is not currently using it.

Other sessions that do not meet the above criteria, such as sessions started over SSH or other remote technologies.


"Locally" in this case means that the user is in front of the monitor and is using a keyboard and mouse connected to the computer itself.


It is important to note that a single user can have multiple sessions running at once. The most common example is when a user is logged into multiple virtual consoles, which are the consoles accessed with the Ctrl+Alt+F1 to F6 key combination. In that case, only one of those sessions will be active, and the others will be inactive.


Some online resources make the claim that inactive sessions are usually remote sessions, but this is not the case. The details are described in the appendix at the end of this article for those who are interested.


There is one caveat to all of the above, however. If neither systemd nor elogind is being used, then polkit cannot know whether a session is active, inactive, or some other type of session. In this case, all sessions, whether local or remote, are treated as being in the "other sessions" category. This is very important to know for those that do not run systemd or elogind, and this is actually the case with my Void Linux setup.


Permissions


I know that the polkit(8) man page technically calls them "authorizations", but "permissions" is an easier term to understand in my opinion.


With that out of the way, recall that each action includes a <defaults> tag. Within this tag are three more tags: <allow_active>, <allow_inactive>, and <allow_any>. These correspond to the three types of sessions described earlier, and each type of session can have a different default permission.


The possible values within those three session tags are:

"yes" - action is allowed even without a password

"no" - action is never allowed no matter what

"auth_self" - the session's user must enter their password

"auth_admin" - an admin must enter their password

"auth_self_keep" - same as "auth_self" but the password is remembered for a while

"auth_admin_keep" - same as "auth_admin" but the password is remembered for a while


Thus, it is possible to allow an action unconditionally for active sessions while requiring a password for remote sessions. Recall that remote sessions are classified as "any" since they are not local sessions.


If neither systemd nor elogind is being used, then there is no concept of an active or inactive session, and polkit will only look at the value specified within the <allow_any> tags.


Users can change the permissions associated with actions by editing the .policy files, but this is not recommended because package managers may overwrite these modifications when programs that use polkit are updated. Instead, there is another mechanism that allows users to modify permissions, which is described below.


Details about actions can be displayed using the pkaction command. An example demonstrating its usage is below:


pkaction -a org.freedesktop.udisks2.filesystem-mount -v

Refer to the pkaction(1) man page for more details.


Rules


While .policy files describe the default permissions for each action, rules can be used to augment or override these permissions. Rules provided by an application are located in the /usr/share/polkit-1/rules.d directory, and user-defined rules can be placed in the /etc/polkit-1/rules.d directory. Files containing polkit rules have the .rules file extension.


The permissions defined by rules have priority over those defined in .policy files. If the default permission of an action is to allow any user to do that action, but a rule forbids remote users from performing that action, then remote users will not be allowed to do that action. Creating rules within /etc/polkit-1/rules.d is the intended way of overriding the default permissions specified in .policy files.


For some reason, polkit rules are written in JavaScript, and this makes it very difficult to define rules if one is not a programmer. What usually happens in practice is that most people copy and paste polkit rules that they find on the Internet in order to fix "permission denied" or "operation not permitted" errors without fully understanding what is going on within those rules. In my opinion, this is a stupid design choice that makes an already complex system more difficult to understand, and it can lead to people making their systems less secure due to badly-written rules.


Those that know JavaScript and want to create rules should consult the polkit(8) man page. In practice, though, it might be faster to take an existing rule from a trusted source and modify it to suit one's needs. Rules are actually implemented as JavaScript functions that get called in the process of determining whether a user is allowed to carry out an action. These functions allows for even more fine-grained control that .policy files alone cannot provide, and they can check details such as the user's name and their group membership. Further details on rule creation will not be covered in this article.


If users need to test their rules, they can use the pkcheck command. An example demonstrating its usage is below:


pkcheck -u -p $$ -a org.freedesktop.udisks2.filesystem-mount

Refer to the pkcheck(1) man page for more details.


Conclusion


To summarize everything covered in this article:

Agents are the programs that ask for the user's password

Actions are the features within a program that not all users should be able to do

The default permissions for these actions are defined in XML files that come with the program

These permissions can differ based on the type of session

Rules are JavaScript functions that can override the default permissions


Hopefully this article makes polkit a bit easier to understand.


Appendix: what is an inactive session, exactly?


This section is optional and requires basic knowledge of the C programming language.


Earlier, I stated that some online resources state that inactive sessions are usually remote sessions and that active sessions are usually local sessions. This is an incorrect simplification. I wrote a simple program that demonstrates that inactive sessions can easily be local sessions, especially if multiple users are logged in at the same time. It is written with libelogind in mind since I currently use elogind on my desktop, but it will work with libsystemd with minor modifications.


The code for this program is below:


/* Replace with 'systemd' if using libsystemd
 *
 * Build command:
 * gcc -Wall session_test.c -lelogind -o session_test
 */
#include <elogind/sd-login.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char** argv) {
    /* Pause to allow virtual console switching */
    sleep(5);

    char* session = NULL;
    int ret = sd_pid_get_session(0, &session);
    if (ret < 0) {
        printf("sd_pid_get_session failed with error code %d\n", ret);
        return 1;
    }

    printf("Session id: %s\n", session);
    printf("Active: %d\n", sd_session_is_active(session));
    printf("Remote: %d\n", sd_session_is_remote(session));

    char* state = NULL;
    ret = sd_session_get_state(session, &state);
    if (ret < 0) {
        printf("sd_session_get_state failed with error code %d\n", ret);
        free(session);
        return 1;
    }

    printf("State: %s\n", state);

    free(session);
    free(state);

    return 0;
}

I am rusty with C, so I apologize if my code is not very robust.


To demonstrate that inactive sessions can easily be local sessions, follow these steps while logged in locally:

Compile the above code.

Run the code and wait a few seconds for the output to be shown.

Note that the return value of sd_session_is_active is a positive integer, and note that the return value of sd_session_is_remote is zero.

Note that the session's state is "active".

Run the code again, but this time, switch to a different virtual console with Ctrl+Alt+F1 to F6 during the sleep() call.

Wait at least five seconds for the program to finish running.

Switch back to the virtual console or desktop where the code was executed. The program should have finished running by now.

Note that the return values of both sd_session_is_active and sd_session_is_remote were zero.

Note that the session's state was "online".


Now I want to go a bit further and prove that inactive sessions can never be remote sessions. We can look at polkit's source code for this, and a link to the relevant part of the code is below:


Link to polkit code where the type of session is checked


Note that whether a session is active or not only matters if the session is local. This distinction corresponds with the <allow_active> and <allow_inactive> tags in .policy files. If the session is not local, which is the case with remote sessions, then the permission stated in the <allow_any> tags are checked. Thus, an inactive session can never be a remote session. In other words, all remote sessions are considered to be in the "other sessions" category described earlier in the article.


To summarize, any statements which claim that inactive sessions can be remote sessions are wrong. An inactive session is a local session that is not being interacted with currently.


External links


I consulted these resources during the creation of this article.


Arch Wiki page on polkit

Comment on LWN about 'active' and 'inactive' sessions

gist post containing example polkit rules

polkit(8) man page

reddit comment on r/voidlinux about polkit without elogind

sd_session_is_active(3) man page


Index

-- Response ended

-- Page fetched on Sun May 19 07:54:44 2024