Skip to content

Commit 417414b

Browse files
committed
Add unserializehash fuzzer.
Unlike the straight unserialize fuzzer, this runs only on HashContexts, and it does an update and finalize on the contexts it creates. With @nikic.
1 parent df8ff45 commit 417414b

File tree

5 files changed

+127
-0
lines changed

5 files changed

+127
-0
lines changed

sapi/fuzzer/Makefile.frag

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ $(SAPI_FUZZER_PATH)/php-fuzz-parser: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_F
88
$(SAPI_FUZZER_PATH)/php-fuzz-unserialize: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_UNSERIALIZE_OBJS)
99
$(FUZZER_BUILD) $(PHP_FUZZER_UNSERIALIZE_OBJS) -o $@
1010

11+
$(SAPI_FUZZER_PATH)/php-fuzz-unserializehash: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_UNSERIALIZEHASH_OBJS)
12+
$(FUZZER_BUILD) $(PHP_FUZZER_UNSERIALIZEHASH_OBJS) -o $@
13+
1114
$(SAPI_FUZZER_PATH)/php-fuzz-json: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_JSON_OBJS)
1215
$(FUZZER_BUILD) $(PHP_FUZZER_JSON_OBJS) -o $@
1316

sapi/fuzzer/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ When running `make` it creates these binaries in `sapi/fuzzer/`:
2222

2323
* `php-fuzz-parser`: Fuzzing language parser and compiler
2424
* `php-fuzz-unserialize`: Fuzzing unserialize() function
25+
* `php-fuzz-unserializehash`: Fuzzing unserialize() for HashContext objects
2526
* `php-fuzz-json`: Fuzzing JSON parser (requires --enable-json)
2627
* `php-fuzz-exif`: Fuzzing `exif_read_data()` function (requires --enable-exif)
2728
* `php-fuzz-mbstring`: fuzzing `mb_ereg[i]()` (requires --enable-mbstring)
@@ -41,6 +42,14 @@ cp -r sapi/fuzzer/corpus/unserialize ./my-unserialize-corpus
4142
sapi/fuzzer/php-fuzz-unserialize -dict=$PWD/sapi/fuzzer/dict/unserialize ./my-unserialize-corpus
4243
```
4344

45+
For the unserializehash fuzzer, generate a corpus of initial hash serializations:
46+
47+
```sh
48+
sapi/cli/php sapi/fuzzer/generate_unserializehash_corpus.php
49+
cp -r sapi/fuzzer/corpus/unserializehash ./my-unserialize-corpus
50+
sapi/fuzzer/php-fuzz-unserializehash ./my-unserialize-corpus
51+
```
52+
4453
For the parser fuzzer, a corpus may be generated from Zend test files:
4554

4655
```sh

sapi/fuzzer/config.m4

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ if test "$PHP_FUZZER" != "no"; then
7676

7777
PHP_FUZZER_TARGET([parser], PHP_FUZZER_PARSER_OBJS)
7878
PHP_FUZZER_TARGET([unserialize], PHP_FUZZER_UNSERIALIZE_OBJS)
79+
PHP_FUZZER_TARGET([unserializehash], PHP_FUZZER_UNSERIALIZEHASH_OBJS)
7980
PHP_FUZZER_TARGET([json], PHP_FUZZER_JSON_OBJS)
8081

8182
if test -n "$enable_exif" && test "$enable_exif" != "no"; then

sapi/fuzzer/fuzzer-unserializehash.c

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| http://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
*/
14+
15+
16+
#include "fuzzer.h"
17+
18+
#include "Zend/zend.h"
19+
#include "main/php_config.h"
20+
#include "main/php_main.h"
21+
22+
#include <stdio.h>
23+
#include <stdint.h>
24+
#include <stdlib.h>
25+
26+
#include "fuzzer-sapi.h"
27+
28+
#include "ext/standard/php_var.h"
29+
30+
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t FullSize) {
31+
zend_execute_data execute_data;
32+
zend_function func;
33+
const uint8_t *Start = memchr(Data, '|', FullSize);
34+
if (!Start) {
35+
return 0;
36+
}
37+
++Start;
38+
39+
size_t Size = (Data + FullSize) - Start;
40+
unsigned char *orig_data = malloc(Size+1);
41+
memcpy(orig_data, Start, Size);
42+
orig_data[Size] = '\0';
43+
44+
if (fuzzer_request_startup()==FAILURE) {
45+
return 0;
46+
}
47+
48+
/* Set up a dummy stack frame so that exceptions may be thrown. */
49+
{
50+
memset(&execute_data, 0, sizeof(zend_execute_data));
51+
memset(&func, 0, sizeof(zend_function));
52+
53+
func.type = ZEND_INTERNAL_FUNCTION;
54+
func.common.function_name = ZSTR_EMPTY_ALLOC();
55+
execute_data.func = &func;
56+
EG(current_execute_data) = &execute_data;
57+
}
58+
59+
{
60+
const unsigned char *data = orig_data;
61+
zval result;
62+
ZVAL_UNDEF(&result);
63+
64+
php_unserialize_data_t var_hash;
65+
PHP_VAR_UNSERIALIZE_INIT(var_hash);
66+
php_var_unserialize(&result, (const unsigned char **) &data, data + Size, &var_hash);
67+
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
68+
69+
if (Z_TYPE(result) == IS_OBJECT
70+
&& zend_string_equals_literal(Z_OBJCE(result)->name, "HashContext")) {
71+
zval args[2];
72+
ZVAL_COPY_VALUE(&args[0], &result);
73+
ZVAL_STRINGL(&args[1], (char *) Data, (Start - Data) - 1);
74+
fuzzer_call_php_func_zval("hash_update", 2, args);
75+
zval_ptr_dtor(&args[1]);
76+
fuzzer_call_php_func_zval("hash_final", 1, args);
77+
}
78+
79+
zval_ptr_dtor(&result);
80+
81+
/* Destroy any thrown exception. */
82+
if (EG(exception)) {
83+
zend_object_release(EG(exception));
84+
EG(exception) = NULL;
85+
}
86+
}
87+
88+
/* Unserialize may create circular structure. Make sure we free them.
89+
* Two calls are performed to handle objects with destructors. */
90+
zend_gc_collect_cycles();
91+
zend_gc_collect_cycles();
92+
php_request_shutdown(NULL);
93+
94+
free(orig_data);
95+
96+
return 0;
97+
}
98+
99+
int LLVMFuzzerInitialize(int *argc, char ***argv) {
100+
fuzzer_init_php();
101+
102+
/* fuzzer_shutdown_php(); */
103+
return 0;
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
$corpusDir = __DIR__ . '/corpus/unserializehash';
4+
@mkdir($corpusDir);
5+
6+
foreach (hash_algos() as $algo) {
7+
$ctx = hash_init($algo);
8+
$algx = preg_replace('/[^-_a-zA-Z0-9]/', '_', $algo);
9+
file_put_contents($corpusDir . '/' . $algx, "x|" . serialize($ctx));
10+
}

0 commit comments

Comments
 (0)