zNID: NDAY-2017-0105
CVE: CVE-2016-2411
Type: Elevation of Privileges
Platform: Android 6.0.1
Device type: Nexus 5x
Zimperium protection: Detected the exploit without an update. Zimperium partners and customers do not need to take any action to detect this exploit on all affected devices.
Android bulletin: https://source.android.com/security/bulletin/2016-04-02.html
Public release date: 25th of April, 2017
Credit: Jianqiang Zhao (@jianqiangzhao) and pjf (weibo.com/jfpan) of IceSword Lab, Qihoo 360
Download Exploit (password zimperium_ndays)
In the function msm_thermal_process_ voltage_table_req, cluster_id is passed from userland but not validated. It can lead to heap overflow. It requires root to trigger, however it can be used as privilege escalation to disable SELinux.
static long msm_thermal_process_voltage_table_req( |
struct msm_thermal_ioctl *query, |
unsigned long *arg) |
{ |
long ret = 0; |
uint32_t table_idx = 0, idx = 0; |
uint32_t cluster_id = query->voltage.cluster_num; |
struct voltage_plan_arg *voltage = &(query->voltage); |
if (!voltage_table_ptr[cluster_id]) { |
if (!freq_table_len[cluster_id]) { |
ret = msm_thermal_get_freq_plan_size(cluster_id, |
&freq_table_len[cluster_id]); |
if (ret) { |
pr_err( |
"%s: Cluster%d freq table len err:%ld\n", |
KBUILD_MODNAME, cluster_id, ret); |
goto process_volt_exit; |
} |
if (!freq_table_len[cluster_id]) { |
pr_err("%s: Cluster%d freq table empty\n", |
KBUILD_MODNAME, cluster_id); |
ret = -EAGAIN; |
goto process_volt_exit; |
} |
} |
voltage_table_ptr[cluster_id] = kzalloc( |
sizeof(uint32_t) * |
freq_table_len[cluster_id], GFP_KERNEL); |
if (!voltage_table_ptr[cluster_id]) { |
pr_err("%s: memory alloc failed\n", |
KBUILD_MODNAME); |
ret = -ENOMEM; |
goto process_volt_exit; |
} |
ret = msm_thermal_get_cluster_voltage_plan(cluster_id, |
voltage_table_ptr[cluster_id]); |
if (ret) { |
pr_err("%s: Error getting voltage table. err:%ld\n", |
KBUILD_MODNAME, ret); |
kfree(voltage_table_ptr[cluster_id]); |
voltage_table_ptr[cluster_id] = NULL; |
goto process_volt_exit; |
} |
} |
if (!voltage->voltage_table_len) { |
voltage->voltage_table_len = freq_table_len[cluster_id]; |
goto copy_and_return; |
} |
voltage_table_set[cluster_id] = freq_table_len[cluster_id] |
/ MSM_IOCTL_FREQ_SIZE; |
if (freq_table_len[cluster_id] % MSM_IOCTL_FREQ_SIZE) |
voltage_table_set[cluster_id]++; |
if (voltage->set_idx >= voltage_table_set[cluster_id]) { |
pr_err("%s: Invalid voltage table set%d for cluster%d\n", |
KBUILD_MODNAME, voltage->set_idx, |
cluster_id); |
ret = -EINVAL; |
goto process_volt_exit; |
} |
table_idx = MSM_IOCTL_FREQ_SIZE * voltage->set_idx; |
for (; table_idx < freq_table_len[cluster_id] |
&& idx < MSM_IOCTL_FREQ_SIZE; idx++, table_idx++) { |
voltage->voltage_table[idx] = |
voltage_table_ptr[cluster_id][table_idx]; |
} |
copy_and_return: |
ret = copy_to_user((void __user *)(*arg), query, |
sizeof(struct msm_thermal_ioctl)); |
if (ret) { |
pr_err("%s: copy_to_user error:%ld.\n", KBUILD_MODNAME, ret); |
goto process_volt_exit; |
} |
process_volt_exit: |
return ret; |
} |