diff --git a/sapi/fuzzer/Makefile.frag b/sapi/fuzzer/Makefile.frag
index c1fa75896912..f10596fd265f 100644
--- a/sapi/fuzzer/Makefile.frag
+++ b/sapi/fuzzer/Makefile.frag
@@ -23,6 +23,9 @@ $(SAPI_FUZZER_PATH)/php-fuzz-unserializehash: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OB
$(SAPI_FUZZER_PATH)/php-fuzz-json: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_FUZZER_JSON_OBJS)
$(FUZZER_BUILD) $(PHP_FUZZER_JSON_OBJS) -o $@
+$(SAPI_FUZZER_PATH)/php-fuzz-uri: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_FUZZER_URI_OBJS)
+ $(FUZZER_BUILD) $(PHP_FUZZER_URI_OBJS) -o $@
+
$(SAPI_FUZZER_PATH)/php-fuzz-exif: $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_FUZZER_EXIF_OBJS)
$(FUZZER_BUILD) $(PHP_FUZZER_EXIF_OBJS) -o $@
diff --git a/sapi/fuzzer/README.md b/sapi/fuzzer/README.md
index c9ce6ebab52b..3c0d78cdbb21 100644
--- a/sapi/fuzzer/README.md
+++ b/sapi/fuzzer/README.md
@@ -27,6 +27,7 @@ When running `make` it creates these binaries in `sapi/fuzzer/`:
* `php-fuzz-unserialize`: Fuzzing unserialize() function
* `php-fuzz-unserializehash`: Fuzzing unserialize() for HashContext objects
* `php-fuzz-json`: Fuzzing JSON parser
+* `php-fuzz-uri`: Fuzzing the `Uri\Rfc3986\Uri` and `Uri\WhatWg\Url` classes and `parse_url()`
* `php-fuzz-exif`: Fuzzing `exif_read_data()` function (requires --enable-exif)
* `php-fuzz-mbstring`: Fuzzing `mb_convert_encoding()` (requires `--enable-mbstring`)
* `php-fuzz-mbregex`: Fuzzing `mb_ereg[i]()` (requires --enable-mbstring)
@@ -95,3 +96,10 @@ export ONIG_LIBS="-L$PWD/oniguruma/src/.libs -l:libonig.a"
```
This will link an instrumented libonig statically into the PHP binary.
+
+For the uri fuzzer, a seed corpus is available:
+
+```sh
+cp -r sapi/fuzzer/corpus/uri ./my-uri-corpus
+sapi/fuzzer/php-fuzz-uri ./my-uri-corpus
+```
diff --git a/sapi/fuzzer/config.m4 b/sapi/fuzzer/config.m4
index 21a44cd6d886..39352d46b747 100644
--- a/sapi/fuzzer/config.m4
+++ b/sapi/fuzzer/config.m4
@@ -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([uri], [PHP_FUZZER_URI_OBJS])
if test -n "$enable_exif" && test "$enable_exif" != "no"; then
PHP_FUZZER_TARGET([exif], [PHP_FUZZER_EXIF_OBJS])
diff --git a/sapi/fuzzer/corpus/uri/seed01 b/sapi/fuzzer/corpus/uri/seed01
new file mode 100644
index 000000000000..1f8666542d15
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed01
@@ -0,0 +1 @@
+https://user:pass@example.com:8080/a/b/c?x=1&y=2#frag
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed02 b/sapi/fuzzer/corpus/uri/seed02
new file mode 100644
index 000000000000..7d3403c77be5
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed02
@@ -0,0 +1 @@
+http://[2001:db8::1]:80/path
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed03 b/sapi/fuzzer/corpus/uri/seed03
new file mode 100644
index 000000000000..edae4d83ecd7
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed03
@@ -0,0 +1 @@
+http://[v1.fe80::a+en1]/
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed04 b/sapi/fuzzer/corpus/uri/seed04
new file mode 100644
index 000000000000..26c277355b19
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed04
@@ -0,0 +1 @@
+http://192.168.0.1./x
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed05 b/sapi/fuzzer/corpus/uri/seed05
new file mode 100644
index 000000000000..656462a3a84c
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed05
@@ -0,0 +1 @@
+http://0x7f.1/
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed06 b/sapi/fuzzer/corpus/uri/seed06
new file mode 100644
index 000000000000..68eedd165fe0
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed06
@@ -0,0 +1 @@
+file:///C:/Windows/system32
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed07 b/sapi/fuzzer/corpus/uri/seed07
new file mode 100644
index 000000000000..557ded7992dc
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed07
@@ -0,0 +1 @@
+file:///tmp/test.txt
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed08 b/sapi/fuzzer/corpus/uri/seed08
new file mode 100644
index 000000000000..44d438efbc1f
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed08
@@ -0,0 +1 @@
+mailto:someone@example.org?subject=hi
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed09 b/sapi/fuzzer/corpus/uri/seed09
new file mode 100644
index 000000000000..31d8a5633d7a
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed09
@@ -0,0 +1 @@
+urn:isbn:0451450523
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed10 b/sapi/fuzzer/corpus/uri/seed10
new file mode 100644
index 000000000000..715a44bd06a3
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed10
@@ -0,0 +1 @@
+//example.com/network/path
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed11 b/sapi/fuzzer/corpus/uri/seed11
new file mode 100644
index 000000000000..3e8883892562
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed11
@@ -0,0 +1 @@
+../relative/./path/../x
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed12 b/sapi/fuzzer/corpus/uri/seed12
new file mode 100644
index 000000000000..d8ebcb2f44c7
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed12
@@ -0,0 +1,2 @@
+https://example.com/
+../other?q#f
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed13 b/sapi/fuzzer/corpus/uri/seed13
new file mode 100644
index 000000000000..52f9f9031d9d
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed13
@@ -0,0 +1 @@
+http://xn--nxasmq6b.example/
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed14 b/sapi/fuzzer/corpus/uri/seed14
new file mode 100644
index 000000000000..53b37573dc11
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed14
@@ -0,0 +1 @@
+http://日本.example/
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed15 b/sapi/fuzzer/corpus/uri/seed15
new file mode 100644
index 000000000000..cd7b2bb409f5
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed15
@@ -0,0 +1 @@
+https://example.com/%7Euser/%E2%82%AC
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed16 b/sapi/fuzzer/corpus/uri/seed16
new file mode 100644
index 000000000000..20c9fbd26e36
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed16
@@ -0,0 +1 @@
+data:text/plain;base64,SGVsbG8=
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed17 b/sapi/fuzzer/corpus/uri/seed17
new file mode 100644
index 000000000000..08551640b5c9
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed17
@@ -0,0 +1 @@
+HTTP://EXAMPLE.COM:/A%2fB
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed18 b/sapi/fuzzer/corpus/uri/seed18
new file mode 100644
index 000000000000..74f751784ab7
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed18
@@ -0,0 +1,2 @@
+http://example.com
+http://other.example:1/
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed19 b/sapi/fuzzer/corpus/uri/seed19
new file mode 100644
index 000000000000..05a9b6667616
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed19
@@ -0,0 +1,2 @@
+ws://example.com/chat
+?query
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed20 b/sapi/fuzzer/corpus/uri/seed20
new file mode 100644
index 000000000000..0045b075b554
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed20
@@ -0,0 +1 @@
+foo://bar@[::ffff:1.2.3.4]:65535/
\ No newline at end of file
diff --git a/sapi/fuzzer/corpus/uri/seed21 b/sapi/fuzzer/corpus/uri/seed21
new file mode 100644
index 000000000000..17af2129946f
--- /dev/null
+++ b/sapi/fuzzer/corpus/uri/seed21
@@ -0,0 +1,2 @@
+http://a/b/c/d;p?q
+g;x=1/../y
\ No newline at end of file
diff --git a/sapi/fuzzer/fuzzer-uri.c b/sapi/fuzzer/fuzzer-uri.c
new file mode 100644
index 000000000000..8c27014df533
--- /dev/null
+++ b/sapi/fuzzer/fuzzer-uri.c
@@ -0,0 +1,150 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Author: Yuma Kakei |
+ +----------------------------------------------------------------------+
+*/
+
+#include
+#include
+
+#include "fuzzer.h"
+#include "fuzzer-sapi.h"
+
+#define MAX_SIZE (8 * 1024)
+
+static const char *rfc3986_getters[] = {
+ "getUriType", "getScheme", "getRawScheme", "getUserInfo", "getRawUserInfo",
+ "getUsername", "getRawUsername", "getPassword", "getRawPassword", "getHost",
+ "getRawHost", "getHostType", "getPort", "getPath", "getRawPath", "getQuery",
+ "getRawQuery", "getFragment", "getRawFragment", "toString", "toRawString",
+ "__serialize", "__debugInfo", NULL
+};
+
+static const char *rfc3986_withers[] = {
+ "withScheme", "withUserInfo", "withHost", "withPath", "withQuery", "withFragment", NULL
+};
+
+static const char *whatwg_getters[] = {
+ "getScheme", "isSpecialScheme", "getUsername", "getPassword", "getAsciiHost",
+ "getUnicodeHost", "getHostType", "getPort", "getPath", "getQuery", "getFragment",
+ "toAsciiString", "toUnicodeString", "__serialize", "__debugInfo", NULL
+};
+
+static const char *whatwg_withers[] = {
+ "withScheme", "withUsername", "withPassword", "withHost", "withPath", "withQuery",
+ "withFragment", NULL
+};
+
+/* Call a method, discarding its result and any exception it throws. If the result is an
+ * object, call to_string_method on it. */
+static void call_method(
+ zend_object *obj, const char *name, uint32_t argc, zval *argv, const char *to_string_method)
+{
+ zend_function *fn = zend_hash_str_find_ptr_lc(&obj->ce->function_table, name, strlen(name));
+ ZEND_ASSERT(fn != NULL);
+
+ zval retval;
+ ZVAL_UNDEF(&retval);
+ zend_call_known_instance_method(fn, obj, &retval, argc, argv);
+ zend_clear_exception();
+
+ if (to_string_method && Z_TYPE(retval) == IS_OBJECT) {
+ call_method(Z_OBJ(retval), to_string_method, 0, NULL, NULL);
+ }
+ zval_ptr_dtor(&retval);
+}
+
+static void fuzz_uri_class(
+ const char *class_name, size_t class_name_len, zval *uri, zval *ref,
+ const char **getters, const char **withers, const char *to_string_method)
+{
+ zend_class_entry *ce = zend_hash_str_find_ptr(CG(class_table), class_name, class_name_len);
+ ZEND_ASSERT(ce != NULL);
+ zend_function *parse = zend_hash_str_find_ptr(&ce->function_table, ZEND_STRL("parse"));
+ ZEND_ASSERT(parse != NULL);
+
+ /* Url::parse() reports errors through its third, by-reference argument. */
+ zval args[3], retval;
+ ZVAL_COPY_VALUE(&args[0], uri);
+ ZVAL_NULL(&args[1]);
+ ZVAL_NEW_EMPTY_REF(&args[2]);
+ ZVAL_NULL(Z_REFVAL(args[2]));
+ ZVAL_UNDEF(&retval);
+ zend_call_known_function(parse, NULL, ce, &retval, parse->common.num_args, args, NULL);
+ zend_clear_exception();
+ zval_ptr_dtor(&args[2]);
+
+ if (Z_TYPE(retval) != IS_OBJECT) {
+ zval_ptr_dtor(&retval);
+ return;
+ }
+
+ zend_object *obj = Z_OBJ(retval);
+ for (const char **getter = getters; *getter; getter++) {
+ call_method(obj, *getter, 0, NULL, NULL);
+ }
+ call_method(obj, "equals", 1, &retval, NULL);
+
+ if (!Z_ISUNDEF_P(ref)) {
+ call_method(obj, "resolve", 1, ref, to_string_method);
+ for (const char **wither = withers; *wither; wither++) {
+ call_method(obj, *wither, 1, ref, to_string_method);
+ }
+ }
+
+ zval_ptr_dtor(&retval);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+ if (Size > MAX_SIZE) {
+ /* Large inputs have a large impact on fuzzer performance,
+ * but are unlikely to be necessary to reach new codepaths. */
+ return 0;
+ }
+
+ if (fuzzer_request_startup() == FAILURE) {
+ return 0;
+ }
+
+ fuzzer_setup_dummy_frame();
+
+ /* The input is a URI, optionally followed by a newline and a string that is passed to
+ * resolve() and the with*() methods. The WHATWG parser strips newlines from its input, so
+ * using them as separator loses little. */
+ const uint8_t *newline = memchr(Data, '\n', Size);
+ size_t uri_len = newline ? (size_t) (newline - Data) : Size;
+ zval uri, ref;
+ ZVAL_STRINGL(&uri, (const char *) Data, uri_len);
+ if (newline) {
+ ZVAL_STRINGL(&ref, (const char *) newline + 1, Size - uri_len - 1);
+ } else {
+ ZVAL_UNDEF(&ref);
+ }
+
+ fuzz_uri_class(ZEND_STRL("uri\\rfc3986\\uri"), &uri, &ref,
+ rfc3986_getters, rfc3986_withers, "toRawString");
+ fuzz_uri_class(ZEND_STRL("uri\\whatwg\\url"), &uri, &ref,
+ whatwg_getters, whatwg_withers, "toAsciiString");
+ fuzzer_call_php_func_zval("parse_url", 1, &uri);
+
+ zval_ptr_dtor(&uri);
+ zval_ptr_dtor(&ref);
+
+ fuzzer_request_shutdown();
+ return 0;
+}
+
+int LLVMFuzzerInitialize(int *argc, char ***argv) {
+ fuzzer_init_php(NULL);
+
+ /* fuzzer_shutdown_php(); */
+ return 0;
+}