如何驗證一個地址可否使用—— MmIsAddressValid函式分析

Ox9A82發表於2016-06-08

  又是一篇核心函式分析的博文,我個人覺得Windows的核心是最好的老師,當你想實現一個功能之前可以看看Windows核心是怎麼做的,說不定就有靈感呢:)

  首先看下官方的註釋說明:

/*++

Routine Description:

    For a given virtual address this function returns TRUE if no page fault
    will occur for a read operation on the address, FALSE otherwise.

    Note that after this routine was called, if appropriate locks are not
    held, a non-faulting address could fault.

Arguments:

    VirtualAddress - Supplies the virtual address to check.

Return Value:

    TRUE if no page fault would be generated reading the virtual address,
    FALSE otherwise.

Environment:

    Kernel mode.

--*/

  WDK文件中給出的功能描述是這樣的:The MmIsAddressValid routine checks whether a page fault will occur for a read or write operation at a given virtual address.根據描述來看這個函式的功能只是去檢查讀寫操作會不會觸發一個頁錯誤,但是作為一個常用函式,我們常常用這個函式來檢查地址合不合法,這次就在原始碼裡看下具體的流程,主要目的是搞清楚這個函式是怎麼判斷一個函式會不會觸發頁錯誤的。

 1 BOOLEAN
 2 MiIsAddressValid (
 3     IN PVOID VirtualAddress,
 4     IN LOGICAL UseForceIfPossible
 5     )
 6 {
 7     PMMPTE PointerPte;
 8 
 9 
10     //
11     // If the address is not canonical then return FALSE as the caller (which
12     // may be the kernel debugger) is not expecting to get an unimplemented
13     // address bit fault.
14     //
15 
16     if (MI_RESERVED_BITS_CANONICAL(VirtualAddress) == FALSE) {
17         return FALSE;
18     }
19 
20 
21 
22 
23     PointerPte = MiGetPdeAddress (VirtualAddress);
24     if (PointerPte->u.Hard.Valid == 0) {
25         return FALSE;
26     }
27 
28     if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
29         return TRUE;
30     }
31   
32     PointerPte = MiGetPteAddress (VirtualAddress);
33     if (PointerPte->u.Hard.Valid == 0) {
34         return FALSE;
35     }
36 
37     //
38     // Make sure we're not treating a page directory as a page table here for
39     // the case where the page directory is mapping a large page.  This is
40     // because the large page bit is valid in PDE formats, but reserved in
41     // PTE formats and will cause a trap.  A virtual address like c0200000 (on
42     // x86) triggers this case.
43     //
44 
45     if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
46         return FALSE;
47     }
48 
49     return TRUE;
50 }

程式碼出人意外的簡單,很明顯,這是利用了分頁機制去查詢。先查下頁目錄項是否為空,然後再看一下頁表項是否為空。至於28、29行應該是判斷是不是直接使用PDE作為一級表吧,但是現在應該沒有這麼用的吧。

 if (MI_PDE_MAPS_LARGE_PAGE (PointerPte))
{
        return TRUE;
 }

如上就是一個判斷。

MiGetPdeAddressMiGetPteAddress 其實是兩個宏,這個宏我們也可以拿來用。

#define MiGetPdeAddress(va)  \
    ((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PDI_SHIFT) << PTE_SHIFT) + PDE_BASE))
#define MiGetPteAddress(va) \
    ((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + PTE_BASE))
#define VIRTUAL_ADDRESS_BITS 48
#define VIRTUAL_ADDRESS_MASK ((((ULONG_PTR)1) << VIRTUAL_ADDRESS_BITS) - 1)

