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;
}
}
=======================================================
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;
}
}
=======================================================