다음을 통해 공유


버퍼 처리

아마도 모든 드라이버 내에서 가장 일반적인 오류는 버퍼가 유효하지 않거나 너무 작은 버퍼 처리와 관련이 있습니다. 이러한 오류는 버퍼 오버플로를 허용하거나 시스템의 보안 손상을 구성하는 시스템 충돌을 일으킬 수 있습니다.

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

  • 메모리에 상주하거나 상주하지 않을 수 있는 페이징된 버퍼입니다.

  • 메모리에 상주해야 하는 페이징되지 않은 버퍼입니다.

물론 잘못된 주소는 페이징되거나 페이징되지 않지만 운영 체제가 이러한 버퍼로 인해 발생하는 페이지 오류를 해결하기 시작함에 따라 잘못된 주소를 "표준" 주소 범위(페이징된 커널 주소, 페이징되지 않은 커널 주소 또는 사용자 주소) 중 하나로 격리하고 적절한 유형의 오류를 발생합니다. 버퍼 오류는 항상 버그 검사(예: PAGE_FAULT_IN_NONPAGED_AREA) 또는 예외(예: STATUS_ACCESS_VIOLATION)에 의해 처리됩니다. 버그 검사 경우 시스템은 작업을 중지합니다. 예외의 경우 스택 기반 예외 처리기가 호출되고, 예외를 처리하지 않는 경우 버그 검사 호출됩니다.

어쨌든 드라이버가 버그 검사 발생시키는 애플리케이션 프로그램에서 호출할 수 있는 액세스 경로는 드라이버 내의 보안 위반입니다. 이렇게 하면 애플리케이션이 전체 시스템에 서비스 거부 공격을 일으킬 수 있습니다.

이 영역에서 가장 일반적인 문제 중 하나는 드라이버 작성기가 운영 환경에 대해 너무 많이 가정한다는 것입니다. 여기에는 다음이 포함될 수 있습니다.

  • 높은 비트가 주소에 설정되어 있는지 확인합니다. Boot.ini 파일에서 /3GB 옵션을 설정하여 시스템에서 4GT(4기가바이트 튜닝)를 사용하는 x86 기반 컴퓨터에서는 작동하지 않습니다. 이 경우 사용자 모드 주소는 주소 공간의 세 번째 GB(기가바이트)에 대해 높은 비트를 설정합니다.

  • ProbeForReadProbeForWrite를 사용하여 주소의 유효성을 검사합니다. 이렇게 하면 프로브 시 주소가 유효한 사용자 모드 주소가 되지만 프로브 작업 후 유효한 상태를 유지해야 하는 것은 없습니다. 따라서 이 기술은 주기적인 비생산적 크래시로 이어질 수 있는 미묘한 경합 상태를 도입합니다. ProbeForReadProbeForWrite 호출은 주소가 사용자 모드 주소인지 여부와 버퍼의 길이가 사용자 주소 범위 내에 있는지 유효성을 검사하는 다른 이유로 필요합니다. 프로브를 생략하면 사용자는 유효한 커널 모드 주소를 전달할 수 있습니다. 이 주소는 __try 및 __except 블록(구조적 예외 처리)에 의해 catch되지 않으며 큰 보안 구멍이 열립니다. 따라서 정렬을 보장하고 사용자 모드 주소와 길이가 사용자 주소 범위 내에 있는지 확인하기 위해 ProbeForReadProbeForWrite 호출이 필요합니다. 그러나 액세스를 막기 위해 __try 및 __except 블록이 필요합니다.

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

  • 메모리 관리자 함수(예: MmIsAddressValid)를 사용하여 주소가 유효한지 확인합니다. 프로브 함수와 마찬가지로 이로 인해 복구할 수 없는 충돌이 발생할 수 있는 경합 상태가 발생합니다.

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

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

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

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

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

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

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

  • cdfs\read.c의 CdCommonRead 함수는 코드 주위에 __try 및 __except 사용하여 사용자 버퍼를 0개까지 사용합니다. CdCommonRead의 샘플 코드는 try 및 except 키워드를 사용하는 것처럼 보입니다. WDK 환경에서 C의 이러한 키워드는 컴파일러 확장 __try 및 __except 측면에서 정의됩니다. C++ 코드를 사용하는 모든 사용자는 __try C++ 키워드(keyword) 아니라 C 키워드(keyword)이므로 네이티브 컴파일러 형식을 사용하여 예외를 올바르게 처리해야 하며 커널 드라이버에 유효하지 않은 C++ 예외 처리 형식을 제공합니다.