NVIDIA Driver - Escape Code Leaks Uninitialised ExAllocatePoolWithTag Memory to Userspace







Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=892

The handler for the DxgkDdiEscape escape code 0x70000D4 has the following pseudocode:

void __fastcall escape_70000D4(NvMiniportDeviceContext *a1, NvEscapeData *a2)
  Escape70000D4 *escape_data_; // rbx@1
  PVOID alloc_buf; // rsi@1
  unsigned int v4; // edi@1
  __int64 user_ptr; // r14@4
  DWORD *v6; // rbx@5
  __int128 v7; // [rsp+40h] [rbp-38h]@1
  __int128 v8; // [rsp+50h] [rbp-28h]@4
  PVOID alloc_buf_; // [rsp+60h] [rbp-18h]@4

  escape_data_ = (Escape70000D4 *)a2;
  a2->unknown_rest[6] = 1;
  LODWORD(v7) = 0;
  memset((char *)&v7 + 4, 0, 0x24ui64);
  alloc_buf = ExAllocatePoolWithTag_(PagedPool, escape_data_->user_ptr_size, 'paVN');
  v4 = 0;
  if ( !alloc_buf )
    v4 = 0xFFFF;
  if ( v4 )
    goto LABEL_12;
  HIDWORD(v8) = escape_data_->user_ptr_size;
  alloc_buf_ = alloc_buf;
  v4 = sub_625BC(0i64, dword_B1BB94, escape_data_->unknown_0, 0x83F30101, (__int64)&v7, 40);
  user_ptr = escape_data_->user_ptr;
  ProbeForWrite((PVOID)escape_data_->user_ptr, escape_data_->user_ptr_size, UserMode);
  memcpy((void *)escape_data_->user_ptr, alloc_buf, escape_data_->user_ptr_size);
  *(_OWORD *)&escape_data_->unknown_2 = v7;
  *(_OWORD *)&escape_data_->unknown_4 = v8;
  escape_data_->user_ptr = user_ptr;
  if ( v4 )
    v6 = &escape_data_->header.unknown_rest[6];
    if ( v6 )
      if ( v4 <= 0xFFFFF000 )
        *v6 = -4096 - v4;
  if ( alloc_buf )
    ExFreePoolWithTag_(alloc_buf, 0x7061564Eu);

ExAllocatePoolWithTag is called with a user provided size to allocate a buffer, but the subsequent copying of said buffer to the user provided pointer doesn't make sense since the buffer is never initialised with any values. This means that a user mode program can leak uninitialised memory from arbitrarily-sized pool allocations.


Looks like I made an oversimplified analysis of the pseudocode in the report. The allocated buffer pointer is indeed passed off to the sub_625BC function (as part of a struct member on the stack) which eventually passes it to a bunch of other functions.

However, this doesn't change the fact that with the provided PoC, the pool allocated buffer still isn't being initialised and is copied into the user buffer unchanged.

Proof of Concept: