Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ext/bcmath: Added fuzzer for divide #18045

Open
wants to merge 10 commits into
base: PHP-8.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sapi/fuzzer/Makefile.frag
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ $(SAPI_FUZZER_PATH)/php-fuzz-mbstring: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP

$(SAPI_FUZZER_PATH)/php-fuzz-mbregex: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_MBREGEX_OBJS)
$(FUZZER_BUILD) $(PHP_FUZZER_MBREGEX_OBJS) -o $@

$(SAPI_FUZZER_PATH)/php-fuzz-bcmath: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(PHP_FUZZER_BCMATH_OBJS)
$(FUZZER_BUILD) $(PHP_FUZZER_BCMATH_OBJS) -o $@
1 change: 1 addition & 0 deletions sapi/fuzzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ When running `make` it creates these binaries in `sapi/fuzzer/`:
* `php-fuzz-execute`: Fuzzing the executor
* `php-fuzz-function-jit`: Fuzzing the function JIT (requires --enable-opcache)
* `php-fuzz-tracing-jit`: Fuzzing the tracing JIT (requires --enable-opcache)
* `php-fuzz-bcmath`: Fuzzing division (requires --enable-bcmath)

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

Expand Down
1 change: 1 addition & 0 deletions sapi/fuzzer/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ if test "$PHP_FUZZER" != "no"; then
PHP_FUZZER_TARGET([unserialize], [PHP_FUZZER_UNSERIALIZE_OBJS])
PHP_FUZZER_TARGET([unserializehash], [PHP_FUZZER_UNSERIALIZEHASH_OBJS])
PHP_FUZZER_TARGET([json], [PHP_FUZZER_JSON_OBJS])
PHP_FUZZER_TARGET([bcmath], [PHP_FUZZER_BCMATH_OBJS])

if test -n "$enable_exif" && test "$enable_exif" != "no"; then
PHP_FUZZER_TARGET([exif], [PHP_FUZZER_EXIF_OBJS])
Expand Down
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/bcmath/1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
15,7,0
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/bcmath/2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
14.14,9,10
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/bcmath/3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.23456789,0.56,10
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/bcmath/4
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.00123456789,0.001,10
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/bcmath/5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12345.6789,100,2
1 change: 1 addition & 0 deletions sapi/fuzzer/corpus/bcmath/6
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12345.6,0.00001,20
165 changes: 165 additions & 0 deletions sapi/fuzzer/fuzzer-bcmath.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Saki Takamachi <[email protected]> |
+----------------------------------------------------------------------+
*/



#include "fuzzer.h"

#include "Zend/zend.h"
#include <main/php_config.h>
#include "main/php_main.h"

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include "fuzzer-sapi.h"

zend_long char_to_size_t(char *c) {
zend_long ret = 0;
if (*c >= '0' && *c <= '9') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also cause overflow

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 82ed67d
Parsing scale was left unfinished code...

ret *= 10;
ret += *c - '0';
}
return ret;
}

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
/* num1,num2,scale */
const uint8_t *Comma1 = memchr(Data, ',', Size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please just use snake_case instead of CamelCase for variables

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1fb2cd1

if (!Comma1) {
return 0;
}

size_t dividend_len = Comma1 - Data;
char *dividend_str = estrndup((char *) Data, dividend_len);
Data = Comma1 + 1;
Size -= dividend_len + 1;

const uint8_t *Comma2 = memchr(Data, ',', Size);
if (!Comma2) {
efree(dividend_str);
return 0;
}

size_t divisor_len = Comma2 - Data;
char *divisor_str = estrndup((char *) Data, divisor_len);
Data = Comma2 + 1;
Size -= divisor_len + 1;

char *scale_str = malloc(Size + 1);
memcpy(scale_str, Data, Size);
scale_str[Size] = '\0';

zend_long scale = char_to_size_t(scale_str);
free(scale_str);

if (fuzzer_request_startup() == FAILURE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen earlier to not leak memory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed unnecessary memory allocations and relocated code.
How about the code now?
e1fa2fa

return 0;
}

fuzzer_setup_dummy_frame();

zval result;
ZVAL_UNDEF(&result);

zval args[4];
ZVAL_COPY_VALUE(&args[0], &result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very confused by this line, fuzzer_call_php_func_zval uses a local zval for the result. You only need to pass 3 arguments as far as I can tell.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5341ef7

ZVAL_STRINGL(&args[1], dividend_str, dividend_len);
ZVAL_STRINGL(&args[2], divisor_str, divisor_len);
ZVAL_LONG(&args[3], scale);

fuzzer_call_php_func_zval("bcdiv", 4, args);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to extend the fuzzer to pick one of a few bc function at random (e.g. bcadd, bcsub, ...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in bd630e3


zval_ptr_dtor(&result);
zval_ptr_dtor(&args[1]);
zval_ptr_dtor(&args[2]);
efree(dividend_str);
efree(divisor_str);

fuzzer_request_shutdown();

return 0;
}

#define BUF_SIZE 128

static inline bool rand_bool() {
return rand() & 1;
}

static inline size_t generate_random_num_fraction(char *buf, size_t len) {
int zeros = rand() % 10;
for (int i = 0; i < zeros; i++) {
buf[len] = '0';
len++;
}
len += snprintf(buf + len, BUF_SIZE - len, "%ld", random());
return len;
}

static inline size_t generate_random_num(char *buf, size_t len) {
if (rand_bool()) {
/* num < 1 */
buf[len] = '0';
buf[len + 1]= '.';
len += 2;
/* fraction */
len = generate_random_num_fraction(buf, len);
} else {
/* integer */
len += snprintf(buf + len, BUF_SIZE - len, "%ld", random());
if (rand_bool()) {
/* fraction */
buf[len] = '.';
len++;
len = generate_random_num_fraction(buf, len);
}
}

return len;
}

size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure a custom mutator is the best approach. You're also not really mutating the buffer here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to let the fuzzer mutate the input but just choose to interpret the raw bytes as numbers?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nielsdos

Thanks, does that mean accepting the mutated input as is? Or does it mean, for example, taking it, chopping it into 64-bit chunks, interpreting them as integers, converting them to numeric characters, and concatenating them?

I had trouble creating numeric data with no decimal points or just one, so I used a custom mutator.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it so that the mutated input is entered as is.
e1fa2fa
13319aa

char buf[BUF_SIZE];
size_t len = 0;

/* num1 */
len = generate_random_num(buf, len);
buf[len] = ',';
len++;

/* num2 */
len = generate_random_num(buf, len);
buf[len] = ',';
len++;

/* scale */
len += snprintf(buf + len, BUF_SIZE - len, "%d", rand() % 10);

if (len > MaxSize) {
return 0;
}
memcpy(Data, buf, len);
return len;
}

int LLVMFuzzerInitialize(int *argc, char ***argv) {
fuzzer_init_php(NULL);

/* fuzzer_shutdown_php(); */
return 0;
}
Loading