无法验证长度可变的缓冲区

驱动程序通常接受具有固定标头和尾随可变长度数据的输入缓冲区,如以下示例所示:

   typedef struct _WAIT_FOR_BUFFER {
      LARGE_INTEGER Timeout;
      ULONG NameLength;
      BOOLEAN TimeoutSpecified;
      WCHAR Name[1];
   } WAIT_FOR_BUFFER, *PWAIT_FOR_BUFFER;

   if (InputBufferLength < sizeof(WAIT_FOR_BUFFER)) {
      IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
      return( STATUS_INVALID_PARAMETER );
   }

   WaitBuffer = Irp->AssociatedIrp.SystemBuffer;

   if (FIELD_OFFSET(WAIT_FOR_BUFFER, Name[0]) +
          WaitBuffer->NameLength > InputBufferLength) {
       IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
       return( STATUS_INVALID_PARAMETER );
   }

如果 WaitBuffer-NameLength> 是非常大的 ULONG 值,则将其添加到偏移量可能会导致整数溢出。 相反,驱动程序应从 InputBufferLength (缓冲区大小) ) 减去固定标头大小 (偏移量,并测试结果是否为 WaitBuffer-NameLength> (可变长度数据) 留出足够的空间,如以下示例所示:

   if (InputBufferLength < sizeof(WAIT_FOR_BUFFER)) {
      IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
      Return( STATUS_INVALID_PARAMETER );
   }

   WaitBuffer = Irp->AssociatedIrp.SystemBuffer;

   if ((InputBufferLength -
         FIELD_OFFSET(WAIT_FOR_BUFFER, Name[0])  <
         WaitBuffer->NameLength) {
      IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
      return( STATUS_INVALID_PARAMETER );
   }

换句话说,如果缓冲区大小减去固定标头大小所留下的字节数小于可变长度数据所需的字节数,我们将返回失败。

上述减法不能下溢,因为第一个 if 语句可确保 InputBufferLength 大于或等于 WAIT_FOR_BUFFER的大小。

下面显示了更复杂的溢出问题:

   case IOCTL_SET_VALUE:
      dwSize = sizeof(SET_VALUE);

      if (inputBufferLength < dwSize) {
         ntStatus = STATUS_BUFFER_TOO_SMALL;
         break;
      }

      dwSize = FIELD_OFFSET(SET_VALUE, pInfo[0]) +
                  pSetValue->NumEntries * sizeof(SET_VALUE_INFO);

      if (inputBufferLength < dwSize) {
         ntStatus = STATUS_BUFFER_TOO_SMALL;
         break;
      }

在此示例中,整数溢出可能在乘法期间发生。 如果 SET_VALUE_INFO 结构的大小是 2 的倍数,则 NumEntries 值(如 0x80000000)在乘法期间向左移动位时会导致溢出。 但是,缓冲区大小仍会通过验证测试,因为溢出会导致 dwSize 看起来很小。 若要避免此问题,请减去上一示例中的长度,除以 sizeof (SET_VALUE_INFO) ,并将结果与 NumEntries 进行比较。