Chapter 23: Kernel Overflows

Kernel Vulnerability Types

Many functions and bad coding practices exist that can lead to exploitable conditions in kernel land. We will go over these weaknesses and provide examples from various kernels , giving hints about what to look for when conducting audits . Dawson Engler's excellent paper and audit, "Using Programmer-Written Compiler Extensions to Catch Security Holes" ( www. stanford .edu/~engler/sp-ieee-02.ps ), provides perfect examples of what to look for while hunting for kernel-land vulnerabilities.

Although many possible bad coding practices have been identified specific to kernel-level vulnerabilities, some potentially dangerous functions have still been missed even under rigorous code audits. OpenBSD's kernel stack overflow, presented in this chapter, falls into the category of a not-commonly- audited function. Kernel land contains potentially dangerous functions that can be the source of overflows, similar to the user -land APIs strcpy and memcpy .

These functions and various logic mistakes can be abstracted as follows :

  • signed integer problems

    • buf[user_controlled_index] vulnerabilities

    • copyin/copyout functions

  • integer overflows

    • malloc / free functions

    • copyin / copyout functions

    • integer arithmetic problems

  • buffer overflows (stack/heap)

    • copyin and several other similar functions

    • read/write from v-node to kernel buffer

  • format string overflows

    • log , print functions

  • design errors

    • modload , ptrace

