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