Talos Vulnerability Report

TALOS-2020-1098

Microsoft Windows 10 CLFS.sys ValidateRegionBlocks privilege escalation vulnerability

September 8, 2020
CVE Number

CVE-2020-1115

Summary

A privilege escalation vulnerability exists in the CLFS.sys ValidateRegionBlocks functionality of Microsoft Windows 10 CLFS.SYS 10.0.19041.264 (WinBuild.160101.0800) and Insider Preview CLFS.SYS 10.0.20150.1000 (WinBuild.160101.0800). A specially crafted malformed log file can cause a heap buffer overflow, resulting in privilege escalation. An attacker can trigger this bug from userland using a malformed log file.

Tested Versions

Microsoft Windows 10 CLFS.SYS 10.0.19041.264 (WinBuild.160101.0800)
Microsoft Windows 10 Insider Preview CLFS.SYS 10.0.20150.1000 (WinBuild.160101.0800)

Product URLs

https://www.microsoft.com/en-us/windows

CVSSv3 Score

8.8 - CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-122 - Heap-based Buffer Overflow

Details

Windows 10 is a popular operating system.

The Common Log File System (CLFS) is a general-purpose logging service that can be used by software clients running in user-mode or kernel-mode. To provide high performance its major functionality has been implemented as a Windows kernel driver called CLFS.SYS.

A read attempt of a malformed CLFS log file may cause a pool overflow which can subsequently lead to arbitrary code execution.

The vulnerability exists in validation process of blocks related with the last OwnerPage in a multiplexed log file. A simplified example of a multiplexed log container structure can be seen in Figure 1.

						log_multiContainer00000000000000000002
						
                       +--------------------------------------+
                       |   Block1       +---------------------+
                       |                |     Record1         |
                       +--------------------------------------+
                       |   Block2       +---------------------+
                       |                |     Record2         |
                       +----------------+---------------------+
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       +--------------------------------------+
                       |          OwnerPage ( 4KB )           |
                       |                                      |
                       +--------------------------------------+  512KB ( 0x80000 )
                       |   BlockN       +---------------------+
                       |                |     RecordN         |
                       +----------------+---------------------+
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       |                                      |
                       +--------------------------------------+
                       |           OwnerPage ( 4KB )          |
                       |                                      |
                       +--------------------------------------+  1MB ( 0x100000 )


                Figure 1. Approximate look of a multiplexed container structure.

Just after the user-mode client tries to open a handle to a log file CLFS.SYS drivers calls the following methods:

Initialized -> RebuildOwnerPage -> FindLastOwnerPage -> ValidateRegionBlocks

Let’s dive in into the vulnerable ValidateRegionBlocks method to see the root-causes. The pseudo-code looks as follows:

Line 1 	__int64 __usercall CClfsLogFcbPhysical::ValidateRegionBlocks@<rax>(CClfsLogFcbPhysical *this@<rcx>, struct _FILE_OBJECT *a2@<rdx>, unsigned __int8 *const a3@<r8>, union _CLS_LSN *a4@<r9>, unsigned int *a5, unsigned int *a6)
Line 2 	{
Line 3 		_sectorSizeSum   = 0;
Line 4 		currentAllocSize = 0;
Line 5 		(...)
Line 6 		  while ( (unsigned int)_sectorSizeSum < (unsigned int)fileContentSize )
Line 7 		  {
Line 8 			currentBlockHeader = (fsclfs_block_header *)&fileContent[(unsigned int)_sectorSizeSum];        
			    (...)
Line 26			
Line 27			if ( _sectorSizeSum + currentBlockHeader->number_of_sectors << 9) <= x_0x40000 )
Line 28			{
Line 29			  nextOwnerPageLsn = CClfsLogFcbPhysical::GetNextOwnerPageLsn((__int64)_this,&v114, v48, 0);
Line 30			  nextBlockLSN = CClfsLogFcbPhysical::AddLsnOffset(
Line 31							   _this,
Line 32							   &v109,
Line 33							   currentBlockHeader->physical_lsn,
Line 34							   currentBlockHeader->number_of_sectors << 9);
Line 35							   
Line 36			  if ( !ClfsLsnGreater(nextBlockLSN, nextOwnerPageLsn) )
Line 37			  {
					(...)
Line 59				goto @endWhile;
Line 60			  }
Line 61			  
Line 62			  number_of_sectors = currentBlockHeader->number_of_sectors;
Line 63			}
Line 64			if ( currentAllocSize < (unsigned int)number_of_sectors << 9 )
Line 65			{
Line 66			  if ( dstBuffer )
Line 67			  {
Line 68				ExFreePoolWithTag(dstBuffer, 0);
Line 69				dstBuffer = 0i64;
Line 72			  }
Line 73			  currentAllocSize = number_of_sectors << 9;
Line 74
Line 75			  dstBuffer = ExAllocatePoolWithTag(PagedPool,number_of_sectors << 9,'sflC');// <- ALLOC
Line 76			  if ( !dstBuffer )
Line 77			  {
Line 78				goto @end;
Line 79			  }
Line 80			}
Line 81			size = x_0x40000 - _sectorSizeSum;
Line 82			if ( flag )                             // flag = FALSE
Line 83			  size = (size - 4096);
Line 84			memmove(dstBuffer, currentBlockHeader, size);
Line 85			
Line 86			@endWhile:        
Line 87			_sectorSizeSum += currentBlockHeader->number_of_sectors << 9;
Line 88		  }

