argv[0]
: Finding bugs in shadow-utils and util-linuxCommand-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[]);
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.
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.
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.
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:
argc == 0
References: