Commit 1095cf560d9 for php.net

commit 1095cf560d97432374edb5fb6387a8265025e573
Author: Gina Peter Banyard <girgias@php.net>
Date:   Wed Apr 29 07:51:41 2026 +0100

    ext/phar: split calling openssl_verify and openssl_sign functions (#21836)

    The only thing vaguely similar is the setting up of arguments, and even that is not identical due to the by-ref arg for openssl_sign().

    Splitting makes the code more legible, as we can add const qualifiers and use a zend_string directly, reducing allocations

diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h
index df4e58c7adb..8206856f6ca 100644
--- a/ext/phar/phar_internal.h
+++ b/ext/phar/phar_internal.h
@@ -412,7 +412,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_create_or_parse_filename(z
 ZEND_ATTRIBUTE_NONNULL_ARGS(3) zend_result phar_open_executed_filename(char *alias, size_t alias_len, char **error);
 zend_result phar_free_alias(const phar_archive_data *phar);
 zend_result phar_get_archive(phar_archive_data **archive, const char *fname, size_t fname_len, const char *alias, size_t alias_len, char **error);
-zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t sig_type, char *sig, size_t sig_len, const char *fname, char **signature, size_t *signature_len, char **error);
+zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t sig_type, const char *sig, size_t sig_len, const char *fname, char **signature, size_t *signature_len, char **error);
 ZEND_ATTRIBUTE_NONNULL zend_string* phar_create_signature(phar_archive_data *phar, php_stream *fp, char **error);

 /* utility functions */
diff --git a/ext/phar/util.c b/ext/phar/util.c
index ad7cdd6c9ae..cf3e6a90e51 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -34,7 +34,8 @@
 #include <openssl/ssl.h>
 #include <openssl/pkcs12.h>
 #else
-static zend_result phar_call_openssl_signverify(bool is_sign, php_stream *fp, zend_off_t end, const char *key, size_t key_len, char **signature, size_t *signature_len, uint32_t sig_type);
+ZEND_ATTRIBUTE_NONNULL static bool phar_call_openssl_verify(php_stream *fp, zend_off_t end, zend_string *public_key, const char *signature, size_t signature_len, uint32_t sig_type);
+static zend_result phar_call_openssl_sign(php_stream *fp, zend_off_t end, const char *key, size_t key_len, char **signature, size_t *signature_len, uint32_t sig_type);
 #endif

 /* for links to relative location, prepend cwd of the entry */
@@ -1372,36 +1373,44 @@ static int phar_hex_str(const char *digest, size_t digest_len, char **signature)
 /* }}} */

 #ifndef PHAR_HAVE_OPENSSL
-static zend_result phar_call_openssl_signverify(bool is_sign, php_stream *fp, zend_off_t end, const char *key, size_t key_len, char **signature, size_t *signature_len, uint32_t sig_type) /* {{{ */
-{
-	zval retval, zp[4];
-	zend_string *str;
-
-	zend_function *fn = NULL;
-	if (is_sign) {
-		fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("openssl_sign"));
-	} else {
-		fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("openssl_verify"));
-	}
+ZEND_ATTRIBUTE_NONNULL static bool phar_call_openssl_verify(
+	php_stream *fp,
+	zend_off_t end,
+	zend_string *public_key,
+	const char *signature,
+	size_t signature_len,
+	uint32_t sig_type
+) {
+	ZEND_ASSERT(signature_len != 0);
+	zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("openssl_verify"));

 	/* OpenSSL is not available, even as a shared module */