Inside the while loop we see iteration over all blocks related to the last OwnerPage. In our case these blocks start at offset 0x80000 in file log_multiContainer00000000000000000002. During these iterations, the block type / integrity is checked in a couple different ways, but scenario that triggers the vulnerability appears when the malformed block located at offset 0x81400 is being validated.

More precisely, the malformed value in this block is physical_lsn :

8:1418h: 00 F6 77 00 01 00 00 00    

Its malformed value is bigger than physical_lsn of LastOwnerPage:

F:F018h: 00 F0 0F 00 01 00 00 00                         

Which results in the ClfsLsnGreater function returning TRUE at line 36 and the code subseuently goes to line 62, instead of doing a few additional block validation checks and being redirected to the end of the while-loop at line 59.
Further based on the fact that sectors size == currentAllocSize in the current block:

Number of sectors
8:1404h: 01 00     	

where

`sector size` = 0x200 == 512 == 2^9

As a result, a pool allocation is made at line 75. In our case, the allocated space equals 0x200 bytes. The size value is laterused in a memcpy operation and is calculated at line 81 and in our case equals :

0x00040000 - 0x00001400 = '0x3ec00'				  

_sectorSizeSum = 0x00001400 - is an offset to the current block starting from 0x80000 ( 512KB ). Each 512KB data portion has its own OwnerPage.
0x00040000 - const value set at the beginning of the function 

At line 84a memmove/memcpy operation is executed which triggers a pool overflow. This vulnerability gives attackers very powerful and flexible way to corrupt kernel pool(s) structure and related data because the size of overflow is nearly fully controllable: malforming physical_lsn of a chosen block an attacker can increase/decrease the value of _sectorSizeSum. The source buffer/data represented by currentBlockHeader is also in almost fully controllable by an attacker because it contains user-defined record data.

