SELinux: shadow: Enhancing security for a security program


useradd is a typical program of the shadow package, used to create new users. But it did not work in the secure domain regulated by the security policy. In this article, we add code to solve the problem.
There is a situation where the useradd runs in the wrong domain and cannot write shadow_t files, so it does not work, because the domain it is running is not allowed to do. We consider the following command, create a user named some_name

sudo useradd some_name

An error has occurred

Why? We go to find the cause and then come up with solutions.

useradd is being run in the sysadm_t domain
We already know what is automatic domain transition and what is manual domain transition through the article SELinux: kernel: The steps for security checking a program before it runs and the corresponding security rules.

When the user runs the sudo command, assuming the user domain is staff_t, the shell will call execve() to execute the binary of type sudo_exec_t. Because the statement domtrans_pattern(staff_t, sudo_exec_t, staff_sudo_t) in the sudo policy module, automatic domain transition is performed, and the sudo program is run in the staff_sudo_t domain. Then it is sudo's turn to execute the useradd program.
But first it needs to work with PAM. When sudo calls execve() to execute the useradd program, the PAM module pam_selinux will set the default security context for the imminent process used for the execve() call. The default security context is determined based on the content in the policy configuration file contexts/default_contexts, which has a line as following:

staff_r:staff_sudo_t:s0		sysadm_r:sysadm_t:s0 staff_r:staff_t:s0

Because sudo executes the command as root user by default, the new domain will be the domain of root user, ie sysadm_t. This is the case of manual domain transition using setexeccon() called by PAM, and the useradd program is set to run in the sysadm_t domain.
Because all user domains including sysadm_t are not allowed to read and write the password file (and other files of the same type of shadow_t) while the useradd needs that, so the user creation command fails.

Executing the useradd one more time
Everything will be fine if the domain is transitioned to useradd_t - the domain designed for useradd by the security policy, to be allowed to read and write shadow_t files.
Naturally sysadm_t is allowed to be transitioned to the useradd_t domain, so if inside the useradd program we call execve() to execute it again, the domain will be transitioned to useradd_t applied automatically by the kernel. The beginning part of the useradd main program can be supplemented with the following code, using execvp() instead of execve()

 * main - useradd command

#include <selinux/selinux.h>
#endif				/* WITH_SELINUX */

int main (int argc, char **argv)
    // Some original declarations here


    // This patch only affects when SELinux is running in enforcing mode
    if (! is_selinux_enabled())
        goto NO_RE_EXECUTE;
    if (! security_getenforce())
        goto NO_RE_EXECUTE;
    errno = 0;
    char *context;
    bool transitioned = strstr(context, ":useradd_t:") ? true : false;
    bool is_root = strstr(context, "root:sysadm_r:sysadm_t:") ? true : false;

    // Do not re-execute once the desired security context has been obtained
    if (transitioned)
        goto NO_RE_EXECUTE;
    // Re-execute only if it is in the root user's domain
    if (! is_root)
        goto NO_RE_EXECUTE;
    // Fix file context if needed
    if (! selinux_file_context_verify("/usr/sbin/useradd", 0)) {
        int ret = setfilecon("/usr/sbin/useradd",
        if (ret == -1) {
    // Execute the program again to apply automatic domain transition
    execvp(argv[0], argv);

#endif				/* WITH_SELINUX */

Patching the sudo package
The essence of the problem is that we only need to execute the useradd after the domain has reached sysadm_t, so we have another approach of not letting sudo execute the useradd directly but indirectly via the shell. First, sudo runs the shell and the domain is transitioned from staff_sudo_t to sysadm_t. The shell then executes useradd and the domain is transitioned from sysadm_t to useradd_t. The source file src/parse_args.c of the sudo package needs to be added the following

    if (ISSET(mode, MODE_RUN) && argc > 0) {
        if (
            strcmp(argv[0], "useradd")  == 0 ||
            strcmp(argv[0], "userdel")  == 0 ||
            strcmp(argv[0], "usermod")  == 0 ||
            strcmp(argv[0], "groupadd") == 0 ||
            strcmp(argv[0], "groupdel") == 0 ||
            strcmp(argv[0], "groupmod") == 0 ||
            strcmp(argv[0], "pwconv")   == 0 ||
            strcmp(argv[0], "grpconv")  == 0 ||
            strcmp(argv[0], "chfn")     == 0 ||
            strcmp(argv[0], "chsh")     == 0
            SET(flags, MODE_SHELL);

Complete information is in the patch file sudo-1.8.31p1-selinux_trans-1.patch.

Running the sudo with the -s or -i option
If you don't want to fix the code, just run sudo with the -s or -i option. The effect is similar to the second solution

sudo -s useradd some_name

Currently unrated


There are currently no comments

New Comment


required (not published)



What is 8 - 4?