Skip to content

Commit c4e2ca6

Browse files
committed
Various improvements to fuzzer SAPIs
1 parent 41f4564 commit c4e2ca6

25 files changed

+320
-100
lines changed

configure.ac

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,18 @@ else
897897
ZEND_DEBUG=no
898898
fi
899899

900+
PHP_ARG_ENABLE([debug-assertions],
901+
[whether to enable debug assertions in release mode],
902+
[AS_HELP_STRING([--enable-debug-assertions],
903+
[Compile with debug assertions even in release mode])],
904+
[no],
905+
[no])
906+
907+
if test "$PHP_DEBUG_ASSERTIONS" = "yes"; then
908+
PHP_DEBUG=1
909+
ZEND_DEBUG=yes
910+
fi
911+
900912
PHP_ARG_ENABLE([rtld-now],
901913
[whether to dlopen extensions with RTLD_NOW instead of RTLD_LAZY],
902914
[AS_HELP_STRING([--enable-rtld-now],

sapi/fuzzer/README

Lines changed: 0 additions & 13 deletions
This file was deleted.

sapi/fuzzer/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Fuzzing SAPI for PHP
2+
--------------------
3+
4+
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.
5+
6+
```sh
7+
./configure \
8+
--enable-fuzzer \
9+
--with-pic \
10+
--enable-debug-assertions \
11+
--enable-exif \
12+
--enable-json \
13+
--enable-mbstring
14+
```
15+
16+
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.
17+
18+
You will need a recent version of clang that supports the `-fsanitize=fuzzer-no-link` option.
19+
20+
When running `make` it creates these binaries in `sapi/fuzzer/`:
21+
22+
* `php-fuzz-parser`: Fuzzing language parser and compiler
23+
* `php-fuzz-unserialize`: Fuzzing unserialize() function
24+
* `php-fuzz-json`: Fuzzing JSON parser (requires --enable-json)
25+
* `php-fuzz-exif`: Fuzzing `exif_read_data()` function (requires --enable-exif)
26+
* `php-fuzz-mbstring`: fuzzing `mb_ereg[i]()` (requires --enable-mbstring)
27+
28+
Some fuzzers have a seed corpus in `sapi/fuzzer/corpus`. You can use it as follows:
29+
30+
```sh
31+
cp -r sapi/fuzzer/corpus/exif ./my-exif-corpus
32+
sapi/fuzzer/php-fuzz-exif ./my-exif-corpus
33+
```
34+
35+
For the unserialize fuzzer, a dictionary of internal classes should be generated first:
36+
37+
```sh
38+
sapi/cli/php sapi/fuzzer/corpus/generate_unserialize_dict.php
39+
cp -r sapi/fuzzer/corpus/unserialize ./my-unserialize-corpus
40+
sapi/fuzzer/php-fuzz-unserialize -dict=$PWD/sapi/fuzzer/corpus/unserialize.dict ./my-unserialize-corpus
41+
```
42+
43+
For the parser fuzzer, a corpus may be generated from Zend test files:
44+
45+
```sh
46+
sapi/cli/php sapi/fuzzer/corpus/generate_parser_corpus.php
47+
mkdir ./my-parser-corpus
48+
sapi/fuzzer/php-fuzz-parser -merge=1 ./my-parser-corpus sapi/fuzzer/corpus/parser
49+
sapi/fuzzer/php-fuzz-parser -only_ascii=1 ./my-parser-corpus
50+
```

sapi/fuzzer/config.m4

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ dnl
1414
AC_DEFUN([PHP_FUZZER_TARGET], [
1515
PHP_FUZZER_BINARIES="$PHP_FUZZER_BINARIES $SAPI_FUZZER_PATH/php-fuzz-$1"
1616
PHP_SUBST($2)
17-
PHP_ADD_SOURCES_X([sapi/fuzzer],[fuzzer-$1.c fuzzer-sapi.c],[],$2)
17+
PHP_ADD_SOURCES_X([sapi/fuzzer],[fuzzer-$1.c],[],$2)
18+
$2="[$]$2 $FUZZER_COMMON_OBJS"
1819
])
1920

2021
if test "$PHP_FUZZER" != "no"; then
@@ -24,14 +25,20 @@ if test "$PHP_FUZZER" != "no"; then
2425
SAPI_FUZZER_PATH=sapi/fuzzer
2526
PHP_SUBST(SAPI_FUZZER_PATH)
2627
if test -z "$LIB_FUZZING_ENGINE"; then
27-
FUZZING_LIB="-lFuzzer"
28+
FUZZING_LIB="-fsanitize=fuzzer"
2829
FUZZING_CC="$CC"
29-
AX_CHECK_COMPILE_FLAG([-fsanitize=address], [
30-
CFLAGS="$CFLAGS -fsanitize=address"
31-
CXXFLAGS="$CXXFLAGS -fsanitize=address"
32-
LDFLAGS="$LDFLAGS -fsanitize=address"
30+
dnl Don't include -fundefined in CXXFLAGS, because that would also require linking
31+
dnl with a C++ compiler.
32+
AX_CHECK_COMPILE_FLAG([-fsanitize=fuzzer-no-link], [
33+
CFLAGS="$CFLAGS -fsanitize=fuzzer-no-link,address"
34+
dnl Disable object-size sanitizer, because it is incompatible with our zend_function
35+
dnl union, and this can't be easily fixed.
36+
dnl We need to specify -fno-sanitize-recover=undefined here, otherwise ubsan warnings
37+
dnl will not be considered failures by the fuzzer.
38+
CFLAGS="$CFLAGS -fsanitize=undefined -fno-sanitize=object-size -fno-sanitize-recover=undefined"
39+
CXXFLAGS="$CXXFLAGS -fsanitize=fuzzer-no-link,address"
3340
],[
34-
AC_MSG_ERROR(compiler doesn't support -fsanitize flags)
41+
AC_MSG_ERROR(Compiler doesn't support -fsanitize=fuzzer-no-link)
3542
])
3643
else
3744
FUZZING_LIB="-lFuzzingEngine"
@@ -44,15 +51,21 @@ if test "$PHP_FUZZER" != "no"; then
4451

4552
PHP_ADD_BUILD_DIR([sapi/fuzzer])
4653
PHP_FUZZER_BINARIES=""
54+
PHP_BINARIES="$PHP_BINARIES fuzzer"
4755
PHP_INSTALLED_SAPIS="$PHP_INSTALLED_SAPIS fuzzer"
4856

57+
PHP_ADD_SOURCES_X([sapi/fuzzer], [fuzzer-sapi.c], [], FUZZER_COMMON_OBJS)
58+
4959
PHP_FUZZER_TARGET([parser], PHP_FUZZER_PARSER_OBJS)
5060
PHP_FUZZER_TARGET([unserialize], PHP_FUZZER_UNSERIALIZE_OBJS)
51-
PHP_FUZZER_TARGET([exif], PHP_FUZZER_EXIF_OBJS)
5261

53-
if test -n "$enable_json" && test "$enable_json" != "no"; then
62+
dnl json extension is enabled by default
63+
if (test -n "$enable_json" && test "$enable_json" != "no") || test -z "$PHP_ENABLE_ALL"; then
5464
PHP_FUZZER_TARGET([json], PHP_FUZZER_JSON_OBJS)
5565
fi
66+
if test -n "$enable_exif" && test "$enable_exif" != "no"; then
67+
PHP_FUZZER_TARGET([exif], PHP_FUZZER_EXIF_OBJS)
68+
fi
5669
if test -n "$enable_mbstring" && test "$enable_mbstring" != "no"; then
5770
PHP_FUZZER_TARGET([mbstring], PHP_FUZZER_MBSTRING_OBJS)
5871
fi
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
$testsDir = __DIR__ . '/../../../Zend/tests/';
4+
$it = new RecursiveIteratorIterator(
5+
new RecursiveDirectoryIterator($testsDir),
6+
RecursiveIteratorIterator::LEAVES_ONLY
7+
);
8+
9+
$corpusDir = __DIR__ . '/parser';
10+
@mkdir($corpusDir);
11+
12+
foreach ($it as $file) {
13+
if (!preg_match('/\.phpt$/', $file)) continue;
14+
$code = file_get_contents($file);
15+
if (!preg_match('/--FILE--(.*)--EXPECT/s', $code, $matches)) continue;
16+
$code = $matches[1];
17+
18+
$outFile = str_replace($testsDir, '', $file);
19+
$outFile = str_replace('/', '_', $outFile);
20+
$outFile = $corpusDir . '/' . $outFile;
21+
file_put_contents($outFile, $code);
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
$dict = "";
4+
foreach (get_declared_classes() as $class) {
5+
$len = strlen($class);
6+
$dict .= "\"$len:\\\"$class\\\"\"\n";
7+
}
8+
9+
file_put_contents(__DIR__ . "/unserialize.dict", $dict);

sapi/fuzzer/corpus/parser.dict

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"exit"
2+
"die"
3+
"fn"
4+
"function"
5+
"const"
6+
"return"
7+
"yield"
8+
"yield from"
9+
"try"
10+
"catch"
11+
"finally"
12+
"throw"
13+
"if"
14+
"elseif"
15+
"endif"
16+
"else"
17+
"while"
18+
"endwhile"
19+
"do"
20+
"for"
21+
"endfor"
22+
"foreach"
23+
"endforeach"
24+
"declare"
25+
"enddeclare"
26+
"instanceof"
27+
"as"
28+
"switch"
29+
"endswitch"
30+
"case"
31+
"default"
32+
"break"
33+
"continue"
34+
"goto"
35+
"echo"
36+
"print"
37+
"class"
38+
"interface"
39+
"trait"
40+
"extends"
41+
"implements"
42+
"new"
43+
"clone"
44+
"var"
45+
"int"
46+
"integer"
47+
"float"
48+
"double"
49+
"real"
50+
"string"
51+
"binary"
52+
"array"
53+
"object"
54+
"bool"
55+
"boolean"
56+
"unset"
57+
"eval"
58+
"include"
59+
"include_once"
60+
"require"
61+
"require_once"
62+
"namespace"
63+
"use"
64+
"insteadof"
65+
"global"
66+
"isset"
67+
"empty"
68+
"__halt_compiler"
69+
"static"
70+
"abstract"
71+
"final"
72+
"private"
73+
"protected"
74+
"public"
75+
"unset"
76+
"list"
77+
"callable"
78+
"__class__"
79+
"__trait__"
80+
"__function__"
81+
"__method__"
82+
"__line__"
83+
"__file__"
84+
"__dir__"
85+
"__namespace__"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
O:13:"ArrayIterator":2:{i:0;i:0;s:1:"x";R:2;}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
C:11:"ArrayObject":11:{x:i:0;r:3;X}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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:{}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
C:11:"ArrayObject":34:{x:i:1;O:8:"stdClass":1:{};m:a:0:{}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
O:8:"00000000":
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
O:9:"Exception":799999999999999999999999999997:0i:0;a:0:{}i:2;i:0;i:0;R:2;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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;}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
O:8:"stdClass":00000000
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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;}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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";

sapi/fuzzer/fuzzer-exif.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
#include "fuzzer-sapi.h"
3434

3535
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
36+
#if HAVE_EXIF
3637
char *filename;
3738
int filedes;
3839

39-
if (php_request_startup()==FAILURE) {
40-
php_module_shutdown();
40+
if (fuzzer_request_startup() == FAILURE) {
4141
return 0;
4242
}
4343

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

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

5963
int LLVMFuzzerInitialize(int *argc, char ***argv) {

sapi/fuzzer/fuzzer-json.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,17 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
4141
memcpy(data, Data, Size);
4242
data[Size] = '\0';
4343

44-
if (php_request_startup()==FAILURE) {
45-
php_module_shutdown();
44+
if (fuzzer_request_startup() == FAILURE) {
4645
return 0;
4746
}
4847

4948
for (int option = 0; option <=1; ++option) {
5049
zval result;
5150
php_json_parser parser;
5251
php_json_parser_init(&parser, &result, data, Size, option, 10);
53-
php_json_yyparse(&parser);
54-
55-
ZVAL_UNDEF(&result);
52+
if (php_json_yyparse(&parser) == SUCCESS) {
53+
zval_ptr_dtor(&result);
54+
}
5655
}
5756

5857
php_request_shutdown(NULL);

sapi/fuzzer/fuzzer-mbstring.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
3636
memcpy(data, Data, Size);
3737
data[Size] = '\0';
3838

39-
if (php_request_startup()==FAILURE) {
40-
php_module_shutdown();
39+
if (fuzzer_request_startup() == FAILURE) {
4140
return 0;
4241
}
4342

0 commit comments

Comments
 (0)