Every day you can do all daily works in the desktop environment as a normal user. Then no need to be the root, even not an administrator, you can power off the machine although really only root has capability to do so. That is because you have used the polkit authorization policy.
As an administrator, you can update passwords for users because you are authorized to an account service in the system. This service has the privileges to handle requests.
All users naturally need a network connection, authorization policy allows this by default. Administrators can even modify the network connection for all users without authentication if there is an authorization rule that allows it (this is the default authorization policy in Omarine).
Administrators can set system time and time zone based on authorization policy.
When you use a file browser, the filesystems on the drives are automatically mounted by the udisks tool, which is governed by the authorization policy.
There are many other applications that use authorization policy to create convenience for users. It also makes administration easy. However, convenience is always accompanied by security concerns. In this article we review and develop security policy in relation to authorization policy. In particular, we practice building an administrator program that updates user passwords applying authorization policy. This program is useful for administrators when the account service is not available.
Normal user, administrator, root, super user, privileged and unprivileged user, restricted user
Before going into the main part, we reinforce some concepts about users. They have different meanings depending on the specific context.
At the kernel level, there are only two kinds of user: root and normal user. root is the privileged user (technically, root has user ID of 0 so processes with an effective user ID
of 0 are privileged processes) and is exempt from all kernel permission checks . Hence it is also called super user. However, since the security policy, root is no longer exempted as before and its "super" ability has also decreased. In contrast, normal users who have a user ID other than zero must undergo full permission checking and the result will, depending on their capabilities, be an unprivileged user.
It is the authorization policy that gives rise to the concept of administrator, who deal with system administration and operating tasks within the scope of the authorization policy. Thus, for authorization policy there are three kinds of user: root, administrator and normal user.
In the same view as the authorization policy, the SELinux security policy has the SELinux root
corresponding to the Linux root, staff_u
corresponding to the administrator and user_u
corresponding to the normal user. In addition, SELinux also has users with lower rights than normal users, which can be classified as restricted user. Those are guest_u and xguest_u. Restricted users are uncommon and can be used for (mapping from) Linux system users. user_u is usually the default user.
Authorization policy
Any application that wants to apply the authorization policy needs to ship (or use) a privileged program called mechanism and an unprivileged program called subject. The mechanism is run as root or its process has the effective user ID of 0, while the subject is run as a normal user or its process has the effective user ID other than zero.
Mechanism provides the service and it can accept or deny service to requests from the subject, through the authority implemented by a polkit system daemon, polkitd
Authorization authentication is performed by an authentication agent running in the user session to check if the user of the session is the right user, administrator, or root according to the authorization policy (by entering the correct password). The agent program only provides outside input dialog, the actual authentication is done by polkit helper with program file /usr/lib/polkit-1/polkit-agent-helper-1. Although the agent runs in the user session and therefore the helper normally has no root privileges, it is set-user-ID-root
to run as root and satisfies the prerequisites for checking the password
Mechanism has to declare actions that set out the default authorization. Authorization rules can be used to overwrite the default authorization or to define administration users. The communication in authorization uses the system message bus. The following is a system architecture diagram of polkit extracted from polkit(8)
Actions are those that the subject can request the mechanism to perform and are defined in XML files whose tail .policy located in /usr/share/polkit-1/actions
. See polkit(8) for more details.
Authorization rules are placed in .rules files in /etc/polkit-1/rules.d
and /usr/share/polkit-1/rules.d
directories, written in JavaScript programming language syntax with the global object is polkit
. Rule files are read by polkitd in descending order of priority by filenames.
The polkit object is of type Polkit where addRule()
and addAdminRule()
methods are common used, which have the following signatures:
void addRule(polkit.Result function(action, subject) {...}); void addAdminRule(string[] function(action, subject) {...});
The addRule() method is used to add a function to the general rules. The function can be called whenever an authorization check for the action and subject is performed. It should return a value from polkit.Result, for example
polkit.Result.NO
: no authorization
polkit.Result.YES
: allow authorization without authentication
polkit.Result.AUTH_ADMIN
: Authentication by admin user is required
polkit.Result.AUTH_ADMIN_KEEP
: Similar to polkit.Result.AUTH_ADMIN, but authorization is kept for a short time (this amount of time in the current version of polkit is hard-coded 5 minutes).
The addAdminRule() method is used to add a function to the admin rules. The function can be called whenever administrator authentication is required. It should return an array of strings where each string has the form "unix-group: <group>"
, "unix-netgroup: <netgroup>"
or "unix-user: <user>"
.
The action
parameter in the above functions is an object with information about the action being checked. It is of type Action which has an id attribute of type string
action.id
: action identifier. For example "org.omarine.change-user-password".
The Action type has a method:string lookup (string key);
The lookup() method is used to find variables passed from the mechanism. For example, the mechanism pkexec
(pkexec is a mechanism available in the polkit package) sets the program variable that can be obtained using the expression action.lookup ("program") whose value is the fully qualified path of the program file that pkexec executes.
The subject
parameter in the above functions is an object with information about the process being checked. It is of type Subject that has a common attribute namely user whose type of string
subject.user
: username.
The Subject type has a common method:boolean isInGroup (string groupName);
The isInGroup() method is used to check if the subject is in the groupName group.
An example of applying the authorization policy
Administrators can use the Kate
editor to modify the content of a file owned by another user, even modifying a file of the root user. When you save the file, the org.kde.ktexteditor.katetextbuffer.savefile
action is triggered. This action is defined in the file /usr/share/polkit-1/actions/org.kde.ktexteditor.katetextbuffer.policy
. The shortened content of the file is as follows
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> <policyconfig> <vendor>Document Actions</vendor> <action id="org.kde.ktexteditor.katetextbuffer.savefile" > <description xml:lang="Name">Save Document</description> <message xml:lang="Description">Root privileges are needed to save this document</message> <defaults> <allow_inactive>no</allow_inactive> <allow_active>auth_admin</allow_active> </defaults> </action> </policyconfig>
Because the default authorization is auth_admin
, administrator authentication is required. This results in the handling of the rule file /etc/polkit-1/rules.d/50-default.rules
because it contains the admin rule, which is the default rule of polkit
/* -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- */ // DO NOT EDIT THIS FILE, it will be overwritten on update // // Default rules for polkit // // See the polkit(8) man page for more information // about configuring polkit. polkit.addAdminRule(function(action, subject) { return ["unix-group:wheel"]; });
The function returns the wheel
group so the users in the wheel group are administrators.
The requirement is to authenticate one of the administrators.
Talking about the admin group we need to know more that PAM
is also involved in grouping the administrators by launching pam_wheel.so
library. This library is included in the su
and sudo
PAM service files, so only users in the wheel group, in other words only the administrators (and root, of course) has permission to run su and sudo.
Observing above, we see that there is disagreement between KDE and polkit. KDE's intention is to allow only root to modify other user's files while polkit's default admin rule allows any administrator to act. (although technically only root can modify other user's files, in terms of authorization, root and administrator are different)
If you want to allow root only to modify other user's files, add the following rule, place in file /usr/share/polkit-1/rules.d/00-kate.rules
to overwrite the default admin rule of polkit
polkit.addAdminRule(function(action, subject) { if (action.id == "org.kde.ktexteditor.katetextbuffer.savefile") { return ["unix-user:root"]; } });
The authentication agent now authenticate root
SELinux root is not Linux root
Linux root does not always become SELinux root and it does not if the root's process did not reach its security context.
We run the pkexec command as an administrator, pkexec allows the authorized user to execute a program as another user. If you run the command without arguments it will run /bin/bash as the root user. But the process does not transition to the root's process context but retains the administrator's process context. Linux user is root while SELinux user is staff_u
To overcome this we need to do two things:
1) Use the pam_selinux.so library in the polkit-1
PAM service to transition domain. You run the command below
sudo tee /etc/pam.d/polkit-1 << EOF auth include system-auth account include system-account password include system-password session required pam_selinux.so close session required pam_unix.so session required pam_selinux.so open session required pam_systemd.so session optional pam_xauth.so EOF
2) Borrow the type sudo_exec_t
of the sudo program, assign it to the program file /usr/bin/pkexec. First create the spec file
cat > spec << EOF /usr/bin/pkexec system_u:object_r:sudo_exec_t:s0 EOF
Then set the file context
sudo setfiles spec /usr/bin/pkexec
Now root has become SELinux root
Authenticating authorization, but disallowing the user domains to read password!
During authentication, the polkit-agent-helper-1 authentication agent helper will be called from the user domain. This program needs to read the password file to authenticate. If we do not transiton the domain, the user domain will attempt to read the password, which is not acceptable in the security policy.
So the domain needs to transiton to the helper program's domain, policykit_auth_t
, using the following statement in the security policy
policykit_run_auth(staff_t, staff_r)
policykit_auth_t is a specialized domain, we allow it to do
auth_read_shadow(policykit_auth_t)
Building the pwadm admin program that applies authorization policy to update user passwords
We follow the steps below to build pwadm
, a program that helps administrators update user passwords. Administrators can change password for all users but root.
We define a custom action, using the mechanism pkexec. The pwadm program does not access the password file, it only encrypts the password that the user has entered and passes the encrypted password to the usermod to update the user password. Cracklib
is used to check for weak passwords. The program changes the user password with a new password but is called an update because even if the password entered is the same as the old password, the encrypted password of the user is completely changed. This keeps the old password in a secure new state.
1) Writing the pwadm program
Create the source file pwadmbin.c
with the following content
/* * pwadm program * * Sat 01 Aug 2020 04:10:08 AM UTC */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pwd.h> #include <crack.h> #include <sys/time.h> #define false 0 #define true 1 typedef _Bool bool; #define MAX_SALT_SIZE 16 #define MIN_SALT_SIZE 8 #define MAX_PASS_TRIES 3 #define STRCPY_SAFE(A,B) \ (strncpy((A), (B), sizeof(A) - 1), (A)[sizeof(A) - 1] = '\0') // Return a random number in range [min, max) long random_range(long min, long max) { long u = (long)((double)random() / ((double)RAND_MAX + 1) * (max - min) + min); return u; } // Set the seed for random () based on current time and process void seed_random(void) { struct timeval tv; (void) gettimeofday (&tv, NULL); srandom ( tv.tv_sec ^ tv.tv_usec ^ getpid() ); } // Create a salt to encrypt the user's password const char *make_salt (size_t salt_size) { static char salt[24]; salt[0] = '\0'; do { strcat (salt, l64a (random())); } while (strlen (salt) < salt_size); salt[salt_size] = '\0'; return salt; } // Check weak password with cracklib bool badpass (const char *pass, const struct passwd *user) { const char *problem = FascistCheckUser( pass, NULL, user->pw_name, user->pw_gecos); if (problem != NULL) { printf ("Bad password: %s.\n", problem); return true; } return false; } int main(int argc, char *argv[]) { char *encrypted_pass, *clear_pass; char *username, *prog; struct passwd *user; int ret, i; char salt[21]; size_t salt_len; char pass[200]; prog = argv[0]; if (argc != 2) { fprintf(stderr, "Usage: %s username\n", prog); exit(EXIT_FAILURE); } username = argv[1]; // Administrator can change the password of every user except root if (strcmp (username, "root") == 0) { fprintf (stderr, \ "%s: you are not authorized to change the password of \ the root user.\n", prog); exit(EXIT_FAILURE); } // Check account name errno = 0; user = getpwnam(username); if (! user) { if (errno) perror("getpwnam"); else fprintf (stderr, "%s: user not found.\n", prog); exit(EXIT_FAILURE); } // Let administrator enter new password up to MAX_PASS_TRIES times bool warned = false; for (i = 0; i < MAX_PASS_TRIES; i++) { clear_pass = getpass ("New password: "); if (! clear_pass) { perror("getpass"); exit(EXIT_FAILURE); } if (warned && (strcmp (pass, clear_pass) != 0)) { warned = false; } STRCPY_SAFE (pass, clear_pass); bzero (clear_pass, strlen(clear_pass)); if (! warned && badpass (pass, user)) { if (i < MAX_PASS_TRIES - 1) { printf ("\n%s\n", \ "Warning: weak password. Enter it again if you still \ want to use it."); warned = true; } continue; } clear_pass = getpass ("Re-enter new password: "); if (! clear_pass) { perror("getpass"); exit(EXIT_FAILURE); } if (strcmp (clear_pass, pass) != 0) { printf ("\n%s\n", "Passwords do not match please try again."); } else { bzero (clear_pass, strlen(clear_pass)); break; } } if (i == MAX_PASS_TRIES) { fprintf (stderr, "The password for %s has not changed.\n", username); exit(EXIT_FAILURE); } // Seed for random () seed_random(); // Make the salt strcpy (salt, "$6$"); salt_len = (size_t) random_range (MIN_SALT_SIZE, MAX_SALT_SIZE + 1); strcat (salt, make_salt (salt_len)); strcat (salt, "$"); // Encrypt the password encrypted_pass = crypt (pass, salt); bzero (pass, sizeof pass); if (! encrypted_pass) { fprintf (stderr, "%s: password encryption failed.\n", prog); exit(EXIT_FAILURE); } // Update the encrypted password for username char *args[6]; args[0] = "/usr/sbin/usermod"; args[1] = "-p"; args[2] = encrypted_pass; args[3] = "--"; args[4] = username; args[5] = NULL; ret = execvp("/usr/sbin/usermod", args); if (ret == -1) { perror("execvp"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
2) Compiling the program
You compile the program as follows
gcc -lcrypt -lcrack -o pwadmbin pwadmbin.c
3) Copy the pwadmbin binary to /usr/bin
sudo cp pwadmbin /usr/bin
4) Create the pwadm script program
sudo tee /usr/bin/pwadm << "EOF” #!/usr/bin/pkexec /bin/bash pwadmbin $* EOF
5) Turn the script file into an executable file
sudo chmod u+x /usr/bin/pwadm
6) Do the same as pkexec, set pwadm file type to sudo_exec_t
7) Create org.omarine.change-user-password
action defined in /usr/share/polkit-1/actions/org.omarine.policy
as follows
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD polkit Policy Configuration 1.0//EN" "http://www.freedesktop.org/software/polkit/policyconfig-1.dtd"> <policyconfig> <vendor>pwadm</vendor> <vendor_url>https://www.omarine.org</vendor_url> <action id="org.omarine.change-user-password"> <description>Change user passwords</description> <message>Authentication is required to change user password</message> <defaults> <allow_any>no</allow_any> <allow_inactive>no</allow_inactive> <allow_active>auth_admin_keep</allow_active> </defaults> <annotate key="org.freedesktop.policykit.exec.path">/bin/bash</annotate> <annotate key="org.freedesktop.policykit.exec.argv1">/usr/bin/pwadm</annotate> </action> </policyconfig>
8) Getting details about the action
You run the command below to see action details
pkaction --action-id org.omarine.change-user-password --verbose
Now we can run the program
pwadm tho
Because the default authorization is auth_admin_keep, we can work with user passwords within 5 minutes without having to re-authenticate.
Unlike account service as a mechanism, the pwadm program is a subject. The account service updates the requested password from a subject for example named user manager. The user manager receives the password entered from the user, encrypts it and then sends it to the account service. These operations are conducted as the user of the session and require absolutely no root privileges. Account service then updates the password based on its root privileges. While the pwadm program does not inherently have the privileges but after authorization it is run as root and becomes privileged. It does all the works, from receiving password, encryption to updating password.
Questions for you
1) As a mechanism, the account service performs a specific action of updating password. So what action does pkexec take?
2) Is the pwadm program secure?
Hint: see org.omarine.change-user-password action
3) When running pwadm, how does the domain transition chain take place?
Hint: when the kernel loads the script, it replaces the script with the interpreter. But the script determines the security context, not the interpreter program. The sequence of executables seen in the user space is /usr/bin/pkexec → /bin/bash → /usr/bin/pwadmbin
Contact: tuyen@omarine.org
Comments
There are currently no comments
New Comment