Release of source code has revealed numerous security flaws in Quake 2 engine. Since then many authors have been working on improving stability and security of their Quake 2 engine forks. However, most work has been focused at fixing remote vulnerabilities directly exploitable over the network. Very little attention has been paid to seemingly ‘local’ vulnerabilities present in various file format parsing routines.

Robust handling of malformed game data is nevertheless very important, especially for client security. Many users are downloading packfiles from untrusted places. Automatic downloading feature built into Quake 2 engine has been recently upgraded to utilize HTTP. That means server can push megabytes of data to client in a matter of seconds, without user’s consent. Even if automatic downloads are disabled by user, malicious server can easily re-enable them (remember that Quake 2 server has full access to client console).

This page is dedicated to show the current status of issues stemming from insufficient BSP file format validation. Several most widely used multiplayer Quake 2 clients are examined against a testcase of 45 corrupted BSP files, and results are summarized in a table.

Environment

A collection of 46 ‘hand-made’ BSP files is provided. The first BSP file (testbox00.bsp) is a tiny, but otherwise complete and valid Quake 2 map. Every engine should load it correctly. Remaining 45 files are variations of the same map corrupted in different ways to evaluate the robustness of various routines responsible for parsing, detecting collisions and rendering BSP files.

All testing is performed on top of default Quake 2 installation with 3.20 patch applied. Original Quake 2 client is tested with its default OpenGL renderer (‘ref_gl’). R1Q2 client is tested with R1GL renderer (‘ref_r1gl’). AprQ2 and Q2PRO are tested with their built-in OpenGL renderers.

Table 1. Versions of tested engine components
Engine Component Version

Quake 2

quake2.exe

v3.20

ref_gl.dll

R1Q2

r1q2.exe

b8012

ref_r1gl.dll

v0.1.5.41

AprQ2

aq2.exe

v1.211

Q2PRO

q2pro.exe

r1093

Table 2. Machine used for testing

CPU

Pentium M

GPU

Mobility Radeon 9600

Operating system

Windows 7 Service Pack 1 (32-bit)

Method

Both client and server parts of code are tested. Testing method for each testcase number NN > 0 is as follows. Engine is started in windowed mode with default configuration. Listen server is created on a known valid map (for example, ‘testbox00’). After local client enters the map, ‘gamemap testboxNN’ command is executed. If engine didn’t crash upon loading the map, player moves around the map for a while and shoots. If engine still didn’t crash, it is exited by typing ‘quit’ command.

Two-stage map loading process is needed to check if server handles transitions to malformed maps robustly: it should validate the map before ‘gamemap’ command completes. Dropping all clients at map change with error message is unacceptable for multiplayer servers. Server should print a warning message and stay on the previous map instead, like it does for maps that are merely missing.

Client, of course, is permitted to drop the connection if it encounters a malformed map, since it can’t enter the server without a map!

Results

Definition of terms
crash

Program is terminated abnormally by the operating system or internal exception handler. A crash report may be created if engine supports creation of such reports.

hang

Program becomes unresponsive and is eventually terminated by the operating system.

hard

Program terminates due to internal fatal error. Engine displays a dialog box with error message and then exits.

soft

Server is killed due to internal error. Engine displays an error message in console and continues to run, but all clients are kicked off and further connections are denied.

robust

Engine detects malformed data and refuses to load the map. Server continues to run on the previous map.

unknown

Map loads successfully, and navigating it for a while does not affect engine stability. This doesn’t necessarily mean that engine has correctly worked around malformed data, further analysis is required.

other

Loading malformed map doesn’t result in error, but causes some sort of engine instability (see footnotes).

Table 3. Testing results
Test # Test name Quake 2 R1Q2 AprQ2 Q2PRO

01

header_badofs

crash

crash

crash

robust

02

header_badlen

crash

soft

soft

robust

03

header_overflow

crash

crash

crash

robust

04

vis_badnumclusters

crash

unknown

unknown

robust

05

vis_badoffsets

crash

crash

crash

robust

06

vis_overflow

crash

crash

crash

robust

07

nodes_loop

crash

crash

crash

robust

08

nodes_badplanenum

crash

crash

crash

robust

09

nodes_badchildren

crash

