Thursday, 6 September 2018

Linux Kernel Infoleaks

Here are 6 Linux kernel local infoleaks.

InfoSect is available for engagements in code review. Please look at http://infosectcbr.com.au/consulting or check out some of our public code review videos on http://youtube.com/c/InfoSect. Or, check out our code review training at http://infosectcbr.com.au/training.

1 ==========================


/usr/src/linux-source-4.14/drivers/net/appletalk/ipddp.h

struct ipddp_route
{
        struct net_device *dev;             /* Carrier device */
        __be32 ip;                       /* IP address */
        struct atalk_addr at;              /* Gateway appletalk address */
        int flags;
        struct ipddp_route *next;
};


/usr/src/linux-source-4.14/drivers/net/appletalk/ipddp.c

static struct ipddp_route* __ipddp_find_route(struct ipddp_route *rt)
{
        struct ipddp_route *f;

        for(f = ipddp_route_list; f != NULL; f = f->next)
        {
                if(f->ip == rt->ip &&
                   f->at.s_net == rt->at.s_net &&
                   f->at.s_node == rt->at.s_node)
                        return f;
        }

        return NULL;
}
...

                case SIOCFINDIPDDPRT:
                        spin_lock_bh(&ipddp_route_lock);
                        rp = __ipddp_find_route(&rcp);
                        if (rp)
                                memcpy(&rcp2, rp, sizeof(rcp2));
                        spin_unlock_bh(&ipddp_route_lock);

                        if (rp) {
                                if (copy_to_user(rt, &rcp2,
                                                 sizeof(struct ipddp_route)))
                                        return -EFAULT;
                                return 0;
                        } else
                                return -ENOENT;

++ in the ipddp_route struct, there are pointers next and dev which get
++ leaked here.

2 ===================================================

struct snd_ctl_elem_list {
        unsigned int offset;            /* W: first element ID to get */
        unsigned int space;             /* W: count of element IDs to get */
        unsigned int used;              /* R: count of element IDs set */
        unsigned int count;             /* R: count of all elements */
        struct snd_ctl_elem_id __user *pids; /* R: IDs */
        unsigned char reserved[50];
};
+++ note reserved, pids

/usr/src/linux-source-4.14/sound/core/control.c

static int snd_ctl_elem_list(struct snd_card *card,
                             struct snd_ctl_elem_list __user *_list)
{
        struct snd_ctl_elem_list list;
        struct snd_kcontrol *kctl;
        struct snd_ctl_elem_id id;
        unsigned int offset, space, jidx;
        int err = 0;
...

               list_for_each_entry(kctl, &card->controls, list) {
                        if (offset >= kctl->count) {
                                offset -= kctl->count;
                                continue;
                        }
                        for (jidx = offset; jidx < kctl->count; jidx++) {
                                snd_ctl_build_ioff(&id, kctl, jidx);
                                if (copy_to_user(list.pids + list.used, &id,
                                                 sizeof(id))) {
                                        err = -EFAULT;
                                        goto out;
                                }
                                list.used++;
                                if (!--space)
                                        goto out;
                        }
                        offset = 0;
                }
        }
 out:
        up_read(&card->controls_rwsem);
        if (!err && copy_to_user(_list, &list, sizeof(list)))
                err = -EFAULT;
+++ copying list back to userspace leaks a pointer at least