-	if (fn == NULL) {
-		return FAILURE;
+	if (UNEXPECTED(fn == NULL)) {
+		return false;
 	}

-	if (*signature_len) {
-		ZVAL_STRINGL(&zp[1], *signature, *signature_len);
-	} else {
-		ZVAL_EMPTY_STRING(&zp[1]);
-	}
-	ZVAL_STRINGL(&zp[2], key, key_len);
+	/* Read and copy stream content */
 	php_stream_rewind(fp);
-	str = php_stream_copy_to_mem(fp, (size_t) end, 0);
-	if (str) {
-		ZVAL_STR(&zp[0], str);
-	} else {
-		ZVAL_EMPTY_STRING(&zp[0]);
-	}
+	zend_string *str = php_stream_copy_to_mem(fp, (size_t) end, false);
+	/* No content thus signing must fail */
+	if (UNEXPECTED(str == NULL)) {
+		return false;
+	}
+
+	/* Set up parameters for call to openssl_verify()
+	 * openssl_verify(
+	 *	string $data,
+	 *	string $signature,
+	 *	OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $public_key,
+	 *	string|int $algorithm = OPENSSL_ALGO_SHA1,
+	 *	int $padding = 0
+	 * ): int|false
+	 */
+	zval retval, zp[4];
+	ZVAL_STR(&zp[0], str);
+	ZVAL_STRINGL(&zp[1], signature, signature_len);
+	/* Note we do not own the lifetime of the public key, but it is fine as calling the function will increase the refcount) */
+	ZVAL_STR(&zp[2], public_key);
 	if (sig_type == PHAR_SIG_OPENSSL_SHA512) {
 		ZVAL_LONG(&zp[3], 9); /* value from openssl.c #define OPENSSL_ALGO_SHA512 9 */
 	} else if (sig_type == PHAR_SIG_OPENSSL_SHA256) {
@@ -1410,52 +1419,84 @@ static zend_result phar_call_openssl_signverify(bool is_sign, php_stream *fp, ze
 		/* don't rely on default value which may change in the future */
 		ZVAL_LONG(&zp[3], 1); /* value from openssl.c #define OPENSSL_ALGO_SHA1   1 */
 	}
+	zend_call_known_function(fn, NULL, NULL, &retval, /* param_count */ 4, zp, NULL);
+	/* Free string arguments that we own */
+	zval_ptr_dtor_str(&zp[0]);
+	zval_ptr_dtor_str(&zp[1]);
+
+	/* Returns 1 if the signature is correct, 0 if it is incorrect, and -1 or false on error. */
+	switch (Z_TYPE(retval)) {
+		case IS_LONG:
+			if (1 == Z_LVAL(retval)) {
+				return true;
+			}
+			ZEND_FALLTHROUGH;
+		default:
+			/* Unlikely, but the openssl_verify() function may be disabled and redefined in userland and return bollocks */
+			zval_ptr_dtor(&retval);
+			return false;
+	}
+}
+
+static zend_result phar_call_openssl_sign(php_stream *fp, zend_off_t end, const char *key, size_t key_len, char **signature, size_t *signature_len, uint32_t sig_type) /* {{{ */
+{
+	ZEND_ASSERT(end != 0);
+	ZEND_ASSERT(*signature_len == 0);
+	zval retval, zp[4];

-	if ((size_t)end != Z_STRLEN(zp[0])) {
-		zval_ptr_dtor_str(&zp[0]);
-		zval_ptr_dtor_str(&zp[1]);
-		zval_ptr_dtor_str(&zp[2]);
+	zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("openssl_sign"));
+
+	/* OpenSSL is not available, even as a shared module */
+	if (fn == NULL) {
+		return FAILURE;
+	}
+
+	/* Read and copy stream content */
+	php_stream_rewind(fp);
+	zend_string *str = php_stream_copy_to_mem(fp, (size_t) end, false);
+	/* No content thus signing must fail */
+	if (!str || (size_t)end != ZSTR_LEN(str)) {
 		return FAILURE;
 	}

-	Z_ADDREF(zp[0]);
-	if (is_sign) {
-		ZVAL_NEW_REF(&zp[1], &zp[1]);
+	/* Set up parameters for call to openssl_sign()
+	 * openssl_sign(
+	 *	string $data,
+	 *	string &$signature,
+	 *	#[\SensitiveParameter] OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key,
+	 *	string|int $algorithm = OPENSSL_ALGO_SHA1,
+	 *	int $padding = 0
+	 * ): bool
+	 */
+	ZVAL_STR(&zp[0], str);
+	ZVAL_EMPTY_STRING(&zp[1]);
+	ZVAL_NEW_REF(&zp[1], &zp[1]);
+	ZVAL_STRINGL(&zp[2], key, key_len);
+	if (sig_type == PHAR_SIG_OPENSSL_SHA512) {
+		ZVAL_LONG(&zp[3], 9); /* value from openssl.c #define OPENSSL_ALGO_SHA512 9 */
+	} else if (sig_type == PHAR_SIG_OPENSSL_SHA256) {
+		ZVAL_LONG(&zp[3], 7); /* value from openssl.c #define OPENSSL_ALGO_SHA256 7 */
 	} else {
-		Z_ADDREF(zp[1]);
+		/* don't rely on default value which may change in the future */
+		ZVAL_LONG(&zp[3], 1); /* value from openssl.c #define OPENSSL_ALGO_SHA1   1 */
 	}
-	Z_ADDREF(zp[2]);

 	zend_call_known_function(fn, NULL, NULL, &retval, /* param_count */ 4, zp, NULL);

-	Z_DELREF(zp[0]);
-
-	if (is_sign) {
-		ZVAL_UNREF(&zp[1]);
-	} else {
-		Z_DELREF(zp[1]);
-	}
-	Z_DELREF(zp[2]);
+	ZVAL_UNREF(&zp[1]);

 	zval_ptr_dtor_str(&zp[0]);
 	zval_ptr_dtor_str(&zp[2]);

 	switch (Z_TYPE(retval)) {
-		case IS_LONG:
-			zval_ptr_dtor(&zp[1]);
-			if (1 == Z_LVAL(retval)) {
-				return SUCCESS;
-			}
-			return FAILURE;
 		case IS_TRUE:
 			*signature = estrndup(Z_STRVAL(zp[1]), Z_STRLEN(zp[1]));
 			*signature_len = Z_STRLEN(zp[1]);
 			zval_ptr_dtor(&zp[1]);
 			return SUCCESS;
 		default:
+			/* Unlikely, but the openssl_sign() function may be disabled and redefined in userland and return bollocks */
 			zval_ptr_dtor(&retval);
-			ZEND_FALLTHROUGH;
-		case IS_FALSE:
 			zval_ptr_dtor(&zp[1]);
 			return FAILURE;
 	}
@@ -1463,7 +1504,7 @@ static zend_result phar_call_openssl_signverify(bool is_sign, php_stream *fp, ze
 /* }}} */
 #endif /* #ifndef PHAR_HAVE_OPENSSL */

-zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t sig_type, char *sig, size_t sig_len, const char *fname, char **signature, size_t *signature_len, char **error) /* {{{ */
+zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t sig_type, const char *sig, size_t sig_len, const char *fname, char **signature, size_t *signature_len, char **error) /* {{{ */
 {
 	size_t read_size, len;
 	zend_off_t read_len;
@@ -1488,8 +1529,6 @@ zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t s
 			} else {
 				mdtype = EVP_sha1();
 			}
-#else
-			size_t tempsig;
 #endif
 			zend_string *pubkey = NULL;
 			char *pfile;
@@ -1524,9 +1563,7 @@ zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t s
 			}

 #ifndef PHAR_HAVE_OPENSSL
-			tempsig = sig_len;
-
-			if (FAILURE == phar_call_openssl_signverify(false, fp, end_of_phar, ZSTR_VAL(pubkey), ZSTR_LEN(pubkey), &sig, &tempsig, sig_type)) {
+			if (!phar_call_openssl_verify(fp, end_of_phar, pubkey, sig, sig_len, sig_type)) {
 				zend_string_release_ex(pubkey, 0);

 				if (error) {
@@ -1536,9 +1573,7 @@ zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t s
 				return FAILURE;
 			}

-			zend_string_release_ex(pubkey, 0);
-
-			sig_len = tempsig;
+			zend_string_release_ex(pubkey, false);
 #else
 			in = BIO_new_mem_buf(ZSTR_VAL(pubkey), ZSTR_LEN(pubkey));

@@ -1610,7 +1645,7 @@ zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t s
 			EVP_MD_CTX_destroy(md_ctx);
 #endif

-			*signature_len = phar_hex_str((const char*)sig, sig_len, signature);
+			*signature_len = phar_hex_str(sig, sig_len, signature);
 		}
 		break;
 		case PHAR_SIG_SHA512: {
@@ -1909,7 +1944,7 @@ ZEND_ATTRIBUTE_NONNULL zend_string* phar_create_signature(phar_archive_data *pha
 			siglen = 0;
 			php_stream_seek(fp, 0, SEEK_END);

-			if (FAILURE == phar_call_openssl_signverify(true, fp, php_stream_tell(fp), PHAR_G(openssl_privatekey), PHAR_G(openssl_privatekey_len), (char **)&sigbuf, &siglen, phar->sig_flags)) {
+			if (FAILURE == phar_call_openssl_sign(fp, php_stream_tell(fp), PHAR_G(openssl_privatekey), PHAR_G(openssl_privatekey_len), (char **)&sigbuf, &siglen, phar->sig_flags)) {
 				spprintf(error, 0, "unable to write phar \"%s\" with requested openssl signature", ZSTR_VAL(phar->fname));
 				return NULL;
 			}