From 26ee817a0e5a2bed4994fd4efc13e7f5106ca55c Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Sat, 7 Aug 2021 22:33:21 +0200 Subject: PHP7 compatibility --- src/sp_php_compat.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/sp_php_compat.c (limited to 'src/sp_php_compat.c') diff --git a/src/sp_php_compat.c b/src/sp_php_compat.c new file mode 100644 index 0000000..933acd8 --- /dev/null +++ b/src/sp_php_compat.c @@ -0,0 +1,22 @@ +#include "php_snuffleupagus.h" + +#if PHP_VERSION_ID < 80000 + +// zend_string_concat2 taken from PHP 8.0.9 zend_string.c +// TODO: license clarification + +ZEND_API zend_string *zend_string_concat2( + const char *str1, size_t str1_len, + const char *str2, size_t str2_len) +{ + size_t len = str1_len + str2_len; + zend_string *res = zend_string_alloc(len, 0); + + memcpy(ZSTR_VAL(res), str1, str1_len); + memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len); + ZSTR_VAL(res)[len] = '\0'; + + return res; +} + +#endif -- cgit v1.3 From ecbc2bba7ba2d1c0c766dd16195ee88edbe550a8 Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Sun, 8 Aug 2021 12:44:13 +0200 Subject: more PHP 7 compatibility and license clarification --- PHP_LICENSE | 68 +++++++++++++++++++++++++++++++++++++++ doc/source/faq.rst | 6 ++++ src/sp_php_compat.c | 3 +- src/sp_php_compat.h | 93 +++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 PHP_LICENSE (limited to 'src/sp_php_compat.c') diff --git a/PHP_LICENSE b/PHP_LICENSE new file mode 100644 index 0000000..4076fe9 --- /dev/null +++ b/PHP_LICENSE @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2019 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + +This software consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see . + +PHP includes the Zend Engine, freely available at +. diff --git a/doc/source/faq.rst b/doc/source/faq.rst index bdfc7c1..57b910d 100644 --- a/doc/source/faq.rst +++ b/doc/source/faq.rst @@ -79,6 +79,12 @@ We chose the LGPL because we don't care that much how you're using Snuffleupagus but we'd like to force people to make their improvements/contributions available to everyone. +The complete license text is shipped with the sources and can be found under ``LICENSE``. + +For compatibility with older PHP versions, some original PHP source code was copied or ported back to older versions. +This source code resides in ``src/sp_php_compat.c`` and ``src/sp_php_compat.h`` and retains its original license +`The PHP License, version 3.01 `, also included with the sources as ``PHP_LICENSE``. + What is the different between SNuffleupaugs and a (WAF) like ModSecurity? """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/src/sp_php_compat.c b/src/sp_php_compat.c index 933acd8..cd7c3e7 100644 --- a/src/sp_php_compat.c +++ b/src/sp_php_compat.c @@ -2,8 +2,7 @@ #if PHP_VERSION_ID < 80000 -// zend_string_concat2 taken from PHP 8.0.9 zend_string.c -// TODO: license clarification +// copied from PHP 8.0.9 sources ZEND_API zend_string *zend_string_concat2( const char *str1, size_t str1_len, diff --git a/src/sp_php_compat.h b/src/sp_php_compat.h index 380abe4..992c3e2 100644 --- a/src/sp_php_compat.h +++ b/src/sp_php_compat.h @@ -1,12 +1,93 @@ #if PHP_VERSION_ID < 80000 + +// copied from PHP 8.0.9 sources ZEND_API zend_string *zend_string_concat2( - const char *str1, size_t str1_len, - const char *str2, size_t str2_len); + const char *str1, size_t str1_len, + const char *str2, size_t str2_len); #define ZEND_HASH_REVERSE_FOREACH_KEY_PTR(ht, _h, _key, _ptr) \ - ZEND_HASH_REVERSE_FOREACH(ht, 0); \ - _h = _p->h; \ - _key = _p->key; \ - _ptr = Z_PTR_P(_z); + ZEND_HASH_REVERSE_FOREACH(ht, 0); \ + _h = _p->h; \ + _key = _p->key; \ + _ptr = Z_PTR_P(_z); + +#endif + +#if PHP_VERSION_ID < 70300 + +// copied from PHP 7.4.22 sources + +static zend_always_inline uint32_t zend_gc_delref(zend_refcounted_h *p) { + ZEND_ASSERT(p->refcount > 0); + // ZEND_RC_MOD_CHECK(p); + return --(p->refcount); +} +#define GC_DELREF(p) zend_gc_delref(&(p)->gc) + +static zend_always_inline void zend_string_release_ex(zend_string *s, int persistent) +{ + if (!ZSTR_IS_INTERNED(s)) { + if (GC_DELREF(s) == 0) { + if (persistent) { + ZEND_ASSERT(GC_FLAGS(s) & IS_STR_PERSISTENT); + free(s); + } else { + ZEND_ASSERT(!(GC_FLAGS(s) & IS_STR_PERSISTENT)); + efree(s); + } + } + } +} + +static zend_always_inline void zend_string_efree(zend_string *s) +{ + ZEND_ASSERT(!ZSTR_IS_INTERNED(s)); + ZEND_ASSERT(GC_REFCOUNT(s) <= 1); + ZEND_ASSERT(!(GC_FLAGS(s) & IS_STR_PERSISTENT)); + efree(s); +} + +#endif + +#if PHP_VERSION_ID < 70200 + +#undef ZEND_HASH_REVERSE_FOREACH + +// copied from PHP 7.4.22 sources + +#define ZEND_HASH_REVERSE_FOREACH(_ht, indirect) do { \ + HashTable *__ht = (_ht); \ + uint32_t _idx = __ht->nNumUsed; \ + Bucket *_p = __ht->arData + _idx; \ + zval *_z; \ + for (_idx = __ht->nNumUsed; _idx > 0; _idx--) { \ + _p--; \ + _z = &_p->val; \ + if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \ + _z = Z_INDIRECT_P(_z); \ + } \ + if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue; + + +#define ZEND_HASH_FOREACH_END_DEL() \ + __ht->nNumOfElements--; \ + do { \ + uint32_t j = HT_IDX_TO_HASH(_idx - 1); \ + uint32_t nIndex = _p->h | __ht->nTableMask; \ + uint32_t i = HT_HASH(__ht, nIndex); \ + if (UNEXPECTED(j != i)) { \ + Bucket *prev = HT_HASH_TO_BUCKET(__ht, i); \ + while (Z_NEXT(prev->val) != j) { \ + i = Z_NEXT(prev->val); \ + prev = HT_HASH_TO_BUCKET(__ht, i); \ + } \ + Z_NEXT(prev->val) = Z_NEXT(_p->val); \ + } else { \ + HT_HASH(__ht, nIndex) = Z_NEXT(_p->val); \ + } \ + } while (0); \ + } \ + __ht->nNumUsed = _idx; \ + } while (0) #endif \ No newline at end of file -- cgit v1.3 From c447df6ce8964b2863a50f0f8027d9b234b7507f Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Fri, 19 Nov 2021 14:57:01 +0100 Subject: replaced call_user_func with C level call --- src/sp_php_compat.c | 4 +++ src/sp_php_compat.h | 36 ++++++++++++++++++++++- src/sp_unserialize.c | 81 +++++++++++++++++++++++++++++++++------------------- 3 files changed, 90 insertions(+), 31 deletions(-) (limited to 'src/sp_php_compat.c') diff --git a/src/sp_php_compat.c b/src/sp_php_compat.c index cd7c3e7..a0693c3 100644 --- a/src/sp_php_compat.c +++ b/src/sp_php_compat.c @@ -1,5 +1,9 @@ #include "php_snuffleupagus.h" +/* code in this file is licensed under its original license + The PHP License, version 3.01 (https://www.php.net/license/3_01.txt) + which is also included with these sources in the file `PHP_LICENSE` */ + #if PHP_VERSION_ID < 80000 // copied from PHP 8.0.9 sources diff --git a/src/sp_php_compat.h b/src/sp_php_compat.h index 09d9a1f..d1102a8 100644 --- a/src/sp_php_compat.h +++ b/src/sp_php_compat.h @@ -1,3 +1,7 @@ +/* code in this file is licensed under its original license +The PHP License, version 3.01 (https://www.php.net/license/3_01.txt) +which is also included with these sources in the file `PHP_LICENSE` */ + #if PHP_VERSION_ID < 80000 // copied from PHP 8.0.9 sources @@ -93,4 +97,34 @@ static zend_always_inline void zend_string_efree(zend_string *s) __ht->nNumUsed = _idx; \ } while (0) -#endif \ No newline at end of file +#endif + +// copied from PHP 8.0.11 sources, ext/hash/hash.c + +static inline void php_hash_string_xor_char(unsigned char *out, const unsigned char *in, const unsigned char xor_with, const size_t length) { + size_t i; + for (i=0; i < length; i++) { + out[i] = in[i] ^ xor_with; + } +} + +static inline void php_hash_hmac_prep_key(unsigned char *K, const php_hash_ops *ops, void *context, const unsigned char *key, const size_t key_len) { + memset(K, 0, ops->block_size); + if (key_len > ops->block_size) { + /* Reduce the key first */ + ops->hash_init(context); + ops->hash_update(context, key, key_len); + ops->hash_final(K, context); + } else { + memcpy(K, key, key_len); + } + /* XOR the key with 0x36 to get the ipad) */ + php_hash_string_xor_char(K, K, 0x36, ops->block_size); +} + +static inline void php_hash_hmac_round(unsigned char *final, const php_hash_ops *ops, void *context, const unsigned char *key, const unsigned char *data, const zend_long data_size) { + ops->hash_init(context); + ops->hash_update(context, key, ops->block_size); + ops->hash_update(context, data, data_size); + ops->hash_final(final, context); +} diff --git a/src/sp_unserialize.c b/src/sp_unserialize.c index 5ede015..4a9f565 100644 --- a/src/sp_unserialize.c +++ b/src/sp_unserialize.c @@ -1,5 +1,40 @@ #include "php_snuffleupagus.h" +// condensed version of PHP's php_hash_do_hash_hmac() in ext/hash/hash.c +static zend_string *sp_do_hash_hmac_sha256(char *data, size_t data_len, char *key, size_t key_len) +{ + zend_string *algo = zend_string_init(ZEND_STRL("sha256"), 0); + const php_hash_ops *ops = php_hash_fetch_ops(algo); + zend_string_release_ex(algo, 0); + + if (!ops || !ops->is_crypto) { + sp_log_err("unsupported hash algorithm for hmac: %s", ZSTR_VAL(algo)); + return NULL; + } + + void *context = php_hash_alloc_context(ops); + + unsigned char *K = emalloc(ops->block_size); + zend_string *digest = zend_string_alloc(ops->digest_size, 0); + + php_hash_hmac_prep_key(K, ops, context, (unsigned char *) key, key_len); + php_hash_hmac_round((unsigned char *) ZSTR_VAL(digest), ops, context, K, (unsigned char *) data, data_len); + php_hash_string_xor_char(K, K, 0x6A, ops->block_size); + php_hash_hmac_round((unsigned char *) ZSTR_VAL(digest), ops, context, K, (unsigned char *) ZSTR_VAL(digest), ops->digest_size); + + /* Zero the key */ + ZEND_SECURE_ZERO(K, ops->block_size); + efree(K); + efree(context); + + zend_string *hex_digest = zend_string_safe_alloc(ops->digest_size, 2, 0, 0); + + php_hash_bin2hex(ZSTR_VAL(hex_digest), (unsigned char *) ZSTR_VAL(digest), ops->digest_size); + ZSTR_VAL(hex_digest)[2 * ops->digest_size] = 0; + zend_string_release_ex(digest, 0); + return hex_digest; +} + PHP_FUNCTION(sp_serialize) { zif_handler orig_handler; @@ -10,19 +45,13 @@ PHP_FUNCTION(sp_serialize) { } /* Compute the HMAC of the textual representation of the serialized data*/ - zval func_name; - zval hmac; - zval params[3] = {0}; - - ZVAL_STRING(&func_name, "hash_hmac"); - ZVAL_STRING(¶ms[0], "sha256"); - params[1] = *return_value; - ZVAL_STRING( - ¶ms[2], - ZSTR_VAL(SPCFG(encryption_key))); - call_user_function(CG(function_table), NULL, &func_name, &hmac, 3, params); - - size_t len = Z_STRLEN_P(return_value) + Z_STRLEN(hmac); + zend_string *hmac = sp_do_hash_hmac_sha256(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), ZSTR_VAL(SPCFG(encryption_key)), ZSTR_LEN(SPCFG(encryption_key))); + + if (!hmac) { + zend_bailout(); + } + + size_t len = Z_STRLEN_P(return_value) + ZSTR_LEN(hmac); if (len < Z_STRLEN_P(return_value)) { // LCOV_EXCL_START sp_log_err("overflow_error", @@ -32,8 +61,9 @@ PHP_FUNCTION(sp_serialize) { } /* Append the computed HMAC to the serialized data. */ - return_value->value.str = zend_string_concat2(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), Z_STRVAL(hmac), Z_STRLEN(hmac)); - return; + zend_string *orig_ret_str = return_value->value.str; + RETVAL_NEW_STR(zend_string_concat2(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), ZSTR_VAL(hmac), ZSTR_LEN(hmac))); + zend_string_free(orig_ret_str); } PHP_FUNCTION(sp_unserialize) { @@ -42,7 +72,6 @@ PHP_FUNCTION(sp_unserialize) { char *buf = NULL; char *serialized_str = NULL; char *hmac = NULL; - zval expected_hmac; size_t buf_len = 0; zval *opts = NULL; @@ -62,22 +91,14 @@ PHP_FUNCTION(sp_unserialize) { serialized_str = ecalloc(buf_len - 64 + 1, 1); memcpy(serialized_str, buf, buf_len - 64); - zval func_name; - ZVAL_STRING(&func_name, "hash_hmac"); - - zval params[3] = {0}; - ZVAL_STRING(¶ms[0], "sha256"); - ZVAL_STRING(¶ms[1], serialized_str); - ZVAL_STRING( - ¶ms[2], - ZSTR_VAL(SPCFG(encryption_key))); - call_user_function(CG(function_table), NULL, &func_name, &expected_hmac, 3, - params); + zend_string *expected_hmac = sp_do_hash_hmac_sha256(serialized_str, strlen(serialized_str), ZSTR_VAL(SPCFG(encryption_key)), ZSTR_LEN(SPCFG(encryption_key))); unsigned int status = 0; - for (uint8_t i = 0; i < 64; i++) { - status |= (hmac[i] ^ (Z_STRVAL(expected_hmac))[i]); - } + if (expected_hmac) { + for (uint8_t i = 0; i < 64; i++) { + status |= (hmac[i] ^ (ZSTR_VAL(expected_hmac))[i]); + } + } else { status = 1; } if (0 == status) { if ((orig_handler = zend_hash_str_find_ptr(SPG(sp_internal_functions_hook), ZEND_STRL("unserialize")))) { -- cgit v1.3