보통 함수를 작성할 때 다음과 같이 인자로 받은 포인터를 검증한다.

  1. BOOL    SampleFunc(LPTSTR   lpTargetBuffer, DWORD   dwBufferSize)  
  2. {  
  3.     if (lpTargetBuffer == NULL)    // 인자 검증!!  
  4.         return FALSE;  
  5.     // 여기서부터 작업 시작!!  
  6.     // ...  
  7. }  

한 후배가 물었다.
 "만약 NULL 이 아닌 잘못된 포인터가 넘어온 경우는 어떻게 체크하죠?"
당연한 듯이 대답해주었다.
 "그걸 어떻게 체크하냐? 어쩔 수 없는 거지!!"

퇴근하면서 생각해 보니... 어쩔수 없지는... 않을 것 같아서 한번 만들어 보았다. ^^

 

  1. /*  
  2. IsWritableMemory : 주어진 메모리 주소의 상태가 쓰기가능인지 체크하는 함수 
  3.     Param :  
  4.         LPVOID  pMemoryAddr : 검사하고자 하는 메모리의 주소 
  5.     Return Value :  
  6.         ERROR_SUCCESS : 인자로 주어진 주소의 메모리가 Read/Write 가능한 경우 
  7.         기타 값 : Read/Write 가능한 메모리가 아니면 해당 주소의 Protect Mode 를  
  8.                     나타내는 0이 아닌 값을 리턴함. 
  9.                     http://msdn.microsoft.com/en-us/library/aa915370.aspx 참조 
  10. */  
  11. INT IsWritableMemory(LPVOID pMemoryAddr)  
  12. {  
  13.     MEMORY_BASIC_INFORMATION    MemInfo = {0,};  
  14.     SIZE_T  nResult = 0;  
  15.   
  16.     nResult = VirtualQuery (pMemoryAddr, &MemInfo, sizeof(MemInfo));  
  17.   
  18.     if (nResult == 0) // 커널 영역인 경우 VirtualQuery 자체가 Fail함.  
  19.     {  
  20.         return -1;  
  21.     } else if (MemInfo.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE) )  
  22.     {  
  23.         return  ERROR_SUCCESS;    
  24.     } else  
  25.     {  
  26.         return  MemInfo.Protect;  
  27.     }  
  28. }  
  29.   
  30.   
  31.   
  32. /*  
  33. IsReadableMemory : 주어진 메모리 주소의 상태가 참조(Read)가능인지 체크하는 함수 
  34.     Param :  
  35.         LPVOID  pMemoryAddr : 검사하고자 하는 메모리의 주소 
  36.     Return Value :  
  37.         ERROR_SUCCESS : 인자로 주어진 주소의 메모리가 Read 가능한 경우 
  38.         기타 값 : Read 가능한 메모리가 아니면 해당 주소의 State 를  
  39.                     나타내는 0이 아닌 값을 리턴함. 
  40.                     http://msdn.microsoft.com/en-us/library/aa915370.aspx 참조 
  41. */  
  42. INT IsReadableMemory(LPVOID pMemoryAddr)  
  43. {  
  44.     MEMORY_BASIC_INFORMATION    MemInfo = {0,};  
  45.     SIZE_T  nResult = 0;  
  46.   
  47.     nResult = VirtualQuery (pMemoryAddr, &MemInfo, sizeof(MemInfo));  
  48.   
  49.     if (nResult == 0) // 커널 영역인 경우 VirtualQuery 자체가 Fail함.  
  50.     {  
  51.         return -1;  
  52.     } else if (MemInfo.State & MEM_COMMIT)  
  53.     {  
  54.         return  ERROR_SUCCESS;    
  55.     } else  
  56.     {  
  57.         return  MemInfo.State;  
  58.     }  
  59. }  

사용법은 다음과 같다.

  1. BOOL    SampleFunc(LPTSTR   lpTargetBuffer, DWORD   dwBufferSize)  
  2. {  
  3.     if (IsWritableMemory(lpTargetBuffer) != ERROR_SUCCESS)  
  4.     {  
  5.         return FALSE;   // 주어진 버퍼가 유효한 메모리가 아닐 경우  
  6.     }  
  7.   
  8.     lstrcpyn(lpTargetBuffer, L"Some String", dwBufferSize);  
  9.   
  10.     return TRUE;  
  11. }  
  12.   
  13. int _tmain(int argc, _TCHAR* argv[])  
  14. {  
  15.     TCHAR   szSafeBuffer[1024] = {0,};  // 정상적인 버퍼  
  16.     LPTSTR  lpTest;     // 초기화 되지 않은 포인터!!  
  17.   
  18.     BOOL    bResult = FALSE;  
  19.   
  20.     bResult = SampleFunc(szSafeBuffer, _countof(szSafeBuffer));  
  21.   
  22.     if (bResult != TRUE)  
  23.     {  
  24.         wprintf(L"Fail to Copy String\r\n");  
  25.         return FALSE;  
  26.     }  
  27.     wprintf(L"Result String : %s\r\n", szSafeBuffer);  
  28.   
  29.     bResult = SampleFunc(lpTest, sizeof(lpTest));  // Access Violation??  
  30.     if (bResult != TRUE)  
  31.     {  
  32.         wprintf(L"Fail to Copy String\r\n");  
  33.         return FALSE;  
  34.     }  
  35.     wprintf(L"Result String : %s\r\n", lpTest);  
  36.   
  37.     return TRUE;  
  38. }  

 

실행 결과는 다음과 같다

Result String : Some String
Fail to Copy String

 

할당되지 않은 포인터를 버퍼 인자로 넘겼을 때 Access Violation이 발생하지 않고 Parameter Validation 과정에서 걸러지는 것을 확인할 수 있다.

생각나는대로 뚝딱 만든 것이니 지나가던 고수님께서 혹시 틀린 부분이라도 발견하신다면 알려주시라.

Posted by kuaaan
,


사랑합니다. 편안히 잠드소서