static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg
)
{
        struct snd_ctl_file *ctl;
        struct snd_card *card;
        struct snd_kctl_ioctl *p;
        void __user *argp = (void __user *)arg;
        int __user *ip = argp;
        int err;

        ctl = file->private_data;
        card = ctl->card;
        if (snd_BUG_ON(!card))
                return -ENXIO;
        switch (cmd) {
        case SNDRV_CTL_IOCTL_PVERSION:
                return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
        case SNDRV_CTL_IOCTL_CARD_INFO:
                return snd_ctl_card_info(card, ctl, cmd, argp);
        case SNDRV_CTL_IOCTL_ELEM_LIST:
                return snd_ctl_elem_list(card, argp);

3 ======================================

struct snd_emu10k1_fx8010_info {
        unsigned int internal_tram_size;        /* in samples */
        unsigned int external_tram_size;        /* in samples */
        char fxbus_names[16][32];               /* names of FXBUSes */
        char extin_names[16][32];               /* names of external inputs */
        char extout_names[32][32];              /* names of external outputs */
        unsigned int gpr_controls;              /* count of GPR controls */
};
+++ note the strings/names are fixed lengths of 32

/usr/src/linux-source-4.14/sound/pci/emu10k1/emufx.c

static int snd_emu10k1_fx8010_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg)
{
...
        switch (cmd) {
        case SNDRV_EMU10K1_IOCTL_PVERSION:
                emu->support_tlv = 1;
                return put_user(SNDRV_EMU10K1_VERSION, (int __user *)argp);
        case SNDRV_EMU10K1_IOCTL_INFO:
                info = kmalloc(sizeof(*info), GFP_KERNEL);
+++ note kmalloc
                if (!info)
                        return -ENOMEM;
                snd_emu10k1_fx8010_info(emu, info);
                if (copy_to_user(argp, info, sizeof(*info))) {
                        kfree(info);
                        return -EFAULT;
                }
                kfree(info);
                return 0;

...

static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu,
                                   struct snd_emu10k1_fx8010_info *info)
{
        char **fxbus, **extin, **extout;
        unsigned short fxbus_mask, extin_mask, extout_mask;
        int res;

        info->internal_tram_size = emu->fx8010.itram_size;
        info->external_tram_size = emu->fx8010.etram_pages.bytes / 2;
        fxbus = fxbuses;
        extin = emu->audigy ? audigy_ins : creative_ins;
        extout = emu->audigy ? audigy_outs : creative_outs;
        fxbus_mask = emu->fx8010.fxbus_mask;
        extin_mask = emu->fx8010.extin_mask;
        extout_mask = emu->fx8010.extout_mask;
        for (res = 0; res < 16; res++, fxbus++, extin++, extout++) {
                copy_string(info->fxbus_names[res], fxbus_mask & (1 << res) ? *fxbus : NULL, "FXBUS", res);
                copy_string(info->extin_names[res], extin_mask & (1 << res) ? *extin : NULL, "Unused", res);
                copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
        }
+++ info leaks. the copy_strings partially fill the structure/strings which
+++ are 32 bytes. the remainder of the string is uninitialized (from kmalloc)

...
static void copy_string(char *dst, char *src, char *null, int idx)
{
        if (src == NULL)
                sprintf(dst, "%s %02X", null, idx);
        else
                strcpy(dst, src);
}




4 ====================================

struct inquiry_data {
        bdaddr_t        bdaddr;
        __u8            pscan_rep_mode;
        __u8            pscan_period_mode;
        __u8            pscan_mode;
        __u8            dev_class[3];
        __le16          clock_offset;
        __s8            rssi;
        __u8            ssp_mode;
};
a

/usr/src/linux-source-4.14/net/bluetooth/hci_core.c

static int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf)
{
        struct discovery_state *cache = &hdev->discovery;
        struct inquiry_info *info = (struct inquiry_info *) buf;
        struct inquiry_entry *e;
        int copied = 0;

        list_for_each_entry(e, &cache->all, all) {
                struct inquiry_data *data = &e->data;

                if (copied >= num)
                        break;

                bacpy(&info->bdaddr, &data->bdaddr);
                info->pscan_rep_mode    = data->pscan_rep_mode;
                info->pscan_period_mode = data->pscan_period_mode;
                info->pscan_mode        = data->pscan_mode;
                memcpy(info->dev_class, data->dev_class, 3);
                info->clock_offset      = data->clock_offset;
+++ note rssi and ssp_mode fields are not filled
+++ because this is from a kmalloc, it is left uninitialized. hence an infoleak

                info++;
                copied++;
        }

...
       /* cache_dump can't sleep. Therefore we allocate temp buffer and then
         * copy it to the user space.
         */
        buf = kmalloc(sizeof(struct inquiry_info) * max_rsp, GFP_KERNEL);
+++ note kmalloc
        if (!buf) {
                err = -ENOMEM;
                goto done;
        }

        hci_dev_lock(hdev);
        ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf);
        hci_dev_unlock(hdev);

        BT_DBG("num_rsp %d", ir.num_rsp);

        if (!copy_to_user(ptr, &ir, sizeof(ir))) {
                ptr += sizeof(ir);
                if (copy_to_user(ptr, buf, sizeof(struct inquiry_info) *
                                 ir.num_rsp))
                        err = -EFAULT;
        } else
                err = -EFAULT;

        kfree(buf);

5 ========================================================

/usr/src/linux-source-4.14/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c

        buf = vmalloc(mr->buffer_size);
        if (!buf) {
                DBG("vmalloc failed\n");
                return -ENOMEM;
        }

        r = display->driver->memory_read(display, buf, mr->buffer_size,
                        mr->x, mr->y, mr->w, mr->h);
+++ r returns the amount of memory read. can be less than buffer_size

        if (r > 0) {
                if (copy_to_user(mr->buffer, buf, mr->buffer_size))
+++ copies the entire buffer
+++ hence, an infoleak from unitialised memory from vmalloc
                       r = -EFAULT;
        }

        vfree(buf);

6 ==============================================

/usr/src/linux-source-4.14/arch/ia64/sn/kernel/sn2/sn_hwperf.c
a
static long sn_hwperf_ioctl(struct file *fp, u32 op, unsigned long arg)
{
        struct sn_hwperf_ioctl_args a;
...
        r = copy_from_user(&a, (const void __user *)arg,
                sizeof(struct sn_hwperf_ioctl_args));
        if (r != 0) {
                r = -EFAULT;
                goto error;
        }
...
        if (a.ptr) {
                p = vmalloc(a.sz);
                if (!p) {
                        r = -ENOMEM;
                        goto error;
                }
        }

        if (op & SN_HWPERF_OP_MEM_COPYIN) {
                r = copy_from_user(p, (const void __user *)a.ptr, a.sz);
                if (r != 0) {
                        r = -EFAULT;
                        goto error;
                }
        }

...

        switch (op) {
        case SN_HWPERF_GET_CPU_INFO:
...
        case SN_HWPERF_GET_MMRS:
        case SN_HWPERF_SET_MMRS:
        case SN_HWPERF_OBJECT_DISTANCE:
                op_info.p = p;
                op_info.a = &a;
                op_info.v0 = &v0;
                op_info.op = op;
                r = sn_hwperf_op_cpu(&op_info);
                if (r) {
                        r = sn_hwperf_map_err(r);
                        a.v0 = v0;
                        goto error;
                }
                break;

        default:
                /* all other ops are a direct SAL call */
                r = ia64_sn_hwperf_op(sn_hwperf_master_nasid, op,
                              a.arg, a.sz, (u64) p, 0, 0, &v0);
                if (r) {
                        r = sn_hwperf_map_err(r);
                        goto error;
                }
                a.v0 = v0;
                break;
        }
+++ if any of the above cases don't fully overwrite the memory buffer from
+++ vmalloc, then we might have uninitialized memory and hence an infoleak

        if (op & SN_HWPERF_OP_MEM_COPYOUT) {
                r = copy_to_user((void __user *)a.ptr, p, a.sz);
                if (r != 0) {
                        r = -EFAULT;
                        goto error;
                }
        }

=======================================================

Exploiting the Lorex 2K Indoor Wifi at Pwn2Own Ireland

Introduction In October InfoSect participated in Pwn2Own Ireland 2024 and successfully exploited the Sonos Era 300 smart speaker and Lor...