Commit d1d144e44bd for php.net
commit d1d144e44bd3a23c7f80c6d29dd77d4d43e089a0
Author: Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
Date: Fri Jul 10 14:37:32 2026 +0200
Throw an error on null byte in curl array options (#22662)
This is already done for string options in php_curl_option_str
(see bug #68089). Similarly, array arguments should also not contain
null bytes, so we throw the same error for these.
This was extracted from https://github.com/php/php-src/pull/22651, as
this seems less controversial.
Using null bytes is clearly not supported and curl just throws away
everything after the null byte, so
https://wiki.php.net/rfc/policy-exempt-type-value-error-bc-policy seems
to apply here.
diff --git a/ext/curl/interface.c b/ext/curl/interface.c
index 1dff14b7c8c..09c7009068b 100644
--- a/ext/curl/interface.c
+++ b/ext/curl/interface.c
@@ -2030,42 +2030,42 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
HashTable *ph;
zend_string *val, *tmp_val;
struct curl_slist *slist = NULL;
+ const char *name = NULL;
- if (Z_TYPE_P(zvalue) != IS_ARRAY) {
- const char *name = NULL;
- switch (option) {
- case CURLOPT_HTTPHEADER:
- name = "CURLOPT_HTTPHEADER";
- break;
- case CURLOPT_QUOTE:
- name = "CURLOPT_QUOTE";
- break;
- case CURLOPT_HTTP200ALIASES:
- name = "CURLOPT_HTTP200ALIASES";
- break;
- case CURLOPT_POSTQUOTE:
- name = "CURLOPT_POSTQUOTE";
- break;
- case CURLOPT_PREQUOTE:
- name = "CURLOPT_PREQUOTE";
- break;
- case CURLOPT_TELNETOPTIONS:
- name = "CURLOPT_TELNETOPTIONS";
- break;
- case CURLOPT_MAIL_RCPT:
- name = "CURLOPT_MAIL_RCPT";
- break;
- case CURLOPT_RESOLVE:
- name = "CURLOPT_RESOLVE";
- break;
- case CURLOPT_PROXYHEADER:
- name = "CURLOPT_PROXYHEADER";
- break;
- case CURLOPT_CONNECT_TO:
- name = "CURLOPT_CONNECT_TO";
- break;
- }
+ switch (option) {
+ case CURLOPT_HTTPHEADER:
+ name = "CURLOPT_HTTPHEADER";
+ break;
+ case CURLOPT_QUOTE:
+ name = "CURLOPT_QUOTE";
+ break;
+ case CURLOPT_HTTP200ALIASES:
+ name = "CURLOPT_HTTP200ALIASES";
+ break;
+ case CURLOPT_POSTQUOTE:
+ name = "CURLOPT_POSTQUOTE";
+ break;
+ case CURLOPT_PREQUOTE:
+ name = "CURLOPT_PREQUOTE";
+ break;
+ case CURLOPT_TELNETOPTIONS:
+ name = "CURLOPT_TELNETOPTIONS";
+ break;
+ case CURLOPT_MAIL_RCPT:
+ name = "CURLOPT_MAIL_RCPT";
+ break;
+ case CURLOPT_RESOLVE:
+ name = "CURLOPT_RESOLVE";
+ break;
+ case CURLOPT_PROXYHEADER:
+ name = "CURLOPT_PROXYHEADER";
+ break;
+ case CURLOPT_CONNECT_TO:
+ name = "CURLOPT_CONNECT_TO";
+ break;
+ }
+ if (Z_TYPE_P(zvalue) != IS_ARRAY) {
zend_type_error("%s(): The %s option must have an array value", get_active_function_name(), name);
return FAILURE;
}
@@ -2074,6 +2074,14 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
ZEND_HASH_FOREACH_VAL(ph, current) {
ZVAL_DEREF(current);
val = zval_get_tmp_string(current, &tmp_val);
+
+ if (zend_str_has_nul_byte(val)) {
+ curl_slist_free_all(slist);
+ zend_tmp_string_release(tmp_val);
+ zend_value_error("%s(): cURL option %s must not contain any null bytes", get_active_function_name(), name);
+ return FAILURE;
+ }
+
struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val));
zend_tmp_string_release(tmp_val);
if (!new_slist) {
diff --git a/ext/curl/tests/curl_setopt_error_nul_byte.phpt b/ext/curl/tests/curl_setopt_error_nul_byte.phpt
new file mode 100644
index 00000000000..23be973b82c
--- /dev/null
+++ b/ext/curl/tests/curl_setopt_error_nul_byte.phpt
@@ -0,0 +1,52 @@
+--TEST--
+curl_setopt() throws ValueError for NUL bytes in lists
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+
+$ch = curl_init();
+
+$list_options = [
+ "CURLOPT_HTTP200ALIASES",
+ "CURLOPT_HTTPHEADER",
+ "CURLOPT_POSTQUOTE",
+ "CURLOPT_PREQUOTE",
+ "CURLOPT_QUOTE",
+ "CURLOPT_TELNETOPTIONS",
+ "CURLOPT_MAIL_RCPT",
+ "CURLOPT_RESOLVE",
+ "CURLOPT_PROXYHEADER",
+ "CURLOPT_CONNECT_TO",
+];
+
+foreach ($list_options as $option) {
+ try {
+ curl_setopt($ch, constant($option), ["Something: foo\0bar"]);
+ } catch (ValueError $exception) {
+ echo $option . ": " . $exception->getMessage() . "\n\n";
+ }
+}
+
+$ch = null;
+?>
+--EXPECT--
+CURLOPT_HTTP200ALIASES: curl_setopt(): cURL option CURLOPT_HTTP200ALIASES must not contain any null bytes
+
+CURLOPT_HTTPHEADER: curl_setopt(): cURL option CURLOPT_HTTPHEADER must not contain any null bytes
+
+CURLOPT_POSTQUOTE: curl_setopt(): cURL option CURLOPT_POSTQUOTE must not contain any null bytes
+
+CURLOPT_PREQUOTE: curl_setopt(): cURL option CURLOPT_PREQUOTE must not contain any null bytes
+
+CURLOPT_QUOTE: curl_setopt(): cURL option CURLOPT_QUOTE must not contain any null bytes
+
+CURLOPT_TELNETOPTIONS: curl_setopt(): cURL option CURLOPT_TELNETOPTIONS must not contain any null bytes
+
+CURLOPT_MAIL_RCPT: curl_setopt(): cURL option CURLOPT_MAIL_RCPT must not contain any null bytes
+
+CURLOPT_RESOLVE: curl_setopt(): cURL option CURLOPT_RESOLVE must not contain any null bytes
+
+CURLOPT_PROXYHEADER: curl_setopt(): cURL option CURLOPT_PROXYHEADER must not contain any null bytes
+
+CURLOPT_CONNECT_TO: curl_setopt(): cURL option CURLOPT_CONNECT_TO must not contain any null bytes