8:1400h: 15 00 01 03 01 00 01 00 00 00 00 00 00 00 00 00  ................ 
(...)
8:1490h: 00 00 28 00 25 00 00 00 41 41 41 41 41 41 41 41  ..(.%...AAAAAAAA 
8:14A0h: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA 
8:14B0h: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA 
8:14C0h: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA 
(...)

Crash Information

2: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffffcd0f3ee935f0, memory referenced.
Arg2: 0000000000000002, value 0 = read operation, 1 = write operation.
Arg3: fffff8034f29d150, If non-zero, the instruction address which referenced the bad memory
	address.
Arg4: 0000000000000000, (reserved)

ADDITIONAL_XML: 1

BUGCHECK_CODE:  50

BUGCHECK_P1: ffffcd0f3ee935f0

BUGCHECK_P2: 2

BUGCHECK_P3: fffff8034f29d150

BUGCHECK_P4: 0

READ_ADDRESS:  ffffcd0f3ee935f0 Special pool

MM_INTERNAL_CODE:  0

IMAGE_NAME:  CLFS.SYS

MODULE_NAME: CLFS

FAULTING_MODULE: fffff8034f290000 CLFS

BLACKBOXBSD: 1 (!blackboxbsd)


BLACKBOXNTFS: 1 (!blackboxntfs)


BLACKBOXPNP: 1 (!blackboxpnp)


BLACKBOXWINLOGON: 1

PROCESS_NAME:  test.exe

TRAP_FRAME:  ffffe30494bd3d00 -- (.trap 0xffffe30494bd3d00)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=ffffcd0f3ee93600 rbx=0000000000000000 rcx=ffffcd0f3ee935e0
rdx=ffffd9f8041eca00 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8034f29d150 rsp=ffffe30494bd3e98 rbp=ffffe30494bd4760
 r8=0000000000000000  r9=0000000000001840 r10=00007ffffffeffff
r11=ffffcd0f3ee62e00 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz na pe nc
CLFS!memcpy+0x250:
fffff803`4f29d150 660f7f4110      movdqa  xmmword ptr [rcx+10h],xmm0 ds:ffffcd0f`3ee935f0=????????????????????????????????
Resetting default scope

STACK_TEXT:  
ffffe304`94bd3a58 fffff803`4e40c1af : 00000000`00000050 ffffcd0f`3ee935f0 00000000`00000002 ffffe304`94bd3d00 : nt!KeBugCheckEx
ffffe304`94bd3a60 fffff803`4e27773f : 00000000`73666c43 00000000`00000002 00000000`00000000 ffffcd0f`3ee935f0 : nt!MiSystemFault+0x1c5d6f
ffffe304`94bd3b60 fffff803`4e3ef41e : 00000000`00000001 fffff803`4e228d82 00000000`00040000 ffff8f86`af26e118 : nt!MmAccessFault+0x34f
ffffe304`94bd3d00 fffff803`4f29d150 : fffff803`4f2eb171 00000000`0000f800 ffffa707`4304f800 ffffe304`94bd3fc0 : nt!KiPageFault+0x35e
ffffe304`94bd3e98 fffff803`4f2eb171 : 00000000`0000f800 ffffa707`4304f800 ffffe304`94bd3fc0 00000000`00000000 : CLFS!memcpy+0x250
ffffe304`94bd3ea0 fffff803`4f2d8fba : ffff8f86`b6268000 ffff8f86`bab515c0 ffffcd0f`3e1b3000 ffffe304`94bd4058 : CLFS!CClfsLogFcbPhysical::ValidateRegionBlocks+0x759
ffffe304`94bd4010 fffff803`4f2d20cf : ffff8f86`b6268001 ffff8f86`bab515c0 00000000`00000000 00000001`000ff000 : CLFS!CClfsLogFcbPhysical::FindLastOwnerPage+0x69ea
ffffe304`94bd40a0 fffff803`4f29862e : ffff8f86`b6268000 ffff8f86`bab515c0 00000000`0000007b 00000000`00000001 : CLFS!CClfsLogFcbPhysical::RebuildOwnerPage+0x7b
ffffe304`94bd4110 fffff803`4f2c340d : ffff8f86`b6268000 ffff8f86`b6268038 ffff8f86`b91f0ae0 ffff8f86`00120089 : CLFS!CClfsLogFcbPhysical::Initialize+0xa46
ffffe304`94bd4250 fffff803`4f2c1f2b : ffff8f86`b4d26ee0 ffffe304`94bd4301 00000000`00000007 fffff803`4f2c0100 : CLFS!CClfsRequest::Create+0x729
ffffe304`94bd4390 fffff803`4f2c1cf7 : ffff8f86`b4d26ee0 fffff803`4eb9901d ffff8f86`b121a9d0 00000000`00000040 : CLFS!CClfsRequest::Dispatch+0x97
ffffe304`94bd43e0 fffff803`4f2c1c47 : ffff8f86`b67cf780 ffff8f86`b67cf780 ffffe304`94bd46d0 00000000`00000000 : CLFS!ClfsDispatchIoRequest+0x87
ffffe304`94bd4430 fffff803`4e2b8eb9 : fffff803`4e823405 00000000`00000000 00000000`00000001 00000000`00000000 : CLFS!CClfsDriver::LogIoDispatch+0x27
ffffe304`94bd4460 fffff803`4e2b7f74 : 00000000`00000003 00000000`00000000 00000000`00000000 fffff803`4e2b8733 : nt!IofCallDriver+0x59
ffffe304`94bd44a0 fffff803`4e823aeb : ffffe304`94bd4760 fffff803`4e823405 ffffe304`94bd46d0 ffff8f86`ba1f0420 : nt!IoCallDriverWithTracing+0x34
ffffe304`94bd44f0 fffff803`4e82aabf : ffff8f86`b121a9d0 ffff8f86`b121a905 ffff8f86`b91f0ac0 00000000`00000001 : nt!IopParseDevice+0x62b
ffffe304`94bd4660 fffff803`4e828f21 : ffff8f86`b91f0a00 ffffe304`94bd48a8 00000000`00000040 ffff8f86`aeb4b560 : nt!ObpLookupObjectName+0x78f
ffffe304`94bd4820 fffff803`4e870750 : 00000000`00000001 00000000`00fbeac0 00000000`00000001 00000000`00000000 : nt!ObOpenObjectByNameEx+0x201
ffffe304`94bd4960 fffff803`4e86ff19 : 00000000`00fbe1f0 ffffffff`80100000 00000000`00fbeac0 00000000`00fbe208 : nt!IopCreateFile+0x820
ffffe304`94bd4a00 fffff803`4e3f2c18 : 00000000`00000000 00000000`00000000 00000000`00000000 ffffe304`94bd4b80 : nt!NtCreateFile+0x79
ffffe304`94bd4a90 00007ffc`fe8fcb14 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x28
00000000`00fbe178 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ffc`fe8fcb14


SYMBOL_NAME:  CLFS!memcpy+250

STACK_COMMAND:  .thread ; .cxr ; kb

BUCKET_ID_FUNC_OFFSET:  250

FAILURE_BUCKET_ID:  AV_VRF_INVALID_CLFS!memcpy

OS_VERSION:  10.0.18362.1

BUILDLAB_STR:  19h1_release

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 10

FAILURE_ID_HASH:  {5a7b4367-e028-7bd2-60c5-716ed85fea1a}

Followup:     MachineOwner
---------

Timeline

2020-06-23 - Vendor Disclosure
2020-09-08 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.