Skip to content

Commit

Permalink
Various improvements to fuzzer SAPIs
Browse files Browse the repository at this point in the history
  • Loading branch information
nikic committed Sep 16, 2019
1 parent 41f4564 commit c4e2ca6
Show file tree
Hide file tree
Showing 25 changed files with 320 additions and 100 deletions.
12 changes: 12 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,18 @@ else
ZEND_DEBUG=no
fi

PHP_ARG_ENABLE([debug-assertions],
[whether to enable debug assertions in release mode],
[AS_HELP_STRING([--enable-debug-assertions],
[Compile with debug assertions even in release mode])],
[no],
[no])

if test "$PHP_DEBUG_ASSERTIONS" = "yes"; then
PHP_DEBUG=1
ZEND_DEBUG=yes
fi

PHP_ARG_ENABLE([rtld-now],
[whether to dlopen extensions with RTLD_NOW instead of RTLD_LAZY],
[AS_HELP_STRING([--enable-rtld-now],
Expand Down
13 changes: 0 additions & 13 deletions sapi/fuzzer/README

This file was deleted.

50 changes: 50 additions & 0 deletions sapi/fuzzer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Fuzzing SAPI for PHP
--------------------

The following `./configure` options can be used to enable the fuzzing SAPI, as well as all availablefuzzers. If you don't build the exif/json/mbstring extensions, fuzzers for these extensions will not be built.

```sh
./configure \
--enable-fuzzer \
--with-pic \
--enable-debug-assertions \
--enable-exif \
--enable-json \
--enable-mbstring
```

The `--with-pic` option is required to avoid a linking failure. The `--enable-debug-assertions` option can be used to enable debug assertions despite the use of a release build.

You will need a recent version of clang that supports the `-fsanitize=fuzzer-no-link` option.

When running `make` it creates these binaries in `sapi/fuzzer/`:

* `php-fuzz-parser`: Fuzzing language parser and compiler
* `php-fuzz-unserialize`: Fuzzing unserialize() function
* `php-fuzz-json`: Fuzzing JSON parser (requires --enable-json)
* `php-fuzz-exif`: Fuzzing `exif_read_data()` function (requires --enable-exif)
* `php-fuzz-mbstring`: fuzzing `mb_ereg[i]()` (requires --enable-mbstring)

Some fuzzers have a seed corpus in `sapi/fuzzer/corpus`. You can use it as follows:

```sh
cp -r sapi/fuzzer/corpus/exif ./my-exif-corpus
sapi/fuzzer/php-fuzz-exif ./my-exif-corpus
```

For the unserialize fuzzer, a dictionary of internal classes should be generated first:

```sh
sapi/cli/php sapi/fuzzer/corpus/generate_unserialize_dict.php
cp -r sapi/fuzzer/corpus/unserialize ./my-unserialize-corpus
sapi/fuzzer/php-fuzz-unserialize -dict=$PWD/sapi/fuzzer/corpus/unserialize.dict ./my-unserialize-corpus
```

For the parser fuzzer, a corpus may be generated from Zend test files:

```sh
sapi/cli/php sapi/fuzzer/corpus/generate_parser_corpus.php
mkdir ./my-parser-corpus
sapi/fuzzer/php-fuzz-parser -merge=1 ./my-parser-corpus sapi/fuzzer/corpus/parser
sapi/fuzzer/php-fuzz-parser -only_ascii=1 ./my-parser-corpus
```
31 changes: 22 additions & 9 deletions sapi/fuzzer/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ dnl
AC_DEFUN([PHP_FUZZER_TARGET], [
PHP_FUZZER_BINARIES="$PHP_FUZZER_BINARIES $SAPI_FUZZER_PATH/php-fuzz-$1"
PHP_SUBST($2)
PHP_ADD_SOURCES_X([sapi/fuzzer],[fuzzer-$1.c fuzzer-sapi.c],[],$2)
PHP_ADD_SOURCES_X([sapi/fuzzer],[fuzzer-$1.c],[],$2)
$2="[$]$2 $FUZZER_COMMON_OBJS"
])

if test "$PHP_FUZZER" != "no"; then
Expand All @@ -24,14 +25,20 @@ if test "$PHP_FUZZER" != "no"; then
SAPI_FUZZER_PATH=sapi/fuzzer
PHP_SUBST(SAPI_FUZZER_PATH)
if test -z "$LIB_FUZZING_ENGINE"; then
FUZZING_LIB="-lFuzzer"
FUZZING_LIB="-fsanitize=fuzzer"
FUZZING_CC="$CC"
AX_CHECK_COMPILE_FLAG([-fsanitize=address], [
CFLAGS="$CFLAGS -fsanitize=address"
CXXFLAGS="$CXXFLAGS -fsanitize=address"
LDFLAGS="$LDFLAGS -fsanitize=address"
dnl Don't include -fundefined in CXXFLAGS, because that would also require linking
dnl with a C++ compiler.
AX_CHECK_COMPILE_FLAG([-fsanitize=fuzzer-no-link], [
CFLAGS="$CFLAGS -fsanitize=fuzzer-no-link,address"
dnl Disable object-size sanitizer, because it is incompatible with our zend_function
dnl union, and this can't be easily fixed.
dnl We need to specify -fno-sanitize-recover=undefined here, otherwise ubsan warnings
dnl will not be considered failures by the fuzzer.
CFLAGS="$CFLAGS -fsanitize=undefined -fno-sanitize=object-size -fno-sanitize-recover=undefined"
CXXFLAGS="$CXXFLAGS -fsanitize=fuzzer-no-link,address"
],[
AC_MSG_ERROR(compiler doesn't support -fsanitize flags)
AC_MSG_ERROR(Compiler doesn't support -fsanitize=fuzzer-no-link)
])
else
FUZZING_LIB="-lFuzzingEngine"
Expand All @@ -44,15 +51,21 @@ if test "$PHP_FUZZER" != "no"; then

PHP_ADD_BUILD_DIR([sapi/fuzzer])
PHP_FUZZER_BINARIES=""
PHP_BINARIES="$PHP_BINARIES fuzzer"
PHP_INSTALLED_SAPIS="$PHP_INSTALLED_SAPIS fuzzer"

PHP_ADD_SOURCES_X([sapi/fuzzer], [fuzzer-sapi.c], [], FUZZER_COMMON_OBJS)

PHP_FUZZER_TARGET([parser], PHP_FUZZER_PARSER_OBJS)
PHP_FUZZER_TARGET([unserialize], PHP_FUZZER_UNSERIALIZE_OBJS)
PHP_FUZZER_TARGET([exif], PHP_FUZZER_EXIF_OBJS)

if test -n "$enable_json" && test "$enable_json" != "no"; then
dnl json extension is enabled by default
if (test -n "$enable_json" && test "$enable_json" != "no") || test -z "$PHP_ENABLE_ALL"; then
PHP_FUZZER_TARGET([json], PHP_FUZZER_JSON_OBJS)
fi
if test -n "$enable_exif" && test "$enable_exif" != "no"; then
PHP_FUZZER_TARGET([exif], PHP_FUZZER_EXIF_OBJS)
fi
if test -n "$enable_mbstring" && test "$enable_mbstring" != "no"; then
PHP_FUZZER_TARGET([mbstring], PHP_FUZZER_MBSTRING_OBJS)
fi
Expand Down
22 changes: 22 additions & 0 deletions sapi/fuzzer/corpus/generate_parser_corpus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

$testsDir = __DIR__ . '/../../../Zend/tests/';
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($testsDir),
RecursiveIteratorIterator::LEAVES_ONLY
);

$corpusDir = __DIR__ . '/parser';
@mkdir($corpusDir);

foreach ($it as $file) {
if (!preg_match('/\.phpt$/', $file)) continue;
$code = file_get_contents($file);
if (!preg_match('/--FILE--(.*)--EXPECT/s', $code, $matches)) continue;
$code = $matches[1];

$outFile = str_replace($testsDir, '', $file);
$outFile = str_replace('/', '_', $outFile);
$outFile = $corpusDir . '/' . $outFile;
file_put_contents($outFile, $code);
}
9 changes: 9 additions & 0 deletions sapi/fuzzer/corpus/generate_unserialize_dict.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

$dict = "";
foreach (get_declared_classes() as $class) {
$len = strlen($class);
$dict .= "\"$len:\\\"$class\\\"\"\n";
}

file_put_contents(__DIR__ . "/unserialize.dict", $dict);
85 changes: 85 additions & 0 deletions sapi/fuzzer/corpus/parser.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"exit"
"die"
"fn"
"function"
"const"
"return"
"yield"
"yield from"
"try"
"catch"
"finally"
"throw"
"if"
"elseif"
"endif"
"else"
"while"
"endwhile"
"do"
"for"
"endfor"
"foreach"
"endforeach"
"declare"
"enddeclare"
"instanceof"
"as"
"switch"
"endswitch"
"case"
"default"
"break"
"continue"
"goto"
"echo"
"print"
"class"
"interface"
"trait"
"extends"
"implements"
"new"
"clone"
"var"
"int"
"integer"
"float"
"double"
"real"
"string"
"binary"
"array"
"object"
"bool"
"boolean"
"unset"
"eval"
"include"
"include_once"
"require"
"require_once"
"namespace"
"use"
"insteadof"
"global"
"isset"
"empty"
"__halt_compiler"
"static"
"abstract"
"final"
"private"
"protected"
"public"
"unset"
"list"
"callable"
"__class__"
"__trait__"
"__function__"
"__method__"
"__line__"
"__file__"
"__dir__"
"__namespace__"
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/__serialize_007
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
O:13:"ArrayIterator":2:{i:0;i:0;s:1:"x";R:2;}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug7131
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C:11:"ArrayObject":11:{x:i:0;r:3;X}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug71313
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C:16:"SplObjectStorage":113:{x:i:2;O:8:"stdClass":0:{},a:2:{s:4:"prev";i:2;s:4:"next";O:8:"stdClass":0:{}};r:7;,R:2;s:4:"next";;r:3;};m:a:0:{}}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug73144_1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a:2:{i:0;O:1:"0":2:0s:1:"0";i:0;s:1:"0";a:1:{i:0;C:11:"ArrayObject":7:{x:i:0;r}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug73144_2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C:11:"ArrayObject":34:{x:i:1;O:8:"stdClass":1:{};m:a:0:{}}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug73825
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
O:8:"00000000":
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug74101
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
O:9:"Exception":799999999999999999999999999997:0i:0;a:0:{}i:2;i:0;i:0;R:2;
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug74103
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a:7:{i:0;i:04;s:1:"a";i:2;i:9617006;i:4;s:1:"a";i:4;s:1:"a";R:5;s:1:"7";R:3;s:1:"a";R:5;;s:18;}}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug74111
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
O:8:"stdClass":00000000
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug74614
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a:3020000000000000000000000000000001:{i:0;a:0:{}i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;R:2;}
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/unserialize/bug75054
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a:9:{i:0;s:4:"0000";i:0;s:4:"0000";i:0;R:2;s:4:"5003";R:2;s:4:"0000";R:2;s:4:"0000";R:2;s:4:"000";R:2;s:4:"0000";d:0;s:4:"0000";a:9:{s:4:"0000";
8 changes: 6 additions & 2 deletions sapi/fuzzer/fuzzer-exif.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
#include "fuzzer-sapi.h"

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
#if HAVE_EXIF
char *filename;
int filedes;

if (php_request_startup()==FAILURE) {
php_module_shutdown();
if (fuzzer_request_startup() == FAILURE) {
return 0;
}

Expand All @@ -54,6 +54,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
php_request_shutdown(NULL);

return 0;
#else
fprintf(stderr, "\n\nERROR:\nPHP built without EXIF, recompile with --enable-exif to use this fuzzer\n");
exit(1);
#endif
}

int LLVMFuzzerInitialize(int *argc, char ***argv) {
Expand Down
9 changes: 4 additions & 5 deletions sapi/fuzzer/fuzzer-json.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,17 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
memcpy(data, Data, Size);
data[Size] = '\0';

if (php_request_startup()==FAILURE) {
php_module_shutdown();
if (fuzzer_request_startup() == FAILURE) {
return 0;
}

for (int option = 0; option <=1; ++option) {
zval result;
php_json_parser parser;
php_json_parser_init(&parser, &result, data, Size, option, 10);
php_json_yyparse(&parser);

ZVAL_UNDEF(&result);
if (php_json_yyparse(&parser) == SUCCESS) {
zval_ptr_dtor(&result);
}
}

php_request_shutdown(NULL);
Expand Down
3 changes: 1 addition & 2 deletions sapi/fuzzer/fuzzer-mbstring.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
memcpy(data, Data, Size);
data[Size] = '\0';

if (php_request_startup()==FAILURE) {
php_module_shutdown();
if (fuzzer_request_startup() == FAILURE) {
return 0;
}

Expand Down
Loading

0 comments on commit c4e2ca6

Please sign in to comment.