As we can see, the program iterates through the argv elements (line 534). By the time we get to line 610, the for loop has already terminated, and n is now equal to argc-1, which means that argv[n] points to the last argument passed – the target program path to be executed by pkexec. The program now checks if the given target program’s path is an absolute path, which starts with a slash (line 629). If not, it calls the g_find_program_in_path() function to find the absolute path of it (line 632). argv[n] is then modified to hold the now absolute path of the target program.
That is all. This is an expected behaviour of the main() function. But what if we call pkexec with zero arguments? And by zero arguments, I mean without even the first argument that is the pkexec path itself?
How would the above code look now?
The main() function is called, with argc being 0 and argv being empty, that is, containing only a NULL pointer (line 435). The for loop initializes n with a value of 1, and an end condition that n should be less than argc (which is, in our case, 0). This condition of course is not met, as n==1 and can’t be less than 0, which means the loop immediately terminates, which leaves n with a value of 1.
And now the interesting part begins:
Line 610 copies argv[n] to path. argv[n] of course exceeds the array’s length (which is empty), which means the code reads beyond the bounds of the array – an out-of-bounds read.
Moving on, line 632 calls the g_find_program_in_path() function, and tries to find the absolute path of the program name in path, which by now is unknown to us, as it was fetched from a value read out-of-bounds. Suppose there exists a file with the same name as path’s value, its absolute path will now be written back to argv[n] – again accessing the argv array beyond its bounds – which triggers an out-of-bounds write (line 639).
At the end of this flow, a memory location outside of the argv array, which could possibly point to a string which is a file name, is overwritten with the absolute path of the file.
Well, OK. An out-of-bounds read and write, what benefit does it have? It’s not as if we can control the out-of-bounds memory location which is read from and written to… Or can we?
Using our Call-Stack
For those of you who read the background section – your patience now pays off.
When we run the pkexec command, we can pass it the argument list parameters, argv and argc, and also the environment list, envp. Note that although the main() function of pkexec does not use the envp argument, it is still passed to it and stored in the function’s available memory.
As described earlier, the main() function, like every other function, can access its arguments and variables thanks to the call stack, which stores them in an orderly fashion. The argv and envp arrays are stored alongside each other, as seen below: