From 5b33dc29fd139aec22ed7ff28bad31dd103b4efc Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Tue, 10 Nov 2026 05:07:36 +0000 Subject: [PATCH] OSS-Fuzz: Add new fuzzer/corpus targets dom html Signed-off-by: Arthur Chan --- sapi/fuzzer/Makefile.frag | 3 + sapi/fuzzer/config.m4 | 5 ++ sapi/fuzzer/dict/dom-html | 58 +++++++++++++++++ sapi/fuzzer/fuzzer-dom-html.c | 85 +++++++++++++++++++++++++ sapi/fuzzer/generate_all.php | 5 ++ sapi/fuzzer/generate_domhtml_corpus.php | 39 ++++++++++++ 6 files changed, 195 insertions(+) create mode 100644 sapi/fuzzer/dict/dom-html create mode 100644 sapi/fuzzer/fuzzer-dom-html.c create mode 100644 sapi/fuzzer/generate_domhtml_corpus.php diff --git a/sapi/fuzzer/Makefile.frag b/sapi/fuzzer/Makefile.frag index c1fa75896912..eeae1eba8809 100644 --- a/sapi/fuzzer/Makefile.frag +++ b/sapi/fuzzer/Makefile.frag @@ -31,3 +31,6 @@ $(SAPI_FUZZER_PATH)/php-fuzz-mbstring: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(P $(SAPI_FUZZER_PATH)/php-fuzz-mbregex: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_FUZZER_MBREGEX_OBJS) $(FUZZER_BUILD) $(PHP_FUZZER_MBREGEX_OBJS) -o $@ + +$(SAPI_FUZZER_PATH)/php-fuzz-dom-html: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_FUZZER_DOM_HTML_OBJS) + $(FUZZER_BUILD) $(PHP_FUZZER_DOM_HTML_OBJS) -o $@ diff --git a/sapi/fuzzer/config.m4 b/sapi/fuzzer/config.m4 index ce045c8962d6..c4ef3b8f0653 100644 --- a/sapi/fuzzer/config.m4 +++ b/sapi/fuzzer/config.m4 @@ -70,5 +70,10 @@ if test "$PHP_FUZZER" != "no"; then fi fi + dnl HTML5 parsing of untrusted documents through the lexbor engine (ext/dom). + if test -n "$enable_dom" && test "$enable_dom" != "no"; then + PHP_FUZZER_TARGET([dom-html], [PHP_FUZZER_DOM_HTML_OBJS]) + fi + PHP_SUBST([PHP_FUZZER_BINARIES]) fi diff --git a/sapi/fuzzer/dict/dom-html b/sapi/fuzzer/dict/dom-html new file mode 100644 index 000000000000..2bbb72733d98 --- /dev/null +++ b/sapi/fuzzer/dict/dom-html @@ -0,0 +1,58 @@ +# Tokens for HTML5 parsing (php-fuzz-dom-html). +"" +"" +"" +". | + +----------------------------------------------------------------------+ + | Authors: arthurscchan | + +----------------------------------------------------------------------+ +*/ + +#include "fuzzer.h" + +#include "main/php.h" +#include
+#include "main/php_main.h" + +#include +#include +#include + +#include "fuzzer-sapi.h" + +/* Large documents mostly slow down fuzzing without reaching new codepaths. */ +#define MAX_SIZE (1 * 1024 * 1024) + +/* createFromString()'s $options accept HTML_PARSE_* bits (LIBXML_NOERROR is + * 1<<5 = 32 in libxml2); 0 is the neutral default and HTML5 parsing is + * error-tolerant, so no suppression bit is needed. */ +#define FUZZER_HTML_OPTIONS 0 + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size > MAX_SIZE || Size < 2) { + return 0; + } + + if (fuzzer_request_startup() == FAILURE) { + return 0; + } + + fuzzer_setup_dummy_frame(); + + zend_string *class_name = zend_string_init(ZEND_STRL("Dom\\HTMLDocument"), 0); + zend_class_entry *ce = zend_lookup_class(class_name); + zend_string_release(class_name); + + if (ce) { + /* Method table entries are stored lowercased; lookups are case-sensitive. */ + zend_function *create = zend_hash_str_find_ptr(&ce->function_table, ZEND_STRL("createfromstring")); + zend_function *save = zend_hash_str_find_ptr(&ce->function_table, ZEND_STRL("savehtml")); + ZEND_ASSERT(create != NULL); + ZEND_ASSERT(save != NULL); + + zval retval, args[3]; + ZVAL_STRINGL(&args[0], (const char *) Data, Size); + ZVAL_LONG(&args[1], FUZZER_HTML_OPTIONS); + ZVAL_NULL(&args[2]); + + /* Parse untrusted bytes into an HTML5 document (lexbor engine). */ + zend_call_known_function(create, NULL, ce, &retval, 3, args, NULL); + + /* Round-trip direction: serialize the parsed document back to HTML. */ + if (Z_TYPE(retval) == IS_OBJECT && save != NULL) { + zval retval2; + zend_call_known_instance_method_with_0_params(save, Z_OBJ_P(&retval), &retval2); + zval_ptr_dtor(&retval2); + } + zval_ptr_dtor(&retval); + zval_ptr_dtor(&args[0]); + + if (EG(exception)) { + zend_clear_exception(); + } + } + + fuzzer_request_shutdown(); + return 0; +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) { + fuzzer_init_php(NULL); + return 0; +} diff --git a/sapi/fuzzer/generate_all.php b/sapi/fuzzer/generate_all.php index 5794bdacc6ba..45eb9bc8fa7b 100644 --- a/sapi/fuzzer/generate_all.php +++ b/sapi/fuzzer/generate_all.php @@ -1,6 +1,7 @@ $maxLen) { continue; } + file_put_contents(sprintf('%s/domhtml_%03d.html', $corpusDir, $n++), $content); + } + } + + /* Hand-built edge cases not present as fixtures. */ + $extra = [ + 't' + . '

café & "quotes" �

', + '
x
' + . 'y', + '
deep
' + . '

unclosed bold both spans', + '' + . '', + "\xEF\xBB\xBF" + . '' . str_repeat('

d
', 8) . '', + ]; + foreach ($extra as $content) { + file_put_contents(sprintf('%s/domhtml_extra_%02d.html', $corpusDir, $n++), $content); + } +}