crash

crash

robust

10

nodes_badfirstface

crash

crash

crash

robust

11

nodes_badnumfaces

crash

crash

crash

robust

12

leaves_badcluster

crash

crash

crash

robust

13

leaves_badarea

soft

soft

soft

robust

14

leaves_badfirstbrush

unknown

unknown

unknown

robust

15

leaves_badnumbrushes

unknown

unknown

unknown

robust

16

leaves_badfirstface

crash

crash

crash

robust

17

leaves_badnumfaces

crash

crash

crash

robust

18

leaves_badcontents

crash

crash

crash

unknown

19

brushes_badfirstside

crash

crash

crash

robust

20

brushes_badnumsides

crash

crash

crash

robust

21

leafbrushes_badindices

unknown

unknown

unknown

robust

22

leaffaces_badindices

soft

soft

soft

robust

23

surfedges_badindices

unknown

unknown

unknown

robust

24

edges_badindices

crash

crash

crash

robust

25

faces_badfirstedge

unknown

unknown

unknown

robust

26

faces_toomanyedges

crash

crash

crash

robust

27

faces_toofewedges

unknown

unknown

unknown

robust

28

faces_badplanenum

crash

crash

crash

robust

29

faces_badtexinfo

other
[Graphics corruption]

other
[corrupt]

other
[corrupt]

robust

30

faces_badlightmap

crash

crash

crash

robust

31

brushsides_badplanenum

unknown

unknown

unknown

robust

32

brushsides_badtexinfo

crash

unknown

unknown

robust

33

planes_badtype

unknown

soft

unknown

unknown

34

models_badfirstface

crash

crash

soft

robust

35

models_badnumfaces

crash

crash

soft

robust

36

models_badheadnode

soft

crash

soft

robust

37

models_faceloop

hang

hang

hang

unknown

38

texinfo_badnext

crash

crash

crash

robust

39

texinfo_loopnext

hang

hang

hang

hang

40

texinfo_overflow

other
[Infinite reconnect loop]

unknown

hard

unknown

41

areas_badfirstportal

unknown

unknown

unknown

robust

42

areas_badnumportals

crash

crash

crash

robust

43

areaportals_badportalnum

unknown

unknown

unknown

robust

44

areaportals_badotherarea

unknown

unknown

unknown

robust

45

extents_overflow

hard

hard

other
[corrupt]

unknown

Total number of various responses observed per engine is provided in the table below. There, unstable responses include undefined behaviour (crashes, hangs) and general instability (graphics corruption, infinite reconnect loops). Error responses include both hard and soft errors that cause server to be killed.

Table 4. Total number of responses observed
Engine Unstable Error Robust Unknown

Quake 2

30

4

0

11

R1Q2

27

5

0

13

AprQ2

25

7

0

13

Q2PRO

1

0

39

5

Conclusions

Clearly, original Quake 2 client is the most vulnerable one being unstable in 66% of cases. However, more recent engines like R1Q2 and AprQ2 are hardly any more stable. AprQ2 includes a few more validity checks compared to R1Q2. Q2PRO is the most stable engine, and the only one capable of robustly detecting malformed BSP files without crashing the server.

It is out of scope of this document to evaluate the actual exploitability and severeness of discovered flaws. Surely, undefined behaviour signifies at least denial of service vulnerability. In some cases, it may signify arbitrary code execution vulnerability. For example, testcase #06 triggers a reliable stack-based buffer overflow in vulnerable engines.

Recommendations

Developers of Quake 2 engine forks are welcome to use the provided collection of testcases to check, debug and fix their engines. Testcases for other file formats supported by Quake 2 may be added later.

Until discovered flaws are fixed, it is recommended that users of vulnerable clients double check what server they are connecting to and avoid connecting to unknown/untrusted servers. Blindly copying and pasting server addresses from IRC and using commands like ‘followip’ is particularly dangerous. It might be desirable to recursively make the Quake 2 directory read-only to prevent the server from uploading arbitrary files (remember, setting ‘allow_download’ console variable to 0 is not enough).

Server operators must ensure that they obtain game data from trusted sources. In particular, allowing players to upload arbitrary maps or other files to the server (for example, via anonymous FTP) is unacceptable.