Apr 25, 2017

NDAY-2017-0105: Elevation of Privilege Vulnerability in MSM Thermal Driver

zLabs
Share this blog
Following our announcement on the  N-Days Exploit Acquisition Program for smartphones, we are delighted to share the first couple of submissions. We received many submissions and we’re in the process of sharing them with ZHA followed by a public disclosure. We plan to release additional EOPs, RCEs and Infoleaks purchased through Zimperium N-Days EAP in the next few months. If you have a mobile N-Day exploit that you would like to monetize, or would like to practice on exploitation and get paid for it – check out the submission guidelines in our N-Days Exploit Acquisition Program announcement. We encourage all partners of ZHA to share exploit submissions for better collaboration between all those involved in making our mobile devices safer.

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 bulletinhttps://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)

Vulnerability Details

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.

Exploitation

  1. Set cluster_id to 213149, so we can set the value of wan_ioctl_cdev->ops from 0x ffffffc001aa0e30 to 0x00000000 01aa0e30. 0x0000000001aa0e30  is a user space address.
  2. we can set ptmx_cdev->ops to a fake ops which can be controlled in userland. Then get arbitrary kernel read & write by rop.

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