From 5aa6bfee85377a6919fb314d3e3758bb786b64b4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 15 Jan 2022 06:24:57 +0300 Subject: [PATCH] engine: first attempts on fuzzing the engine --- engine/common/imagelib/img_main.c | 21 ++++++++++++++++ engine/common/soundlib/snd_main.c | 22 +++++++++++++++++ engine/common/tests.h | 17 +++++++------ engine/wscript | 7 ++++++ utils/run-fuzzer/run-fuzzer.c | 34 ++++++++++++++++++++++++++ utils/run-fuzzer/wscript | 40 +++++++++++++++++++++++++++++++ wscript | 13 ++++++++-- 7 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 utils/run-fuzzer/run-fuzzer.c create mode 100644 utils/run-fuzzer/wscript diff --git a/engine/common/imagelib/img_main.c b/engine/common/imagelib/img_main.c index 70e67ce1..1b99e0c0 100644 --- a/engine/common/imagelib/img_main.c +++ b/engine/common/imagelib/img_main.c @@ -566,4 +566,25 @@ void Test_RunImagelib( void ) Z_Free( rgb.buffer ); } +#define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ) \ +{ \ + rgbdata_t *rgb; \ + host.type = HOST_NORMAL; \ + Memory_Init(); \ + Image_Init(); \ + if( target( "#internal", Data, Size )) \ + { \ + rgb = ImagePack(); \ + FS_FreeImage( rgb ); \ + } \ + Image_Shutdown(); \ + return 0; \ +} \ + +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadBMP, Image_LoadBMP ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadPNG, Image_LoadPNG ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadDDS, Image_LoadDDS ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadTGA, Image_LoadTGA ) + #endif /* XASH_ENGINE_TESTS */ diff --git a/engine/common/soundlib/snd_main.c b/engine/common/soundlib/snd_main.c index 265395a5..94b4ff9d 100644 --- a/engine/common/soundlib/snd_main.c +++ b/engine/common/soundlib/snd_main.c @@ -278,3 +278,25 @@ void FS_FreeStream( stream_t *stream ) stream->format->freefunc( stream ); } + +#if XASH_ENGINE_TESTS + +#define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ) \ +{ \ + wavdata_t *wav; \ + host.type = HOST_NORMAL; \ + Memory_Init(); \ + Sound_Init(); \ + if( target( "#internal", Data, Size )) \ + { \ + wav = SoundPack(); \ + FS_FreeSound( wav ); \ + } \ + Sound_Shutdown(); \ + return 0; \ +} \ + +IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadMPG, Sound_LoadMPG ) +IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV ) +#endif diff --git a/engine/common/tests.h b/engine/common/tests.h index d4166974..3ff6e030 100644 --- a/engine/common/tests.h +++ b/engine/common/tests.h @@ -15,21 +15,20 @@ extern struct tests_stats_s tests_stats; x; \ Msg( "Finished " #x "\n" ) -#define TASSERT( exp ) \ - if(!( exp )) \ +#define _TASSERT( exp, msg ) \ + if( exp ) \ { \ tests_stats.failed++; \ - Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ); \ + msg; \ } \ else tests_stats.passed++; +#define TASSERT( exp ) \ + _TASSERT( !(exp), Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ) ) +#define TASSERT_EQi( val1, val2 ) \ + _TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, #val1, #val2 )) #define TASSERT_STR( str1, str2 ) \ - if( Q_strcmp(( str1 ), ( str2 ))) \ - { \ - tests_stats.failed++; \ - Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 )); \ - } \ - else tests_stats.passed++; + _TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 ))) void Test_RunImagelib( void ); void Test_RunLibCommon( void ); diff --git a/engine/wscript b/engine/wscript index cf64f7b4..c1479c45 100644 --- a/engine/wscript +++ b/engine/wscript @@ -32,6 +32,9 @@ def options(opt): grp.add_option('--enable-engine-tests', action = 'store_true', dest = 'ENGINE_TESTS', default = False, help = 'embed tests into the engine, jump into them by -runtests command line switch [default: %default]') + grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False, + help = 'add LLVM libFuzzer [default: %default]' ) + opt.load('sdl2') def configure(conf): @@ -87,6 +90,10 @@ def configure(conf): conf.env.ENGINE_TESTS = conf.options.ENGINE_TESTS + if conf.options.ENGINE_FUZZ: + conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer-no-link') + conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') + conf.define_cond('XASH_ENGINE_TESTS', conf.env.ENGINE_TESTS) conf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING) conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP) diff --git a/utils/run-fuzzer/run-fuzzer.c b/utils/run-fuzzer/run-fuzzer.c new file mode 100644 index 00000000..d0bfae0b --- /dev/null +++ b/utils/run-fuzzer/run-fuzzer.c @@ -0,0 +1,34 @@ +#include +#include +#include + +#if !defined LIB || !defined FUNC +#error +#endif + +typedef int (*FuzzFunc)(const char *Data, size_t Size); + +void *handle = NULL; +FuzzFunc f = NULL; + +int LLVMFuzzerTestOneInput( const char *Data, size_t Size ) +{ + if( !handle ) + handle = dlopen( LIB, RTLD_NOW ); + + if( handle ) + { + if( !f ) + f = dlsym( handle, FUNC ); + + if( f ) + { + return f( Data, Size ); + } + } + + fprintf( stderr, "Fail: %s\n", dlerror() ); + + abort(); + return 0; +} diff --git a/utils/run-fuzzer/wscript b/utils/run-fuzzer/wscript new file mode 100644 index 00000000..11174c8a --- /dev/null +++ b/utils/run-fuzzer/wscript @@ -0,0 +1,40 @@ +#! /usr/bin/env python +# encoding: utf-8 +# a1batross, mittorn, 2018 + +def options(opt): + pass + +def configure(conf): + if conf.options.BUILD_TYPE != 'sanitize': + conf.fatal('useless without -T sanitize') + + if conf.env.COMPILER_CC != 'clang': + conf.fatal('only clang is supported') + + conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer') + conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') + +def add_runner_target(bld, lib, func): + source = bld.path.ant_glob('*.c') + includes = '.' + libs = [ 'DL' ] + + bld( + source = source, + target = 'run-fuzzer-' + func, + features = 'c cprogram', + includes = includes, + use = libs, + defines = ['FUNC="Fuzz_' + func + '"', 'LIB="' + lib + '"'], + install_path = bld.env.BINDIR, + subsystem = bld.env.CONSOLE_SUBSYSTEM + ) + +def build(bld): + add_runner_target(bld, 'libxash.so', 'Sound_LoadMPG') + add_runner_target(bld, 'libxash.so', 'Sound_LoadWAV') + add_runner_target(bld, 'libxash.so', 'Image_LoadBMP') + add_runner_target(bld, 'libxash.so', 'Image_LoadPNG') + add_runner_target(bld, 'libxash.so', 'Image_LoadDDS') + add_runner_target(bld, 'libxash.so', 'Image_LoadTGA') diff --git a/wscript b/wscript index e9ba2d90..8879fbeb 100644 --- a/wscript +++ b/wscript @@ -20,12 +20,13 @@ class Subproject: ignore = False # if true will be ignored, set by user request mandatory = False - def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False): + def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False, fuzzer = False): self.name = name self.dedicated = dedicated self.singlebin = singlebin self.mandatory = mandatory self.utility = utility + self.fuzzer = fuzzer def is_enabled(self, ctx): if not self.mandatory: @@ -47,6 +48,9 @@ class Subproject: if self.utility and not ctx.env.ENABLE_UTILS: return False + if self.fuzzer and not ctx.env.ENABLE_FUZZER: + return False + return True SUBDIRS = [ @@ -60,7 +64,8 @@ SUBDIRS = [ Subproject('stub/client'), Subproject('dllemu'), Subproject('engine', dedicated=False), - Subproject('utils/mdldec', utility=True) + Subproject('utils/mdldec', utility=True), + Subproject('utils/run-fuzzer', fuzzer=True) ] def subdirs(): @@ -98,6 +103,9 @@ def options(opt): grp.add_option('--enable-utils', action = 'store_true', dest = 'ENABLE_UTILS', default = False, help = 'enable building various development utilities [default: %default]') + grp.add_option('--enable-fuzzer', action = 'store_true', dest = 'ENABLE_FUZZER', default = False, + help = 'enable building libFuzzer runner [default: %default]' ) + opt.load('compiler_optimizations subproject') for i in SUBDIRS: @@ -245,6 +253,7 @@ def configure(conf): conf.define('STDINT_H', 'pstdint.h') conf.env.ENABLE_UTILS = conf.options.ENABLE_UTILS + conf.env.ENABLE_FUZZER = conf.options.ENABLE_FUZZER conf.env.DEDICATED = conf.options.DEDICATED conf.env.SINGLE_BINARY = conf.options.SINGLE_BINARY or conf.env.DEDICATED