[Bug 40817] The Last Remnant demo installer crashes on startup (broken app , allocates buffer for parsing unicode text files one byte too short, NULL terminator by chance)

wine-bugs at winehq.org wine-bugs at winehq.org
Sat Oct 13 06:55:24 CDT 2018


https://bugs.winehq.org/show_bug.cgi?id=40817

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
          Component|-unknown                    |ntdll
            Summary|The last remnant demo       |The Last Remnant demo
                   |installer crashes inside    |installer crashes on
                   |kernel32                    |startup (broken app,
                   |                            |allocates buffer for
                   |                            |parsing unicode text files
                   |                            |one byte too short, NULL
                   |                            |terminator by chance)
           Keywords|                            |Installer
                 CC|                            |focht at gmx.net

--- Comment #7 from Anastasius Focht <focht at gmx.net> ---
Hello folks,

confirming.

In short: The installer reads multiple unicode .INI files into memory in order
parse them on its own. It allocates one byte too short, essentially file_size+1
which is not enough because you need 2 bytes for unicode NULL terminator.
When the content has been read into memory, it runs 'lstrlenW()' on the buffer.
Depending on the heap usage, 'lstrlenW()' will stop at the first random unicode
NULL terminator encountered or if it doesn't find any run out of mapped heap
bounds, causing exception.

The installer just works by chance on Windows due to different heap manager
implementation which is much more lenient given the gazillion
misbehaving/broken apps there. Any decent heap tool ('PageHeap') would also
flag this installer/app as "broken".

Wine-Staging just works by chance too. The heap manager modifications
(https://github.com/wine-staging/wine-staging/tree/master/patches/ntdll-Heap_Improvements)
just make it happen more likely that a random unicode NULL terminator is found
until it runs out of mapped heap space (yes, and it gets it wrong sometimes,
but no exception).

Installer disassembly, showing broken code:

--- snip ---
004040CC  PUSH EBP
004040CD  MOV EBP,ESP
004040CF  PUSH ECX
004040D0  MOV EAX,DWORD PTR SS:[ARG.1]
...
004040D9  PUSH EDI
004040DA  PUSH EBX                            ; SizeHigh => NULL
004040DB  PUSH EAX                            ; hFile
004040DC  CALL DWORD PTR DS:[<&KERNEL32.GetFileSize>]
004040E2  MOV EDI,EAX
004040E4  CMP EDI,-1
004040E7  JE 004041D5
004040ED  CMP BYTE PTR SS:[EBP+0C],BL
004040F0  MOV ESI,DWORD PTR DS:[<&KERNEL32.GetProcessHeap>]
004040F6  LEA EAX,[EDI+1]                     ; file_size += 1 (bug!)
004040FA  PUSH 8                              ; Flags = HEAP_ZERO_MEMORY
004040FC  JNE SHORT 0040414C
...
0040414E  PUSH EAX
0040414F  CALL DWORD PTR DS:[<&KERNEL32.HeapAlloc>]
00404155  CMP EAX,EBX
00404157  MOV DWORD PTR SS:[EBP+0C],EAX
...
0040415C  MOV EAX,DWORD PTR SS:[ARG.1]
0040415F  LEA ECX,[LOCAL.1]
00404162  PUSH EBX                            ; pOverlapped
00404163  PUSH ECX                            ; pBytesRead 
00404164  MOV EAX,DWORD PTR DS:[EAX]
00404166  PUSH EDI                            ; Size
00404167  PUSH DWORD PTR SS:[ARG.2]           ; Buffer => [ARG.2]
0040416A  MOV DWORD PTR SS:[LOCAL.1],EBX
0040416D  PUSH EAX                            ; hFile
0040416E  CALL DWORD PTR DS:[<&KERNEL32.ReadFile>]
00404174  PUSH DWORD PTR SS:[ARG.2]           ; String => [ARG.2]
00404177  TEST EAX,EAX
00404179  JZ SHORT 004041CB
0040417B  CALL DWORD PTR DS:[<&KERNEL32.lstrlenW>]
00404181  LEA EAX,[EAX+EAX+2]
00404185  MOV DWORD PTR SS:[ARG.1],EAX
00404188  ADD EAX,3
0040418B  AND AL,FC
0040418D  CALL 00441DC0                       ; Allocates EAX bytes on stack
00404192  MOV EDI,ESP
00404194  PUSH EBX
00404195  PUSH EBX
00404196  PUSH DWORD PTR SS:[EBP+8]
00404199  MOV BYTE PTR DS:[EDI],BL
0040419B  PUSH EDI
0040419C  PUSH -1
0040419E  PUSH DWORD PTR SS:[EBP+0C]
004041A1  PUSH EBX
004041A2  PUSH EBX
004041A3  CALL DWORD PTR DS:[<&KERNEL32.WideCharToMultiByte>]
...
--- snip ---

file_size = 0x355C

alloc_buffer(file_size+1) = 0x355D

Heap dump for bad case (with annotations):

--- snip ---
0018FA00   00003560      ; length of block (0x355D rounded to 0x3560 -> align)
0018FA04   03455355  USE ; heap in use MAGIC
0018FA08   000DFEFF      ; 0xFEFF byte order mark (BOM), 0xD -> part of CRLF
0018FA0C   005B000A   [  ; 0xA LF, "[0x0409]" string start
0018FA10   00780030  0 x
0018FA14   00340030  0 4
0018FA18   00390030  0 9
0018FA1C   000D005D  ] 
0018FA20   0054000A   T
0018FA24   00540049  I T
0018FA28   0045004C  L E
0018FA2C   0043003D  = C
0018FA30   006F0068  h o
0018FA34   0073006F  o s
...
00192F48   00530028  ( S
00192F4C   006D0069  i m
00192F50   006C0070  p l
00192F54   00660069  i f
00192F58   00650069  i e
00192F5C   00290064  d )
00192F60   000A000D       ; 0xD, 0xA -> end of file
00192F64   00525A00   ZR  ; garbage -> causes 'lstrlenW()' running further
00192F68   0008D089  
00192F6C   45455246  FREE
00192F70   00110088  
00192F74   00110298  
--- snip ---

Heap dump for random good case (with annotations):

Contents of allocated buffer before 'ReadFile()':

--- snip ---
001533E4   00000000
001533E8   00000000
001533EC   00000000       ; <--- zeroed memory was here before allocation
001533F0   000CCC01  
001533F4   45455246  FREE
001533F8   00110088  
001533FC   00110298 
00153400   00000000
--- snip ---

Contents of allocated buffer after 'ReadFile()':

--- snip ---
001533E4   00290064  d )
001533E8   000A000D  
001533EC   00000000       ; lets 'lstrlenW()' stop by chance
001533F0   000CCC01 
001533F4   45455246  FREE
001533F8   00110088  
001533FC   00110298  
00153400   00000000
--- snip ---

The Wine-Staging case:

--- snip ---
...
00195360   00530028  ( S
00195364   006D0069  i m
00195368   006C0070  p l
0019536C   00660069  i f
00195370   00650069  i e
00195374   00290064  d )
00195378   000A000D        ; end of .INI file content (CR+LF)
0019537C   00525A00   ZR   ; garbage
00195380   0008AC6A  
00195384   45455246  FREE
00195388   00000000        ; lets 'lstrlenW()' stop by chance
--- snip ---

Since the broken app uses 'HEAP_ZERO_MEMORY' one could zero initialize not only
the true requested size but the aligned size (tail bytes). This will not work
for cases where the requested block was already aligned (no unused tail bytes)
and will break heap tail checking/valgrind.

$ sha1sum The_Last_Remnant_NA_Trial_Version.zip 
86b093eec133393ef47bb1dcd268b38b12927899  The_Last_Remnant_NA_Trial_Version.zip

$ du -sh The_Last_Remnant_NA_Trial_Version.zip 
1.1G    The_Last_Remnant_NA_Trial_Version.zip

$ wine --version
wine-3.18

Regards

-- 
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.



More information about the wine-bugs mailing list