Let's look at some recently disclosed kernel-level vulnerabilities and work through various exploitation problems with real-life examples. Two OpenBSD kernel overflows (presented in Phrack 60, Article 0x6), one FreeBSD kernel information leak, and a Solaris design error will be presented as recent case studies.

 2.1 - OpenBSD select() kernel stack buffer overflow sys_select(p, v, retval)         register struct proc *p;         void *v;         register_t *retval; {         register struct sys_select_args /* {                 syscallarg(int) nd;                 syscallarg(fd_set *) in;                 syscallarg(fd_set *) ou;                 syscallarg(fd_set *) ex;                 syscallarg(struct timeval *) tv;         } */ *uap = v;         fd_set bits[6], *pibits[3], *pobits[3];         struct timeval atv;         int s, ncoll, error = 0, timo;         u_int ni;     [1]     if (SCARG(uap, nd) > p->p_fd->fd_nfiles) {                 /* forgiving; slightly wrong */                 SCARG(uap, nd) = p->p_fd->fd_nfiles;         } [2]     ni = howmany(SCARG(uap, nd), NFDBITS) * sizeof(fd_mask); [3]     if (SCARG(uap, nd) > FD_SETSIZE) {             [deleted]     #define getbits(name, x)  [4]   if (SCARG(uap, name) && (error = copyin((caddr_t)SCARG(uap, name),              (caddr_t)pibits[x], ni)))                  goto done; [5]     getbits(in, 0);         getbits(ou, 1);         getbits(ex, 2); #undef  getbits          [deleted] 

In order to make sense out of the selected syscall code, we need to extract the SCARG macro from the header files.

 sys/systm.h:114 ... #if     BYTE_ORDER == BIG_ENDIAN #define SCARG(p, k)     ((p)->k.be.datum)       /* get arg from args  pointer */ #elif   BYTE_ORDER == LITTLE_ENDIAN #define SCARG(p, k)     ((p)->k.le.datum)       /* get arg from args  pointer */     sys/syscallarg.h: line 14     #define syscallarg(x)                                                            union {                                                                          register_t pad;                                                          struct { x datum; } le;                                                  struct {                                                                         int8_t pad[ (sizeof (register_t) < sizeof (x))                                   ? 0                                                                      : sizeof (register_t) - sizeof (x)];                             x datum;                                                         } be;                                                            } 

SCARG() is a macro that retrieves the members of the struct sys_XXX_args structures ( XXX representing the system call name), which are storage entities for system call related data. Access to the members of these structures is performed via SCARG() in order to preserve alignment along CPU register size boundaries, so that memory accesses will be faster and more efficient. The system call must declare incoming arguments as follows in order to make use of the SCARG() macro. The following declaration is for the incoming arguments structure of the select() system call.

 sys/syscallarg.h: line 404 struct sys_select_args { [6]     syscallarg(int) nd;         syscallarg(fd_set *) in;         syscallarg(fd_set *) ou;         syscallarg(fd_set *) ex;         syscallarg(struct timeval *) tv; }; 

This specific vulnerability can be described as an insufficient check on the nd argument (you can find the exact line of code labeled [6] , in the code example), which is used to calculate the length parameter for user-land-to-kernel-land copy operations.

Although there is a check [1] on the nd argument ( nd represents the highest numbered descriptor plus one in any of the fd_sets ), which is checked against the p->p_fd->fd_nfiles (the number of open descriptors that the process is holding). This check is inadequate. nd is declared as signed [6] , so it can be supplied as negative; therefore, the greater-than check will be evaded [1] . Eventually nd is used by the howmany() macro [2] in order to calculate the length argument for the copyin operation ni .

 #define howmany(x, y)   (((x)+((y)-1))/(y)) ni = ((nd + (NFDBITS-1)) / NFDBITS)  * sizeof(fd_mask); ni = ((nd + (32 - 1)) / 32) * 4 

Calculation of ni is followed by another check on the nd argument [3] .

This check is also passed, because OpenBSD developers consistently forget about the signedness checks on the nd argument. Check [3] is done to determine whether the space allocated on the stack is sufficient for the copyin operations following, and if not, then sufficient heap space will be allocated.

Given the inadequacy of the signed check, we'll pass check [3] and continue using stack space. Finally, the getbits() [4 , 5] macro is defined and called to retrieve user-supplied fd_sets ( readfds , writefds , exceptfds these arrays contain the descriptors to be tested for ready for reading , ready for writing, or have an exceptional condition pending ). Obviously if the nd argument is supplied as a negative integer, the copyin operation (within the getbits ) will overwrite chunks of kernel memory, which could lead to code execution if certain kernel overflow tricks are used.

Eventually, with all pieces tied together, this vulnerability translates into the following pseudo code.

 vuln_func(int user_number, char *user_buffer) { char stack_buf[1024];     if( user_number > sizeof(stack_buf) )        goto error;   copyin(stack_buf, user_buf, user_number); /* copyin is somewhat the kernel land equivalent of memcpy */     }     2.2 - OpenBSD setitimer() kernel memory overwrite     sys_setitimer(p, v, retval)         struct proc *p;         register void *v;         register_t *retval; {         register struct sys_setitimer_args /* { [1]             syscallarg(u_int) which;                 syscallarg(struct itimerval *) itv;                 syscallarg(struct itimerval *) oitv;         } */ *uap = v;         struct itimerval aitv;         register const struct itimerval *itvp;         int s, error;         int timo;     [2]     if (SCARG(uap, which) > ITIMER_PROF)                 return (EINVAL); [deleted]     [3]             p->p_stats->p_timer[SCARG(uap, which)] = aitv;         }         splx(s);         return (0); } 

This vulnerability can be categorized as a kernel-memory overwrite due to insufficient checks on a user-controlled index integer that references an entry in an array of kernel structure. The integer that represents the index was used to under-reference the structure, thus writing into arbitrary locations within the kernel memory. This was made possible due to a signedness vulnerability in validating the index against a fixed- sized integer (which represents the largest index number allowed).

This index number is the which [1] argument to the system call; this is falsely claimed as an unsigned integer in the comment text block (hint, the /* */ ) [1] . The which argument is actually declared as a signed integer in the sys/syscallargs.h line 369 (checked in OpenBSD 3.1), thus making it possible for user-land applications to supply a negative value, which will lead to evading the validation checks done by [2] . Eventually, the kernel will copy a user-supplied structure into kernel memory using the which argument as the index into a buffer of structures [3] . At this stage, a carefully calculated negative which integer makes it possible to write into the credential structure of the process or the user, thus elevating privileges.

This vulnerability can be translated into the following pseudo code to illustrate a possible vulnerable pattern in various kernels.

 vuln_func(int user_index, struct userdata *uptr) { if( user_index > FIXED_LIMIT )        goto error;     kbuf[user_index] = *uptr;     }     2.3 - FreeBSD accept() kernel memory infoleak     int accept(td, uap)         struct thread *td;         struct accept_args *uap; {     [1]     return (accept1(td, uap, 0)); }     static int accept1(td, uap, compat)         struct thread *td; [2]     register struct accept_args /* {                 int     s;                 caddr_t name;                 int     *anamelen;         } */ *uap;         int compat; {         struct filedesc *fdp;         struct file *nfp = NULL;         struct sockaddr *sa; [3]     int namelen, error, s;         struct socket *head, *so;         int fd;         u_int fflag;             mtx_lock(&Giant);         fdp = td->td_proc->p_fd;         if (uap->name) { [4]             error = copyin(uap->anamelen, &namelen, sizeof (namelen));                 if(error)                         goto done2;         } [deleted]         error = soaccept(so, &sa);     [deleted]         if (uap->name) {                 /* check sa_len before it is destroyed */ [5]             if (namelen > sa->sa_len)                         namelen = sa->sa_len; [deleted]     [6]             error = copyout(sa, uap->name, (u_int)namelen);     [deleted]     } 

The fact that FreeBSD accepts system call vulnerability is a signedness issue that leads to a kernel memory information leakage condition. The accept() system call is directly dispatched to the accept1() function [1] with only an additional zero argument. The arguments from user land are packed into the accept_args structure [2] which contains:

  • An integer that represents the socket

  • A pointer to a sockaddr structure

  • A pointer to signed integer that represents the size of the sockaddr structure

Initially [4] , the accept1() function copies the value of the user-supplied size argument into a variable called namelen [3] . It is important to note that this is a signed integer and can represent negative values. Subsequently the accept1() function performs an extensive number of socket-related operations to set the proper state of the socket. This places the socket in a waiting-for-new-connections state. Finally the soaccept() function fills out a new sockaddr structure with the address of the connecting entity [5] , which eventually will be copied out to user land.

The size of the new sockaddr structure is compared to the size of the user-supplied size argument [5] , ensuring that there is enough space in the user-land buffer to hold the structure. Unfortunately, this check is evaded, and attackers can supply a negative value for the namelen integer and bypass this bigger-than comparison. This evasion on the size check leads to having a large chunk of kernel memory copied to the user-land buffer.

This vulnerability can be translated into the following pseudo code to illustrate a potential vulnerable pattern in various kernels.

 struct userdata {     int len;     /* signed! */     char *data; };     vuln_func(struct userdata *uptr) {     struct kerneldata *kptr;     internal_func(kptr); /* fill-in kptr */     if( uptr->len > kptr->len )      uptr->len = kptr->len;     copyout(kptr, uptr->data, uptr->len);     }     Solaris priocntl() directory traversal     /*  * The priocntl system call.  */ long priocntlsys(int pc_version, procset_t *psp, int cmd, caddr_t arg) {  [deleted]             switch (cmd) { [1]     case PC_GETCID: ... [2]  if (copyin(arg, (caddr_t)&pcinfo, sizeof (pcinfo))) ...                 error = [3]                    scheduler_load(pcinfo.pc_clname,  &sclass[pcinfo.pc_cid]);  [deleted] }     int scheduler_load(char *clname, sclass_t *clp) {  [deleted] [4]                     if (modload("sched", clname) == -1)                                 return (EINVAL);                         rw_enter(clp->cl_lock, RW_READER);  [deleted] } 

The Solaris priocntl() vulnerability is a perfect example of the design error vulnerability genre . Without going into unnecessary detail, let's examine how this vulnerability is possible. priocntl is a system call that gives users control over the scheduling of light-weight processes (LWPs), which can mean either a single LWP of a process or the process itself. There are several supported scheduling classes available in a typical Solaris installation:

  • the real-time class

  • the time-sharing class

  • the fair-share class

  • the fixed-priority class

All these scheduling classes are implemented as dynamically loadable kernel modules. They are loaded by the prionctl system call based on user-land requests . This system call typically takes two arguments from user-land cmd and a pointer to a structure arg . The vulnerability resides in the PC_GETCID cmd type that is handled by the case statement [1] . The displacement of the cmd argument is followed by copying the user-supplied arg pointer into the relevant scheduling class-related structure [2] . The newly copied structure contains all information regarding the scheduling class, as we can see from this code fragment:

 typedef struct pcinfo {         id_t    pc_cid;                 /* class id */         char    pc_clname[PC_CLNMSZ];   /* class name */         int     pc_clinfo[PC_CLINFOSZ]; /* class information */ } pcinfo_t; 

The interesting piece of this particular structure is the pc_clname argument. This is the scheduling class' name as well as its relative pathname. If we want to use the scheduling class name myclass , the prionctl system call will search the /kernel/sched/ and /usr/kernel/sched/ directories for the mycall kernel module. If it finds it, the module will be loaded. All these steps are orchestrated by the [3] scheduler_load and [4] modload functions. As previously discussed, the scheduler class name is a relative pathname; it is appended to the predefined pathname in which all kernel modules reside. When this appending behavior exists without a check for directory traversal conditions, it is possible to supply a class name with ../ in its name. Now, we can take advantage of this vulnerability and load arbitrary kernel modules from various locations on the *file system. For example, a pc_clname argument such as ../../tmp/mymod will be translated into /kernel/sched/../../tmp/mymod , thus allowing a malicious kernel module to be loaded into memory.

Although several other interesting design errors were identified in various kernels ( ptrace , vfork , and so on), we believe that this particular flaw is an excellent example of a kernel vulnerability. At time of writing, this vulnerability could be located and exploited in a similar fashion in all current versions of the Solaris operating system. The priocntl bug is an important discovery. It leads us to look into the modload interface, which allows us to discover additional exploitable kernel-level weaknesses. We recommend that you look into previously found kernel vulnerabilities and try to translate them into pseudo code, or some sort of bug primitive, which will eventually help you identify and exploit your own 0day .



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net