home

argv[0]: Finding bugs in shadow-utils and util-linux

Command-line arguments and environment variables are forms of user input. Like any user input, they can be exploited by attackers. An attacker can manipulate argc, argv, and envp when considering local privilege escalation (LPE) attacks.

int main(int argc, char *argv[], char *envp[]);

pwnkit

On newer kernels (linux-5.18+), Linux will not allow userspace programs to be called with argc == 0 (it will silently fix this). However, on older Linux kernels, argv[0] can be NULL.

The reason this was changed was due to PwnKit, a vulnerability in pkexec.

This was surprising to me, since argv[0] is usually defined as the running binary name. Almost all programs rely on argv[0] in usage messages/error messages.

In pwnkit, pkexec assumed that argc would be at least 1, which led to argv pointing out of bounds if argc was 0.

This was particularly bad for two reasons. The first is that argv[argc+1] is typically a pointer to envp in most Linux systems. The second is that pwnkit had argv rewriting code that ran after environment variable filtering code ran, resulting in code execution.

Searching for similar vulnerabilities

To generate a list of potentially vulnerable targets, I ran every setuid program on my system with argv[0] pointing to NULL with the simple program below:

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

char* argv[] = {NULL};

int main(int argc, char** argv_real) {
    if (!argv_real[1]) {
        puts("Invalid arg");
    } else {
        printf("Calling %s", argv_real[1]);
        execve(argv_real[1], argv, 0);
    }
}

I discovered that when argc == 0, a few binaries inside of shadow-utils (su, chsh, etc.) can be forced to segfault since they were calling basename on argv[0] without checking if argv[0] is NULL. I reported the issue and wrote a quick patch.

Unlike in the pkexec case, it seems we don’t have a way to exploit this bug. We can force a null dereference in a setuid program, but we can’t gain any permissions or abilities.

util-linux

One other potential security issue is that many binaries log argv[0] as the current program name.

In the case of su, argv[0] is sent to /var/log/auth.log. This allows us to hide our logs from other programs searching for su.

We can also include ANSI escape sequences as another method of attack:

#include<stdio.h>
#include<unistd.h>
int main(int argc, char** my_argv){
        char* prog = "/usr/bin/su";
        char* argv[] = {"\033[33mYellow", "root", NULL};
        char* envp[] = {NULL};

        execve(prog, argv, envp);
        printf("Failed to exec\n");
}

Escape sequences can sometimes lead to code execution due to vulnerabilities in terminals. Terminal vulnerabilities have been found from 2003 to 2024.

I reported the issue and it was fixed in commit: su, agetty: don’t use program_invocation_short_name for openlog.

Thanks

Many thanks to the shadow-utils and util-linux teams for their prompt responses and for merging the fix quickly. Their attention to detail and commitment to secure software is greatly appreciated.


Commits:

References: