다음을 통해 공유


버퍼 처리

모든 드라이버 내에서 가장 일반적인 오류 중 하나는 버퍼가 잘못되었거나 너무 작은 버퍼 처리와 관련이 있습니다. 이러한 오류는 버퍼 오버플로를 허용하거나 시스템 충돌을 유발하여 시스템 보안을 손상시킬 수 있습니다. 이 문서에서는 버퍼 처리와 관련된 몇 가지 일반적인 문제와 이를 방지하는 방법에 대해 설명합니다. 또한 적절한 버퍼 처리 기술을 보여 주는 WDK 샘플 코드를 식별합니다.

버퍼 형식 및 잘못된 주소

드라이버의 관점에서 버퍼는 다음 두 가지 종류 중 하나로 제공됩니다.

  • 메모리에 상주할 수도 있고 없을 수도 있는 페이징된 버퍼입니다.

  • 메모리에 상주해야 하는 페이지가 없는 버퍼입니다.

잘못된 메모리 주소가 페이징되거나 페이징되지 않습니다. 운영 체제가 잘못된 버퍼 처리로 인한 페이지 오류를 해결하기 위해 작동하므로 다음 단계를 수행합니다.

  • 잘못된 주소를 "표준" 주소 범위(페이징된 커널 주소, 페이징하지 않은 커널 주소 또는 사용자 주소) 중 하나로 격리합니다.

  • 적절한 유형의 오류가 발생합니다. 시스템은 항상 PAGE_FAULT_IN_NONPAGED_AREA 같은 버그 검사 또는 STATUS_ACCESS_VIOLATION 같은 예외에 의해 버퍼 오류를 처리합니다. 오류가 버그 검사인 경우 시스템은 작업을 중지합니다. 예외의 경우 시스템은 스택 기반 예외 처리기를 호출합니다. 예외 처리기가 예외를 처리하지 않는 경우 시스템은 버그 검사를 호출합니다.

그럼에도 불구하고 드라이버가 버그 검사를 발생시키는 애플리케이션 프로그램에서 호출할 수 있는 액세스 경로는 드라이버 내의 보안 위반입니다. 이러한 위반을 통해 애플리케이션은 전체 시스템에 서비스 거부 공격을 일으킬 수 있습니다.

일반적인 가정 및 실수

이 영역에서 가장 일반적인 문제 중 하나는 드라이버 작성자가 운영 환경에 대해 너무 많이 가정한다는 것입니다. 몇 가지 일반적인 가정 및 실수는 다음과 같습니다.

  • 드라이버는 단순히 높은 비트가 주소에 설정되어 있는지 여부를 확인합니다. 고정 비트 패턴을 사용하여 주소 유형을 결정하는 것은 모든 시스템 또는 시나리오에서 작동하지 않습니다. 예를 들어 시스템에서 4GT(4기가바이트 튜닝)를 사용하는 경우 x86 기반 컴퓨터에서는 이 검사가 작동하지 않습니다. 4GT를 사용하는 경우 사용자 모드 주소는 주소 공간의 세 번째 기가바이트에 대해 높은 비트를 설정합니다.

  • 주소의 유효성을 검사하기 위해 ProbeForRead ProbeForWrite만 사용하는 드라이버입니다. 이러한 호출은 프로브 시 주소가 유효한 사용자 모드 주소인지 확인합니다. 그러나 프로브 작업 후에 이 주소가 유효한 상태로 유지된다는 보장은 없습니다. 따라서 이 기술은 주기적인 비생산적 충돌로 이어질 수 있는 미묘한 경합 상태를 도입합니다.

    ProbeForReadProbeForWrite 호출은 여전히 필요합니다. 드라이버가 프로브를 생략하면 사용자는 a 및 __except 블록(구조적 예외 처리)이 catch되지 않는 유효한 커널 모드 주소를 __try 전달하여 큰 보안 구멍을 열 수 있습니다.

    결론은 검색 및 구조적 예외 처리가 모두 필요하다는 것입니다.

    • 검색은 주소가 사용자 모드 주소이고 버퍼의 길이가 사용자 주소 범위 내에 있는지 확인합니다.

    • 블록은 __try/__except 액세스를 차단합니다.

    ProbeForRead메모리 주소가 유효한지 여부가 아니라 주소와 길이가 가능한 사용자 모드 주소 범위(예: 4GT가 없는 시스템의 경우 2GB 미만)에 속하는지 확인합니다. 반면 ProbeForWrite는 지정된 길이의 각 페이지에서 첫 번째 바이트에 액세스하여 이러한 바이트가 유효한 메모리 주소인지 확인합니다.

  • 주소가 유효한지 확인하기 위해 MmIsAddressValid와 같은 메모리 관리자 함수를 사용하는 드라이버입니다. 프로브 함수에 대해 설명한 대로 이 상황에서는 재현할 수 없는 충돌을 유발할 수 있는 경합 상태가 발생합니다.

  • 구조적 예외 처리를 사용하지 못하는 드라이버입니다. 컴파일러 내의 함수는 __try/except 예외 처리를 위해 운영 체제 수준 지원을 사용합니다. 커널 수준 예외는 ExRaiseStatus 또는 관련 함수 중 하나를 호출하여 시스템에 다시 throw됩니다. 드라이버가 예외를 발생시킬 수 있는 호출에 대해 구조적 예외 처리를 사용하지 못하면 버그 검사(일반적으로 KMODE_EXCEPTION_NOT_HANDLED)가 발생합니다.

    오류를 발생시키는 것으로 예상되지 않는 코드에 대해 구조적 예외 처리를 사용하는 것은 실수입니다. 이 사용법은 그렇지 않으면 찾을 수 있는 실제 버그를 마스킹합니다. 래퍼를 __try/__except 루틴의 최상위 디스패치 수준에 두는 것은 이 문제에 대한 올바른 해결 방법은 아니지만 드라이버 작성자가 시도하는 반사 솔루션인 경우도 있습니다.

  • 사용자 메모리의 내용이 안정적으로 유지될 것이라고 가정하는 드라이버입니다. 예를 들어 드라이버가 사용자 모드 메모리 위치에 값을 쓴 다음 나중에 동일한 루틴에서 해당 메모리 위치를 참조한다고 가정합니다. 악의적인 애플리케이션은 쓰기 후 해당 메모리를 적극적으로 수정하여 드라이버가 충돌할 수 있습니다.

파일 시스템의 경우 파일 시스템은 일반적으로 사용자 버퍼(METHOD_NEITHER 전송 방법)에 직접 액세스하는 데 의존하기 때문에 이러한 문제가 심각합니다. 이러한 드라이버는 사용자 버퍼를 직접 조작하므로 운영 체제 수준 충돌을 방지하기 위해 버퍼 처리에 대한 예방 방법을 통합해야 합니다. 빠른 I/O는 항상 원시 메모리 포인터를 전달하므로 빠른 I/O가 지원되는 경우 드라이버는 유사한 문제로부터 보호해야 합니다.

버퍼 처리를 위한 샘플 코드

WDK에는 다음을 포함하여 fastfatCDFS 파일 시스템 드라이버 샘플 코드에서 버퍼 유효성 검사의 다양한 예제가 포함되어 있습니다.

  • fastfat\deviosup.c의 FatLockUserBuffer 함수MmProbeAndLockPages를 사용하여 사용자 버퍼 뒤에 있는 실제 페이지를 잠그고 FatMapUserBuffer의 MmGetSystemAddressForMdlSafe 를 사용하여 잠긴 페이지에 대한 가상 매핑을 만듭니다.

  • fastfat\fsctl.c의 FatGetVolumeBitmap 함수는 ProbeForRead ProbeForWrite를 사용하여 조각 모음 API에서 사용자 버퍼의 유효성을 검사합니다.

  • cdfs\read.c의 CdCommonRead 함수코드에서 사용자 버퍼를 0개까지 사용합니다 __try __except. CdCommonRead샘플 코드는 및 except 키워드를 try 사용하는 것처럼 보입니다. WDK 환경에서 C의 이러한 키워드는 컴파일러 확장 __try__except. C++ 코드를 사용하는 모든 사용자는 C++ 키워드와 마찬가지로 __try 네이티브 컴파일러 형식을 사용하여 예외를 제대로 처리해야 하지만 C 키워드는 처리하지 않아야 하며 커널 드라이버에 유효하지 않은 C++ 예외 처리 형식을 제공합니다.