Linux內(nèi)核如何判斷地址是否位于用戶空間?
一、 問題描述
access_ok函數(shù)是什么原理?

問題
二、問題分析
我們?cè)趦?nèi)核空間和用戶空間進(jìn)行數(shù)據(jù)拷貝的時(shí)候必須判斷用戶空間地址是否合法。主要通過偶函數(shù)access_ok來判斷。
1. Linux用戶空間與內(nèi)核地址空間
Linux 操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,兩者不能簡(jiǎn)單地使用指針傳遞數(shù)據(jù),因?yàn)長(zhǎng)inux使用的虛擬內(nèi)存機(jī)制,用戶空間的數(shù)據(jù)可能被換出,當(dāng)內(nèi)核空間使用用戶空間指針時(shí),對(duì)應(yīng)的數(shù)據(jù)可能不在內(nèi)存中。
通常32位Linux內(nèi)核地址空間劃分0~3G為用戶空間,3~4G為內(nèi)核空間。注意這里是32位內(nèi)核地址空間劃分,64位內(nèi)核地址空間劃分是不同的。

- 進(jìn)程尋址空間0~4G
- 進(jìn)程在用戶態(tài)只能訪問0~3G,只有進(jìn)入內(nèi)核態(tài)才能訪問3G~4G
- 進(jìn)程通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)
- 每個(gè)進(jìn)程虛擬空間的3G~4G部分是相同的
- 進(jìn)程從用戶態(tài)進(jìn)入內(nèi)核態(tài)不會(huì)引起CR3的改變但會(huì)引起堆棧的改變
2. access_ok詳解
原型:
- access_ok ( type,addr,size);
功能:
- access_ok — 檢查用戶空間指針是否有效 注意,根據(jù)體系結(jié)構(gòu)的不同,這個(gè)函數(shù)可能只是檢查指針是否在用戶空間范圍內(nèi),在調(diào)用這個(gè)函數(shù)之后,內(nèi)存訪問函數(shù)可能仍然返回 -EFAULT
參數(shù)說明:
- typeType of access: VERIFY_READ or VERIFY_WRITE. 請(qǐng)注意,VERIFY_WRITE是VERIFY_READ的超集——如果寫入一個(gè)塊是安全的,那么從它讀取總是安全的。addr要檢查的塊的開始的用戶空間指針size要檢查的塊的大小
返回值:
- 此函數(shù)檢查用戶空間中的內(nèi)存塊是否可用。如果可用,則返回真(非0值),否則返回假 (0) 。
2. 源碼分析
- #define access_ok(type, addr, size) (__range_ok(addr, size) == 0)
- /* We use 33-bit arithmetic here... */
- #define __range_ok(addr, size) ({ \
- unsigned long flag, roksum; \
- __chk_user_ptr(addr); \
- __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
- : "=&r" (flag), "=&r" (roksum) \
- : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
- : "cc"); \
- flag; })
- static inline void __chk_user_ptr(const volatile void *p, size_t size)
- {
- assert(p >= __user_addr_min && p + size <= __user_addr_max);
- }
其中__range_ok詳解如下:參數(shù)對(duì)應(yīng):
- flag -------- %0
- roksum -------- %1
- addr -------- %2
- size -------- %3
匯編指令詳解
- adds %1, %2, %3
等價(jià)于:
- rosum = addr + size
這個(gè)操作會(huì)影響狀態(tài)位(目的是影響是進(jìn)位標(biāo)志C)。
以下的兩個(gè)指令都帶有條件CC,也就是當(dāng)C=0的時(shí)候才執(zhí)行;如果上面的加法指令進(jìn)位了(C=1),則以下的指令都不執(zhí)行,flag就為初始值current_thread_info()->addr_limit(非0),并返回。如果沒有進(jìn)位(C=0),就執(zhí)行下面的指令:
- sbcccs %1, %1, %0
該指令等價(jià)于
- rosum = rosum - flag - 1
也就是(addr + size) - (current_thread_info()->addr_limit) - 1,操作影響符號(hào)位。.
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,則C=1 如果(addr + size) < (current_thread_info()->addr_limit) - 1,則C=0 當(dāng)C=0的時(shí)候執(zhí)行以下指令,否則跳過(flag非零)。
- movcc %0, #0
等價(jià)于
- flag = 0,給flag賦值0。
綜上所述:__range_ok宏等價(jià)于:
- 如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值
- 如果(addr + size) < (current_thread_info()->addr_limit),返回零
而access_ok就是檢驗(yàn)將要操作的用戶空間的地址范圍是否在當(dāng)前進(jìn)程的用戶地址空間限制中。這個(gè)宏的功能很簡(jiǎn)單,完全可以用C實(shí)現(xiàn),不是必須使用匯編。 由于這兩個(gè)函數(shù)使用頻繁,就使用匯編來實(shí)現(xiàn)部分功能來增加效率。
3. 使用實(shí)例
我們?cè)趦?nèi)核拷貝數(shù)據(jù)到用戶空間或者從用戶空間拷貝數(shù)據(jù)到內(nèi)核空間,都需要判斷用戶空間地址是否在用戶空間。
- static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
- {
- if (access_ok(VERIFY_READ, from, n))
- n = __copy_from_user(to, from, n);
- else /* security hole - plug it */
- memset(to, 0, n);
- return n;
- }
- static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
- {
- if (access_ok(VERIFY_WRITE, to, n))
- n = __copy_to_user(to, from, n);
- return n;
- }