Pwnkit: Linux Privilege Escalation (CVE-2021-4034)
Table of Contents
This was a Linux Priviledge Escalation (LPE) vulnerability in polkit’s pkexec that affected almost every linux distribution. Today we are going to look at the effects of OOB (out-of-bounds) read and write bugs and how they can be critical.
Overview #
What is an OOB vulneraility?
An out-of-bounds write (OOBW) vulnerability is when a process or a software writes data past the end or before the beginning of the allocated buffer in memory. An out-of-bounds read vulnerability (OOBR) is when a process reads data from outside the bounds of an allocated buffer in memory.These vulnerabilities can therefore lead to memory corruption, a system crash or chained for RCE.We are going to see in play the effect of these vulnerabilities chained leading to a local privilege escalation.
What is this polkit pkexec in linux?
This is a tool from the polkit or policy kit software package and it is a common component for modern unix-based systems. It is used to control system-wide privileges and provides an organized way for non-privileged processes to communicate with privileged processes. It can also be used to execute commands with elevated privileges much like sudo using the command pkexec command.
According to the linux manual page
man polkit
PolicyKit provides an authorization API intended to be used by privileged
programs (“MECHANISMS”) offering service to unprivileged programs (“CLIENTS”)
through some form of IPC mechanism such as D-Bus or Unix pipes.
man pkexec
pkexec allows an authorized user to execute a PROGRAM as another user and
If that username is not specified, then the program will be executed as
the admin super user that is root.
Root Cause Analysis (RCA) #
As started earlier the vulnerability is an out-of-bounds read and write vulnerability and this can be seen from the source code snippet of pkexec that was presented by Qualys Security Company during their disclosure.
Below are source code snippets that are used to describe the vulnerability in detail.
- The main() entry function that processes the command-line arguments. (pkexec takes in different arguments from the command-line)
int main(int argc, char *argv[]){
....
for(n=1, n < (guint) argc, n++){
// do something
}
path = g_strdup(argv[n]);
if (path[0] != "/"){
s = g_find_program_in_path(path);
argv[n] = path = s;
}
}
-
From the above snippet and as it is in every C application, the number of arguments are stored in
argc
variable and the actual arguments passed stored in the pointer arraychar *argv[]
. Therefore when we pass no arguments to the/usr/bin/pkexec
the value ofargc
will be1
(this is the calling process’s name) which is also the value in argv followed by a null value to terminate the array. -
This doesn’t make sense at all we need
argv
to be NULL:(, its still not vulnerable to OOB, well remember we can initiate any process with theexecve()
syscall and control args passed to the process. What if we pass aNULL
value to toexecve()
as follows:execve(/usr/bin/pkexec, NULL, char **envp)
The value of
argc
will now be0
and the value ofchar *argv[]
will beNULL
, terminating the array.
-
As from the code snippet above, the following will be our values during execution.
- The
for
loop initializesint n
to1
. - The pointer
path
points to amalloced()
address that isargv[n]
, since the value of n == 1, argv[1] reads passsed our terminated array leading to an OOBR. - The pointer
(s)
writes intoargv[1]
again and this leads to an OOB-write vulnerability.
- The
-
Since we did not provide any arguments when calling the process, what is read and written into these pointers?. When a new process is called with the
execve()
syscall or when you run a new process from the terminal with command-line arguments, the kernel copies out arguments and all eviroment variable strings and pointers to the end of the new programs stack. That is to the end of themain()
function stack. -
Therefore
argv
andenvp
pointers are contagious in memory in that they are next to each other as shown in the image below.
-
This means that when we pass no arguments to
pkexec
,argv[1]
now points to out first enviroment variable that is set. Let us assume the following scenarion:- We add an environment variable called FOO by passing it to
execve()
. - When calling
pkexec
with not command-line arguments as above, the code snippet will allocateargv[1]
that is now FOO dynamically on the heap usingg_strdup()
and the address returned to the(path)
pointer. - It will then check if
path[0]
begins with/
to determine if its an absolute path or not which in this case it doesn’t . - Since FOO does not contain
/
it will executeg_find_program_in_path()
and the value returned tos
will the absolute path ofvalue
and this will overwriteargv[1]
which is nowenvp[0]
(our environment variable) OOBW.
- We add an environment variable called FOO by passing it to
Exploitation #
How can this be exploited to gain root? According to the Qualys Security team,
Since we control envp[0], that is an environment variable using the OOBW
vulnerability, we can write anything to argv[1]
(i.e envp[0]). Therefore we need to
look at some of the dangerous enviroment variables that we can exploit.
(Note that after the out-of-bounds write above, the process clears out its enviroment variables.)
LD_PRELOAD
This is an enviroment variable that specifies the first shared library to load. We can therefore use the OOB-write to write this into our env variables but unfortuanetly, (ld.so) clears these types of variables out when running suid processes and before calling the main() function during execution.
GCONV_PATH
This is an enviroment variable somewhat similar to LD_LIBARY_PATH in functionality and this is part of GLib that is the GNOME Library. GLib is a library that is build on top of the C standard library and provides functions like error logging,authentication and PATH lookups.
Since certain functions return messages to users, encoding and decoding to and fro different formats must be considered and this is where GCONV_PATH comes into play.
When a process encounters e.g “CHARSET=UTF-8” enviroment variable, GLib loads $GCONV_PATH/gconv-modules or when this variable is not set the lookup is done from ("/usr/lib/gconv/gconv-modules"). This is a file that contains something like “BS_4730// UTF-8 ISO646 2” and this is used to define a module located at “ISO646.so” that will allow “BS_4730” to be re-encoded as “UTF-8”. (this is just a simple example).
When we control the
GCONV_PATH
, we can therefore cause thisSUID
process to run with full root privilleges by loading a shared library file that we control in the.so
file.
Proof Of Concept #
Below I have provided a snippet of my POC and the full code can be found at my github page pwnkit.c there are also other different POC payloads out there with the same functionality so be sure to check them out.
int main(int argc, char **argv){
char *pkexec_envp[] = {"tmp", "CHARSET=xxx", "PATH=GCONV_PATH=."};
char *pkexec_args[] = {NULL};
mkdir("GCONV_PATH=.", 0755);
open("GCONV_PATH=./tmp", O_CREAT, 0755);
mkdir("tmp", 07555);
open("tmp/gconv-modules", O_WRONLY, 0755);
execve("/usr/bin/pkexec", pkexec_args, pkexec_envp);
}
The above snippet works in the following way
-
Since we provided no arguments when calling pkexec via the
execve()
syscall,argv[1]
now becomes whatever we loaded in our enviroment variable that is tmp. -
Since tmp does not start with
/
, the process will look for its absolute path and will check from the paths provided by thePATH
enviroment variable. -
Since PATH variable contains our spoofed directory
GCONV_PATH=.
the value that will be returned byg_find_program_in_path()
will beGCONV_PATH=./tmp
as the absolute path. -
GCONV_PATH=./tmp
is a valid environment variable therefore using the oob-write we have an arbitrary write intoenvp[0] = argv[1] = "GCONV_PATH=./tmp"
, since we also included aCHARSET=xxx
, GLib now comes into play and looks forGCONV_PATH
from the enviroment variables that contains./tmp
as its value. -
It will then go to out
./tmp
directory to look forgconv-modules
and based on the contents, it will load a library from “./tmp” that we control and when its loaded this will lead to our privilege escalation. -
Basically the attack is based on the introduction of an insecure environment variable via the OOBW.
Mitigations #
- Update pkexec to the latest version.
- Remove the SUID bit in the executable.