作者:
小叮噹
·
2016/02/29 10:38
0x00 背景
眾所周知,現代作業系統為了安全和統籌硬體的原因,採用了一套非常複雜的管理記憶體的方式,並由此產生了實體地址,邏輯地址,虛擬地址等概念。這部分內容不負累述,簡單來說如下圖
kernel與使用者態程式擁有不同的邏輯地址空間,kernel所在的頁面擁有更高的許可權,使用者許可權是不可以隨意更改的,否則豈不是可以改掉自己的許可權,為所欲為。
不過這也不是完全密不透風的牆,核心提供了多種途徑供使用者態交流資料。其中如果需要在短時間內交換大量資料,並且有實時的要求,linux kernel 提供了一種簡單有效的方式:共享記憶體——mmap syscall。
原理也非常簡單,對映給kernel的物理頁面也同樣對映一份給使用者程式,並且修改掉許可權屬性。這樣的話分屬不同地址空間的兩塊記憶體實際上對應的是同一個物理頁面,一方修改資料,另一方也能夠實時看到變化。
一般的應用場景是在嵌入式裝置的外設中,比如要實施重新整理LED螢幕,實施記錄大量感測器資料,等。這需要開發人員在自己的驅動程式和使用者程式碼中同時實現mmap的邏輯才能夠實現。
首先,在自定義的驅動檔案中要提供這樣的介面函式:
核心函式remap_pfn_range()
會根據需求對映頁面進使用者程式。
詳見:http://www.makelinux.net/ldd3/chp-15-sect-2
隨後在使用者程式中簡單掉用這個自定義的mmap函式就可以建立起頁面對映,達到共享記憶體的目的了。
0x01 漏洞
越是簡單好用的,就越容易出現問題。
函式remap_pfn_range()
並不會檢查傳入的引數,它會完全按照需求的記憶體起始位置,所需長度,訪問許可權,去對映頁面進使用者態。所有這些檢查都需要自定義的驅動中實現的mmap函式去完成。
上面的截圖是官方文件中給出的例子,當然不能用這段程式碼直接用到產品中!
比如如下的這個產品實現:
https://github.com/rajamalw/galaxy-s5360/blob/master/modules/drivers/video/hantro/ldriver/kernel_26x/hx170dec.c
#!cpp
/*------------------------------------------------------------------------------
Function name : hx170dec_mmap
Description : mmap method
Return type : int
------------------------------------------------------------------------------*/
static int hx170dec_mmap(struct file *file, struct vm_area_struct *vma)
{
if (vma->vm_end - vma->vm_start >
((DEC_IO_SIZE + PAGE_SIZE - 1) & PAGE_MASK))
return -EINVAL;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */
if (remap_pfn_range(vma,
vma->vm_start,
(DEC_IO_BASE >> PAGE_SHIFT),
vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
pr_err("%s(): remap_pfn_range() failed\n", __FUNCTION__);
return -EINVAL;
}
return 0;
}
只是稍微對申請的長度做了檢查,其他的理所當然地相信了使用者程式碼。
造成的後果很嚴重:
我們再來看一個比較好的例子:
https://github.com/nirodg/android_device_huawei_hwp7/blob/master/kernel/huawei/hwp7/drivers/hik3/g1dec/hx170dec.c
#!cpp
static int hx170dec_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long start = vma->vm_start;
unsigned long size = vma->vm_end - vma->vm_start;
int retval = 0;
unsigned long pyhs_start = vma->vm_pgoff << PAGE_SHIFT;
unsigned long pyhs_end = pyhs_start + size;
if(!(pyhs_start >= hisi_reserved_codec_phymem//not codec memory
&& pyhs_end <= hisi_reserved_codec_phymem + HISI_MEM_CODEC_SIZE)
&& !(pyhs_start >= hx170dec_data.iobaseaddr//not io address
&& pyhs_end <= hx170dec_data.iobaseaddr + hx170dec_data.iosize)) {
printk(KERN_ERR "%s(%d) failed map:0x%lx-0x%lx\n", __FUNCTION__, __LINE__, pyhs_start, pyhs_end);
return -EFAULT;
}
/* make buffers write-thru cacheable */
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (remap_pfn_range(vma, start, vma->vm_pgoff, size, vma->vm_page_prot))
retval = -ENOBUFS;
return retval;
}
在呼叫remap_pfn_range
函式之前做了諸多檢查,限制了申請地址的起始和結束位置。
0x02 其他
android系統的整體安全架構是很繁複龐大的,上文中出現的漏洞如果發生在某個驅動中是不能單獨被利用提權的。因為其他應用沒有許可權去訪問這個有問題的驅動。但是如果這個驅動的訪問許可權再出錯,比如:
那麼連續的犯錯下來,對於攻擊者來說,就真的是666了。
0x03 結語
本文所討論的安全問題在某廠商的裝置中經常出現,並且時間跨度長達幾年,希望貴廠能夠提高安全意識,加班趕工的同時也注意下程式碼質量。
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!