Talos Vulnerability Report

TALOS-2024-1966

Microsoft CLIPSP.SYS License update privilege escalation vulnerability

August 13, 2024
CVE Number

CVE-2024-38186

SUMMARY

A privilege escalation vulnerability exists in the License update functionality of Microsoft CLIPSP.SYS 10.0.22621 Build 22621, 10.0.26080.1 and 10.0.26085.1. A specially crafted license blob can lead to privilege escalation. An attacker can use the NtQuerySystemInformation function call to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Microsoft CLIPSP.SYS 10.0.22621 Build 22621
Microsoft CLIPSP.SYS 10.0.26080.1
Microsoft CLIPSP.SYS 10.0.26085.1

PRODUCT URLS

CLIPSP.SYS - https://www.microsoft.com/en-us/windows/windows-11

CVSSv3 SCORE

7.4 - CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-130 - Improper Handling of Length Parameter Inconsistency

DETAILS

CLIPSP.SYS is a driver used to implement Client License System Policy on Windows 10 and 11. It provides the functions used when handling most of the requests involving licensing, notably the implementation of many use cases involved with the SystemPolicyInformation class used in conjunction with NtQuerySystemInformation.

Context

When calling NtQuerySystemInformation with the SystemPolicyInformation class, ntoskrnl will call ExHandleSPCall2 that will process the data provided. The format is mostly undocumented and encrypted using Microsoft’s Warbird. Upon decryption of the data provided, a call handler is invoked based on the command_id provided and dispatches the payload to the relevant function (e.g. SPCallServerHandleClepKdf, SPCallServerHandleUpdateLicense, etc.). A substential amount of these functions are wrapers around clipsp functions that are stored as function pointers in the nt!g_kernelCallbacks globlal array.

The SPCallServerHandleUpdateLicense (command_id:100) will accept a License blob whose format is also undocumented. Once installed, these license files are stored in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\{7746D80F-97E0-4E26-9543-26B41FC22F79}\{A25AE4F2-1B96-4CED-8007-AA30E9B1A218} key, only accessible to the SYSTEM user. The format of this license file is TLV (Tag-length-value) following this format:

struct __unaligned __declspec(align(1)) LicenseParsing_entry
{
  __int16 type;
  __int16 reserved;
  int entry_size;
  char value[ANYSIZE_ARRAY]; //expected to be of size entry_size
};

The vulnerability

The code snippets below are decompiled output and variables names were assumed from context or retrieved from public symbol servers, sdk, etc. The addresses provided are for the canary build 26085.1.amd64fre.ge_release.240315-1352

There exists multiple out-of-bound reads vulnerabilities when handling license data of type 0x2 that can be turned into an out-of-bound write. This vulnerability can be triggered by feeding to the SPCallServerHandleUpdateLicense function a license filed that has been tampered with to include a malformed type-0x2 blob. The purpose of the type 0x2 is to set the device_id associated with a license bound to hardware.

When calling SPCallServerHandleUpdateLicense the data will eventually be past to an obfuscated function inside clipsp and stored in an array (see 0x0001C00E8BA3):

 License->field_0[v13].entry_size = v10->entry_size;
 License->field_0[v13].entry_field2 = v10->field_2;
 License->field_0[v13].entry_ptr = (__int64)&v10->first_byte;

Where v13 is the internal type associated with an entry type in the license file.

The problem is with how type 0x2 (internal value representation is 18) is handled throughout the code especially during the DeviceLicenseInstall function (see 0x001C00EDB0C). The data is expected to be an n+2 buffer where the first two bytes are the value n as a little endian short, followed by n bytes that are the device_id value. However the entry_size value is not used to ensures it equals n+2. As such, it is possible to provide a truncated type-0x2 license entry that will lead to multiple out-of-bound reads. Furthermore, because the n field itself can be read out-of-bound, this creates a situation where a race-condition between mutliple reads of this value can be exploited to create a mismatch between the allocation and the copy of the device_id. Let’s see how:

First we can see how the size of the device_id is being returned:

    //0001C00E761C 
    __int64 __fastcall __spoils<rax,rcx> License_get_DeviceIDSize(LicenseStruct *a1)
    {
      __int64 result; // rax
      unsigned __int16 *entry_ptr; // rcx

      result = (unsigned __int16)a1->field_0[5].entry_size;
  [0] if ( !a1->field_0[5].entry_size )
      {
        entry_ptr = (unsigned __int16 *)a1->field_0[18].entry_ptr;
        if ( entry_ptr )
  [1]      return *entry_ptr; //first out-of-bound read
      }
      return result;
    }

At [0] If no field of internal type 5 is contained in the license blob, then the internal field 18 is retrieved and the first two bytes of the entry are being dereferenced at [1], potentially causing an OOB-read if a1->field_0[18].entry_size < 2 Then we can see at [2] that, once again, if there was no internal type 5 in the license blob, then the device_id is return from the internal type 18 starting at offset 2 (see [3]) (assuming it was provided in the license blob). Which means that any subsequent access to the pointer returned by License_get_DeviceID could lead in an out-of-bound read as the expected size of that device_id is coming from the result of License_get_DeviceIDSize whose result is attacker controlled and not matching the actual value that was computed from the license blob into the a1->field_0[18].entry_size field.

//00000001C00E75F8
__int64 __fastcall __spoils<rax,rcx> License_get_DeviceID(LicenseStruct *a1)
{
  __int64 result; // rax
  __int64 entry_ptr; // rcx

  result = a1->field_0[5].entry_ptr;
  [2] if ( !result )
      {
        entry_ptr = a1->field_0[18].entry_ptr;
        if ( entry_ptr )
  [3]     return entry_ptr + 2;
      }
      return result;
}

This vulnerability comes to roost in the DeviceLicenseInstall function when executing the code path responsible for installing a new device-bound license:

      //00001C00EDC1D  
 [4]  DeviceIDSize = License_get_DeviceIDSize(License);
 [5]  status = CLIP_allocate_buffer(DeviceIDSize, &deviceIDcopy);
      if ( status < 0 )
      {
        v3 = v28;
        goto DONE;
      }
      v8 = License_get_DeviceIDSize(License);
 [6]  v13 = (unsigned int)License_get_DeviceIDSize(License);
      DeviceID = License_get_DeviceID(License);
      v3 = deviceIDcopy;
 [7]  memcpy_(deviceIDcopy, DeviceID, v13);
    }

The device_id_size is retrieved twice at [4] and [6] (double-fetch), the first time being used to do a memory allocation at [5] and then as the size variable for the memcpy at [7]. Because the device_id_size is potentially read out-of-bound (see [1]), it’s possible to create a license file where the first byte outside of the license file is the the device_id_size value, and tailor the the license blob is such a way that it will land at the end of a memory page. Then, it creates a race condition where usual heap shaping methodologies can be used to make so that the value read out of bound for the size field changes in-between the reads, leading to an out-of-bound write, if size value read the second time is bigger than the first.

The NtQuerySystemInformation with the SystemPolicyInformation with command_id=100 involved in this vulnerability is reachable from a low privilege user and potentially running in an LPAC container, as such, could be leveraged in an sandbox escape scenario.

TIMELINE

2024-04-08 - Vendor Disclosure
2024-08-13 - Vendor Patch Release
2024-08-13 - Public Release

Credit

Discovered by Philippe Laulheret of Cisco Talos.