다음을 통해 공유


직접 I/O의 오류

가장 일반적인 직접 I/O 문제는 길이가 0인 버퍼를 올바르게 처리하지 못하는 것입니다. I/O 관리자는 길이가 0인 전송에 대한 MDL을 만들지 않으므로 길이가 0인 버퍼는 Irp-MdlAddress>에서 NULL 값을 생성합니다.

주소 공간을 매핑하려면 드라이버가 NULL MdlAddress를 통과하는 경우와 마찬가지로 매핑이 실패할 경우 NULL을 반환하는 MmGetSystemAddressForMdlSafe를 사용해야 합니다. 드라이버는 반환된 주소를 사용하기 전에 항상 NULL 반환을 확인해야 합니다.

직접 I/O에는 사용자의 주소 공간을 시스템 주소 버퍼에 두 번 매핑하여 두 개의 서로 다른 가상 주소가 동일한 실제 주소를 갖도록 합니다. 이중 매핑에는 다음과 같은 결과가 발생하며, 이로 인해 드라이버에 문제가 발생할 수 있습니다.

  • 사용자 주소의 가상 페이지에 대한 오프셋은 시스템 페이지의 오프셋이 됩니다.

    이러한 시스템 버퍼가 끝날 때까지의 액세스는 매핑의 페이지 세분성에 따라 오랜 시간 동안 눈에 띄지 않을 수 있습니다. 호출자의 버퍼가 페이지 끝 근처에 할당되지 않는 한 버퍼의 끝 부분에 기록된 데이터는 버퍼에 표시되고 호출자는 오류가 발생했음을 인식하지 못합니다. 버퍼의 끝이 페이지 끝과 일치하는 경우 끝 너머의 시스템 가상 주소는 아무 것도 가리키거나 유효하지 않을 수 있습니다. 이러한 문제는 찾기가 매우 어려울 수 있습니다.

  • 호출 프로세스에 사용자의 메모리 매핑을 수정하는 다른 스레드가 있는 경우 사용자의 메모리 매핑이 변경되면 시스템 버퍼의 내용이 변경됩니다.

    이 경우 시스템 버퍼를 사용하여 스크래치 데이터를 저장하면 문제가 발생할 수 있습니다. 동일한 메모리 위치에서 두 개의 인출은 서로 다른 값을 생성할 수 있습니다.

    다음 코드 조각은 직접 I/O 요청에서 문자열을 수신한 다음 해당 문자열을 대문자로 변환하려고 시도합니다.

    PWCHAR  PortName = NULL;
    
    PortName = (PWCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
    
    //
    // Null-terminate the PortName so that RtlInitUnicodeString will not
    // be invalid.
    //
    PortName[Size / sizeof(WCHAR) - 1] = UNICODE_NULL;
    
    RtlInitUnicodeString(&AdapterName, PortName);
    

    버퍼가 올바르게 구성되지 않을 수 있으므로 코드는 유니코드 NULL 을 마지막 버퍼 문자로 강제 적용하려고 시도합니다. 그러나 기본 실제 메모리가 사용자 및 커널 모드 주소 모두에 두 배로 매핑되는 경우 이 쓰기 작업이 완료되는 즉시 프로세스의 다른 스레드가 버퍼를 덮어쓸 수 있습니다.

    반대로 NULL이 없으면 RtlInitUnicodeString대한 호출이 버퍼의 범위를 초과할 수 있으며 시스템 매핑을 벗어나면 버그 검사가 발생할 수 있습니다.

드라이버가 자체 MDL을 만들고 매핑하는 경우 검색한 메서드를 사용하여 MDL에만 액세스해야 합니다. 즉, 드라이버가 MmProbeAndLockPages를 호출할 때 액세스 메서드(IoReadAccess, IoWriteAccess 또는 IoModifyAccess)를 지정합니다. 드라이버가 IoReadAccess를 지정하는 경우 나중에 MmGetSystemAddressForMdl 또는 MmGetSystemAddressForMdlSafe에서 사용할 수 있는 시스템 버퍼에 쓰려고 시도해서는 안 됩니다.