Commit 8da53b5d31c for php.net
commit 8da53b5d31cbf4b50c779440afc34f75e79a0afb
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Tue Apr 7 17:48:43 2026 -0400
Fix GH-18173: ext/hash relies on implementation-defined malloc alignment
XXH3_state_t requires 64-byte alignment for its acc, customSecret, and
buffer members, but php_hash_alloc_context() used ecalloc(), which only
guarantees alignof(max_align_t) (typically 16 bytes on x86_64). When heap
layout broke that assumption, xxhash's aligned loads segfaulted. Add a
context_align field to php_hash_ops; when set, php_hash_alloc_context()
over-allocates and manually aligns the returned pointer, storing the offset
for php_hash_free_context() to recover the original allocation.
Fixes GH-18173
Closes GH-21668
diff --git a/NEWS b/NEWS
index 531d6a334c0..86ef47098f7 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.24
+- Hash:
+ . Fixed bug GH-18173 (ext/hash relies on implementation-defined malloc
+ alignment). (iliaal)
+
- Intl:
. Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
fallback locale when a language tag cannot be canonicalized. (Weilin Du)
diff --git a/ext/hash/hash.c b/ext/hash/hash.c
index 6d03bbcca7c..1c90f4821f1 100644
--- a/ext/hash/hash.c
+++ b/ext/hash/hash.c
@@ -392,7 +392,7 @@ static void php_hash_do_hash(
}
php_stream_close(stream);
if (n < 0) {
- efree(context);
+ php_hash_free_context(ops, context);
RETURN_FALSE;
}
} else {
@@ -401,7 +401,7 @@ static void php_hash_do_hash(
digest = zend_string_alloc(ops->digest_size, 0);
ops->hash_final((unsigned char *) ZSTR_VAL(digest), context);
- efree(context);
+ php_hash_free_context(ops, context);
if (raw_output) {
ZSTR_VAL(digest)[ops->digest_size] = 0;
@@ -540,7 +540,7 @@ static void php_hash_do_hash_hmac(
}
php_stream_close(stream);
if (n < 0) {
- efree(context);
+ php_hash_free_context(ops, context);
efree(K);
zend_string_release(digest);
RETURN_FALSE;
@@ -558,7 +558,7 @@ static void php_hash_do_hash_hmac(
/* Zero the key */
ZEND_SECURE_ZERO(K, ops->block_size);
efree(K);
- efree(context);
+ php_hash_free_context(ops, context);
if (raw_output) {
ZSTR_VAL(digest)[ops->digest_size] = 0;
@@ -817,7 +817,7 @@ PHP_FUNCTION(hash_final)
ZSTR_VAL(digest)[digest_len] = 0;
/* Invalidate the object from further use */
- efree(hash->context);
+ php_hash_free_context(hash->ops, hash->context);
hash->context = NULL;
if (raw_output) {
@@ -975,7 +975,7 @@ PHP_FUNCTION(hash_hkdf)
ZEND_SECURE_ZERO(digest, ops->digest_size);
ZEND_SECURE_ZERO(prk, ops->digest_size);
efree(K);
- efree(context);
+ php_hash_free_context(ops, context);
efree(prk);
efree(digest);
ZSTR_VAL(returnval)[length] = 0;
@@ -1091,7 +1091,7 @@ PHP_FUNCTION(hash_pbkdf2)
efree(K1);
efree(K2);
efree(computed_salt);
- efree(context);
+ php_hash_free_context(ops, context);
efree(digest);
efree(temp);
@@ -1347,7 +1347,7 @@ PHP_FUNCTION(mhash_keygen_s2k)
RETVAL_STRINGL(key, bytes);
ZEND_SECURE_ZERO(key, bytes);
efree(digest);
- efree(context);
+ php_hash_free_context(ops, context);
efree(key);
}
}
@@ -1377,7 +1377,7 @@ static void php_hashcontext_dtor(zend_object *obj) {
php_hashcontext_object *hash = php_hashcontext_from_object(obj);
if (hash->context) {
- efree(hash->context);
+ php_hash_free_context(hash->ops, hash->context);
hash->context = NULL;
}
@@ -1413,7 +1413,7 @@ static zend_object *php_hashcontext_clone(zend_object *zobj) {
newobj->ops->hash_init(newobj->context, NULL);
if (SUCCESS != newobj->ops->hash_copy(newobj->ops, oldobj->context, newobj->context)) {
- efree(newobj->context);
+ php_hash_free_context(newobj->ops, newobj->context);
newobj->context = NULL;
return znew;
}
diff --git a/ext/hash/hash_adler32.c b/ext/hash/hash_adler32.c
index 3898ea60e87..e1fdd765b37 100644
--- a/ext/hash/hash_adler32.c
+++ b/ext/hash/hash_adler32.c
@@ -70,5 +70,6 @@ const php_hash_ops php_hash_adler32_ops = {
4, /* what to say here? */
4,
sizeof(PHP_ADLER32_CTX),
+ 0,
0
};
diff --git a/ext/hash/hash_crc32.c b/ext/hash/hash_crc32.c
index a770d0b5541..2e8de00b518 100644
--- a/ext/hash/hash_crc32.c
+++ b/ext/hash/hash_crc32.c
@@ -102,6 +102,7 @@ const php_hash_ops php_hash_crc32_ops = {
4, /* what to say here? */
4,
sizeof(PHP_CRC32_CTX),
+ 0,
0
};
@@ -117,6 +118,7 @@ const php_hash_ops php_hash_crc32b_ops = {
4, /* what to say here? */
4,
sizeof(PHP_CRC32_CTX),
+ 0,
0
};
@@ -132,5 +134,6 @@ const php_hash_ops php_hash_crc32c_ops = {
4, /* what to say here? */
4,
sizeof(PHP_CRC32_CTX),
+ 0,
0
};
diff --git a/ext/hash/hash_fnv.c b/ext/hash/hash_fnv.c
index 92d4922bd81..c126de61cfd 100644
--- a/ext/hash/hash_fnv.c
+++ b/ext/hash/hash_fnv.c
@@ -32,6 +32,7 @@ const php_hash_ops php_hash_fnv132_ops = {
4,
4,
sizeof(PHP_FNV132_CTX),
+ 0,
0
};
@@ -47,6 +48,7 @@ const php_hash_ops php_hash_fnv1a32_ops = {
4,
4,
sizeof(PHP_FNV132_CTX),
+ 0,
0
};
@@ -62,6 +64,7 @@ const php_hash_ops php_hash_fnv164_ops = {
8,
4,
sizeof(PHP_FNV164_CTX),
+ 0,
0
};
@@ -77,6 +80,7 @@ const php_hash_ops php_hash_fnv1a64_ops = {
8,
4,
sizeof(PHP_FNV164_CTX),
+ 0,
0
};
diff --git a/ext/hash/hash_gost.c b/ext/hash/hash_gost.c
index 2ad6948a9a6..ee2f3c89381 100644
--- a/ext/hash/hash_gost.c
+++ b/ext/hash/hash_gost.c
@@ -329,7 +329,8 @@ const php_hash_ops php_hash_gost_ops = {
32,
32,
sizeof(PHP_GOST_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_gost_crypto_ops = {
@@ -344,5 +345,6 @@ const php_hash_ops php_hash_gost_crypto_ops = {
32,
32,
sizeof(PHP_GOST_CTX),
- 1
+ 1,
+ 0
};
diff --git a/ext/hash/hash_haval.c b/ext/hash/hash_haval.c
index 67bc2b2e478..484b9a58291 100644
--- a/ext/hash/hash_haval.c
+++ b/ext/hash/hash_haval.c
@@ -252,7 +252,7 @@ const php_hash_ops php_hash_##p##haval##b##_ops = { \
php_hash_serialize, \
php_hash_unserialize, \
PHP_HAVAL_SPEC, \
- ((b) / 8), 128, sizeof(PHP_HAVAL_CTX), 1 }; \
+ ((b) / 8), 128, sizeof(PHP_HAVAL_CTX), 1, 0 }; \
PHP_HASH_API void PHP_##p##HAVAL##b##Init(PHP_HAVAL_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) \
{ int i; context->count[0] = context->count[1] = 0; \
for(i = 0; i < 8; i++) context->state[i] = D0[i]; \
diff --git a/ext/hash/hash_joaat.c b/ext/hash/hash_joaat.c
index 328f9292c4c..6a16ceeeda1 100644
--- a/ext/hash/hash_joaat.c
+++ b/ext/hash/hash_joaat.c
@@ -33,6 +33,7 @@ const php_hash_ops php_hash_joaat_ops = {
4,
4,
sizeof(PHP_JOAAT_CTX),
+ 0,
0
};
diff --git a/ext/hash/hash_md.c b/ext/hash/hash_md.c
index 96da7fce82a..996e71ec9ff 100644
--- a/ext/hash/hash_md.c
+++ b/ext/hash/hash_md.c
@@ -29,7 +29,8 @@ const php_hash_ops php_hash_md5_ops = {
16,
64,
sizeof(PHP_MD5_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_md4_ops = {
@@ -44,7 +45,8 @@ const php_hash_ops php_hash_md4_ops = {
16,
64,
sizeof(PHP_MD4_CTX),
- 1
+ 1,
+ 0
};
static int php_md2_unserialize(php_hashcontext_object *hash, zend_long magic, const zval *zv);
@@ -61,7 +63,8 @@ const php_hash_ops php_hash_md2_ops = {
16,
16,
sizeof(PHP_MD2_CTX),
- 1
+ 1,
+ 0
};
/* MD common stuff */
diff --git a/ext/hash/hash_murmur.c b/ext/hash/hash_murmur.c
index 0117b2e57d3..d69c5c3bb68 100644
--- a/ext/hash/hash_murmur.c
+++ b/ext/hash/hash_murmur.c
@@ -33,6 +33,7 @@ const php_hash_ops php_hash_murmur3a_ops = {
4,
4,
sizeof(PHP_MURMUR3A_CTX),
+ 0,
0
};
@@ -95,6 +96,7 @@ const php_hash_ops php_hash_murmur3c_ops = {
16,
4,
sizeof(PHP_MURMUR3C_CTX),
+ 0,
0
};
@@ -174,6 +176,7 @@ const php_hash_ops php_hash_murmur3f_ops = {
16,
8,
sizeof(PHP_MURMUR3F_CTX),
+ 0,
0
};
diff --git a/ext/hash/hash_ripemd.c b/ext/hash/hash_ripemd.c
index 4802fdf9a1f..188d0095cbc 100644
--- a/ext/hash/hash_ripemd.c
+++ b/ext/hash/hash_ripemd.c
@@ -33,7 +33,8 @@ const php_hash_ops php_hash_ripemd128_ops = {
16,
64,
sizeof(PHP_RIPEMD128_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_ripemd160_ops = {
@@ -48,7 +49,8 @@ const php_hash_ops php_hash_ripemd160_ops = {
20,
64,
sizeof(PHP_RIPEMD160_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_ripemd256_ops = {
@@ -63,7 +65,8 @@ const php_hash_ops php_hash_ripemd256_ops = {
32,
64,
sizeof(PHP_RIPEMD256_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_ripemd320_ops = {
@@ -78,7 +81,8 @@ const php_hash_ops php_hash_ripemd320_ops = {
40,
64,
sizeof(PHP_RIPEMD320_CTX),
- 1
+ 1,
+ 0
};
/* {{{ PHP_RIPEMD128Init
diff --git a/ext/hash/hash_sha.c b/ext/hash/hash_sha.c
index 3129446fcde..c45947b8584 100644
--- a/ext/hash/hash_sha.c
+++ b/ext/hash/hash_sha.c
@@ -75,7 +75,8 @@ const php_hash_ops php_hash_sha1_ops = {
20,
64,
sizeof(PHP_SHA1_CTX),
- 1
+ 1,
+ 0
};
/* sha224/sha256 */
@@ -92,7 +93,8 @@ const php_hash_ops php_hash_sha256_ops = {
32,
64,
sizeof(PHP_SHA256_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_sha224_ops = {
@@ -107,7 +109,8 @@ const php_hash_ops php_hash_sha224_ops = {
28,
64,
sizeof(PHP_SHA224_CTX),
- 1
+ 1,
+ 0
};
#define ROTR32(b,x) ((x >> b) | (x << (32 - b)))
@@ -624,7 +627,8 @@ const php_hash_ops php_hash_sha384_ops = {
48,
128,
sizeof(PHP_SHA384_CTX),
- 1
+ 1,
+ 0
};
/* {{{ PHP_SHA512InitArgs
@@ -803,7 +807,8 @@ const php_hash_ops php_hash_sha512_ops = {
64,
128,
sizeof(PHP_SHA512_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_sha512_256_ops = {
@@ -818,7 +823,8 @@ const php_hash_ops php_hash_sha512_256_ops = {
32,
128,
sizeof(PHP_SHA512_CTX),
- 1
+ 1,
+ 0
};
const php_hash_ops php_hash_sha512_224_ops = {
@@ -833,5 +839,6 @@ const php_hash_ops php_hash_sha512_224_ops = {
28,
128,
sizeof(PHP_SHA512_CTX),
- 1
+ 1,
+ 0
};
diff --git a/ext/hash/hash_sha3.c b/ext/hash/hash_sha3.c
index 07da2cfd2d0..65221593511 100644
--- a/ext/hash/hash_sha3.c
+++ b/ext/hash/hash_sha3.c
@@ -251,7 +251,8 @@ const php_hash_ops php_hash_sha3_##bits##_ops = { \
bits >> 3, \
(1600 - (2 * bits)) >> 3, \
sizeof(PHP_SHA3_##bits##_CTX), \
- 1 \
+ 1, \
+ 0 \
}
#else
@@ -339,7 +340,8 @@ const php_hash_ops php_hash_sha3_##bits##_ops = { \
bits >> 3, \
(1600 - (2 * bits)) >> 3, \
sizeof(PHP_SHA3_CTX), \
- 1 \
+ 1, \
+ 0 \
}
#endif
diff --git a/ext/hash/hash_snefru.c b/ext/hash/hash_snefru.c
index c1dbc3ae57a..fd2ee28c80e 100644
--- a/ext/hash/hash_snefru.c
+++ b/ext/hash/hash_snefru.c
@@ -214,5 +214,6 @@ const php_hash_ops php_hash_snefru_ops = {
32,
32,
sizeof(PHP_SNEFRU_CTX),
- 1
+ 1,
+ 0
};
diff --git a/ext/hash/hash_tiger.c b/ext/hash/hash_tiger.c
index 841693a67dd..745f6958237 100644
--- a/ext/hash/hash_tiger.c
+++ b/ext/hash/hash_tiger.c
@@ -265,7 +265,8 @@ static int php_tiger_unserialize(php_hashcontext_object *hash, zend_long magic,
b/8, \
64, \
sizeof(PHP_TIGER_CTX), \
- 1 \
+ 1, \
+ 0 \
}
PHP_HASH_TIGER_OPS(3, 128);
diff --git a/ext/hash/hash_whirlpool.c b/ext/hash/hash_whirlpool.c
index db5a0da1236..894e2d9f0af 100644
--- a/ext/hash/hash_whirlpool.c
+++ b/ext/hash/hash_whirlpool.c
@@ -457,5 +457,6 @@ const php_hash_ops php_hash_whirlpool_ops = {
64,
64,
sizeof(PHP_WHIRLPOOL_CTX),
- 1
+ 1,
+ 0
};
diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c
index 1c1315afd4b..b7253b0c99c 100644
--- a/ext/hash/hash_xxhash.c
+++ b/ext/hash/hash_xxhash.c
@@ -34,6 +34,7 @@ const php_hash_ops php_hash_xxh32_ops = {
4,
4,
sizeof(PHP_XXH32_CTX),
+ 0,
0
};
@@ -101,6 +102,7 @@ const php_hash_ops php_hash_xxh64_ops = {
8,
8,
sizeof(PHP_XXH64_CTX),
+ 0,
0
};
@@ -152,7 +154,8 @@ const php_hash_ops php_hash_xxh3_64_ops = {
8,
8,
sizeof(PHP_XXH3_64_CTX),
- 0
+ 0,
+ 64
};
typedef XXH_errorcode (*xxh3_reset_with_secret_func_t)(XXH3_state_t*, const void*, size_t);
@@ -257,7 +260,8 @@ const php_hash_ops php_hash_xxh3_128_ops = {
16,
8,
sizeof(PHP_XXH3_128_CTX),
- 0
+ 0,
+ 64
};
PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args)
diff --git a/ext/hash/php_hash.h b/ext/hash/php_hash.h
index 3b058ef48bd..b77557c5bf7 100644
--- a/ext/hash/php_hash.h
+++ b/ext/hash/php_hash.h
@@ -52,6 +52,7 @@ typedef struct _php_hash_ops {
size_t block_size;
size_t context_size;
unsigned is_crypto: 1;
+ size_t context_align;
} php_hash_ops;
struct _php_hashcontext_object {
@@ -155,9 +156,26 @@ PHP_HASH_API int php_hash_unserialize_spec(php_hashcontext_object *hash, const z
static inline void *php_hash_alloc_context(const php_hash_ops *ops) {
/* Zero out context memory so serialization doesn't expose internals */
+ if (ops->context_align > 0) {
+ size_t align = ops->context_align;
+ char *base = ecalloc(1, ops->context_size + align);
+ size_t offset = align - ((uintptr_t)base & (align - 1));
+ char *ptr = base + offset;
+ ptr[-1] = (char)offset;
+ return ptr;
+ }
return ecalloc(1, ops->context_size);
}
+static inline void php_hash_free_context(const php_hash_ops *ops, void *ctx) {
+ if (ops->context_align > 0) {
+ unsigned char offset = ((unsigned char *)ctx)[-1];
+ efree((char *)ctx - offset);
+ return;
+ }
+ efree(ctx);
+}
+
static inline void php_hash_bin2hex(char *out, const unsigned char *in, size_t in_len)
{
static const char hexits[17] = "0123456789abcdef";