注意,每個程式都有自己的程式頁表和頁目錄但是核心在分配一個程式的地址空間時會把PD給複製一份,以便於訪問。

  由以上的分析可以看出並沒有所謂的驗證地址是否可讀寫的功能,我們有時候會把它和ProbeForRead(),ProbeForWrite()這兩個函式相混淆。這兩個函式才是來驗證地址是否可讀寫的函式,但是僅限於使用者地址空間的地址。從WDK中可以看到如下的描述:The ProbeForWrite routine checks that a user-mode buffer actually resides in the user-mode portion of the address space, is writable, and is correctly aligned.我們來看下這個函式的實現。

 1 VOID
 2 ProbeForWrite (
 3     __inout_bcount(Length) PVOID Address,
 4     __in SIZE_T Length,
 5     __in ULONG Alignment
 6     )
 7 
 8 /*++
 9 
10 Routine Description:
11 
12     This function probes a structure for write accessibility and ensures
13     correct alignment of the structure. If the structure is not accessible
14     or has incorrect alignment, then an exception is raised.
15 
16 Arguments:
17 
18     Address - Supplies a pointer to the structure to be probed.
19 
20     Length - Supplies the length of the structure.
21 
22     Alignment - Supplies the required alignment of the structure expressed
23         as the number of bytes in the primitive datatype (e.g., 1 for char,
24         2 for short, 4 for long, and 8 for quad).
25 
26 Return Value:
27 
28     None.
29 
30 --*/
31 
32 {
33 
34     ULONG_PTR EndAddress;
35     ULONG_PTR StartAddress;
36 
37 #define PageSize PAGE_SIZE
38 
39     //
40     // If the structure has zero length, then do not probe the structure for
41     // write accessibility or alignment.
42     //
43 
44     if (Length != 0) {
45 
46         //
47         // If the structure is not properly aligned, then raise a data
48         // misalignment exception.
49         //
50 
51         ASSERT((Alignment == 1) || (Alignment == 2) ||
52                (Alignment == 4) || (Alignment == 8) ||
53                (Alignment == 16));
54 
55         StartAddress = (ULONG_PTR)Address;
56         if ((StartAddress & (Alignment - 1)) == 0) {
57 
58             //
59             // Compute the ending address of the structure and probe for
60             // write accessibility.
61             //
62 
63             EndAddress = StartAddress + Length - 1;
64             if ((StartAddress <= EndAddress) &&
65                 (EndAddress < MM_USER_PROBE_ADDRESS)) {
66 
67                 //
68                 // N.B. Only the contents of the buffer may be probed.
69                 //      Therefore the starting byte is probed for the
70                 //      first page, and then the first byte in the page
71                 //      for each succeeding page.
72                 //
73                 // If this is a Wow64 process, then the native page is 4K, which
74                 // could be smaller than the native page size/
75                 //
76 
77                 EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize;
78                 do {
79                     *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;
80                     StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize;
81                 } while (StartAddress != EndAddress);
82 
83                 return;
84 
85             } else {
86                 ExRaiseAccessViolation();
87             }
88 
89         } else {
90             ExRaiseDatatypeMisalignment();
91         }
92     }
93 
94     return;
95 }
 1 VOID
 2 ProbeForRead(
 3     __in_bcount(Length) VOID *Address,
 4     __in SIZE_T Length,
 5     __in ULONG Alignment
 6     )
 7 
 8 /*++
 9 
10 Routine Description:
11 
12     This function probes a structure for read accessibility and ensures
13     correct alignment of the structure. If the structure is not accessible
14     or has incorrect alignment, then an exception is raised.
15 
16 Arguments:
17 
18     Address - Supplies a pointer to the structure to be probed.
19 
20     Length - Supplies the length of the structure.
21 
22     Alignment - Supplies the required alignment of the structure expressed
23         as the number of bytes in the primitive datatype (e.g., 1 for char,
24         2 for short, 4 for long, and 8 for quad).
25 
26 Return Value:
27 
28     None.
29 
30 --*/
31 
32 {
33 
34     PAGED_CODE();
35 
36     ASSERT((Alignment == 1) || (Alignment == 2) ||
37            (Alignment == 4) || (Alignment == 8) ||
38            (Alignment == 16));
39 
40     if (Length != 0) {
41         if (((ULONG_PTR)Address & (Alignment - 1)) != 0) {
42             ExRaiseDatatypeMisalignment();
43 
44         } else if ((((ULONG_PTR)Address + Length) > (ULONG_PTR)MM_USER_PROBE_ADDRESS) ||
45                    (((ULONG_PTR)Address + Length) < (ULONG_PTR)Address)) {
46 
47             *(volatile UCHAR * const)MM_USER_PROBE_ADDRESS = 0;
48         }
49     }
50 }

 以分頁來管理記憶體,以頁為單位,如果一個記憶體頁的第一個位元組可寫,那麼整個記憶體頁就可寫,所以就驗證頁的一個位元組就可以了。(xxx & ~(PageSize - 1)) + PageSize就是以頁為單位移動。這裡也可以看到位元組對齊Alignment僅僅是起到了一個驗證的作用,而讀寫驗證也只是一個簡單的指標操作*(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;

相關文章