summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Fuhrmannek2021-08-02 10:42:12 +0200
committerBen Fuhrmannek2021-08-02 10:42:12 +0200
commit4cda0120313dfd5d71236f6faf87416e93f5f89c (patch)
tree0c2c6d15e8ac5287fb3304f96de719547d9e847a
parent6c132e6a1d8d339a20282afb5a4af52eb6bce9db (diff)
parente62f226c3ed885808c832040872fc2d73ca46dac (diff)
Merge branch 'master' of https://github.com/jvoisin/snuffleupagus
-rw-r--r--.github/workflows/coverity.yml43
-rw-r--r--.github/workflows/distributions_php8.yml32
-rw-r--r--config/default.rules34
-rw-r--r--config/default_php8.rules120
-rw-r--r--debian/control2
-rw-r--r--doc/source/config.rst6
-rw-r--r--doc/source/features.rst12
-rw-r--r--doc/source/papers.rst1
-rw-r--r--src/php_snuffleupagus.h2
-rw-r--r--src/snuffleupagus.c109
-rw-r--r--src/sp_config.c31
-rw-r--r--src/sp_config.h1
-rw-r--r--src/sp_disable_xxe.c31
-rw-r--r--src/sp_execute.c24
-rw-r--r--src/sp_pcre_compat.c11
-rw-r--r--src/sp_pcre_compat.h1
-rw-r--r--src/sp_sloppy.c5
-rw-r--r--src/sp_upload_validation.c4
-rw-r--r--src/sp_utils.c18
-rw-r--r--src/sp_var_parser.c2
-rw-r--r--src/sp_var_value.c31
-rw-r--r--src/sp_wrapper.c4
-rw-r--r--src/tests/broken_configuration/broken_conf_config_invalid_param.phpt6
-rw-r--r--src/tests/broken_configuration_php8/broken_conf_session_encryption_without_encryption_key.phpt1
-rw-r--r--src/tests/broken_configuration_php8/broken_conf_session_encryption_without_env_var.phpt1
-rw-r--r--src/tests/deny_writable/deny_writable_execution_simulation.phpt2
-rw-r--r--src/tests/disable_function/config/disabled_functions.ini1
-rw-r--r--src/tests/disable_function/config/disabled_functions_chmod.ini4
-rw-r--r--src/tests/disable_function/disabled_functions_chmod.phpt14
-rw-r--r--src/tests/disable_function/disabled_functions_chmod_php8.phpt14
-rw-r--r--src/tests/disable_function/disabled_functions_shell_exec_wrong.phpt14
-rw-r--r--src/tests/session_encryption/crypt_session_corrupted_session.phpt2
-rw-r--r--src/tests/session_encryption/crypt_session_invalid.phpt2
-rw-r--r--src/tests/xxe/disable_xxe_dom_disabled.phpt5
34 files changed, 441 insertions, 149 deletions
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
new file mode 100644
index 0000000..d83aa9f
--- /dev/null
+++ b/.github/workflows/coverity.yml
@@ -0,0 +1,43 @@
1name: Coverity scan
2on:
3 schedule:
4 - cron: '0 18 * * 1' # Weekly at 18:00 UTC on Mondays
5
6jobs:
7 latest:
8 runs-on: ubuntu-latest
9 container: debian:stable
10 steps:
11 - name: Checkout code
12 uses: actions/checkout@v2
13 - name: Install dependencies
14 run: |
15 apt update
16 DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends php-dev curl ca-certificates make gcc
17 - name: Remove php8 tests on php7
18 run: rm -rf src/tests/*php8*/
19 - name: Download Coverity Build Tool
20 run: |
21 curl https://scan.coverity.com/download/linux64 --form token=$TOKEN --form project=jvoisin/snuffleupagus -o cov-analysis-linux64.tar.gz
22 mkdir cov-analysis-linux64
23 tar xzf cov-analysis-linux64.tar.gz --strip-components=1 -C cov-analysis-linux64
24 env:
25 TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
26 - name: Configure
27 run: cd src; phpize; ./configure --enable-snuffleupagus; cd -
28 - name: Build with cov-build
29 run: ./cov-analysis-linux64/bin/cov-build --dir cov-int make compile_debug
30 - name: Submit the result to Coverity Scan
31 run: |
32 tar czf snuffleupagus.tgz cov-int
33 curl \
34 --form project=jvoisin/snuffleupagus \
35 --form token=$TOKEN \
36 --form file=@snuffleupagus.tgz \
37 --form version=master \
38 --form email=julien.voisin+coverity@dustri.org \
39 --form description=master \
40 https://scan.coverity.com/builds?project=jvoisin/snuffleupagus
41 env:
42 TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
43
diff --git a/.github/workflows/distributions_php8.yml b/.github/workflows/distributions_php8.yml
new file mode 100644
index 0000000..f055499
--- /dev/null
+++ b/.github/workflows/distributions_php8.yml
@@ -0,0 +1,32 @@
1name: CI for linux distributions, on php8
2on: ['pull_request', 'push']
3
4jobs:
5 alpine:
6 runs-on: ubuntu-latest
7 container: alpine:edge
8 steps:
9 - name: Checkout code
10 uses: actions/checkout@v2
11 - name: Remove php7 tests for php8
12 run: rm -rf src/tests/*php7*/
13 - name: Remove tests failing on alpine for wathever reason
14 run: rm -rf src/tests/*session*/ src/tests/broken_configuration/ src/tests/*cookie* src/tests/upload_validation/
15 - name: Install dependencies
16 run: apk add php8-dev php8-cgi php8-simplexml php8-xml pcre-dev build-base php8-pear php8-openssl
17 - name: Install pecl
18 continue-on-error: true
19 run: pecl install vld-beta
20 - name: Link phpize
21 run: ln -s /usr/bin/phpize8 /usr/bin/phpize
22 - name: Link php-config
23 run: ln -s /usr/bin/php-config8 /usr/bin/php-config
24 - name: Build and run the testsuite
25 continue-on-error: true
26 run: make tests
27 - name: Show logs in case of failure
28 if: ${{ failure() }}
29 continue-on-error: true
30 run: |
31 grep -r . ./src/tests/*/*.out
32 grep -r . ./src/tests/*/*.diff
diff --git a/config/default.rules b/config/default.rules
index 05dd91d..ea65e01 100644
--- a/config/default.rules
+++ b/config/default.rules
@@ -33,8 +33,9 @@ sp.disable_xxe.enable();
33# https://snuffleupagus.readthedocs.io/features.html#protection-against-cross-site-request-forgery 33# https://snuffleupagus.readthedocs.io/features.html#protection-against-cross-site-request-forgery
34sp.cookie.name("PHPSESSID").samesite("lax"); 34sp.cookie.name("PHPSESSID").samesite("lax");
35 35
36# Harden the `chmod` function 36# Harden the `chmod` function (0777 (oct = 511, 0666 = 438)
37sp.disable_function.function("chmod").param("mode").value_r("^[0-9]{2}[67]$").drop(); 37sp.disable_function.function("chmod").param("mode").value("438").drop();
38sp.disable_function.function("chmod").param("mode").value("511").drop();
38 39
39# Prevent various `mail`-related vulnerabilities 40# Prevent various `mail`-related vulnerabilities
40sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop(); 41sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop();
@@ -96,34 +97,7 @@ sp.disable_function.function("is_callable").param("var").value("shell_exec").dro
96sp.disable_function.function("is_callable").param("var").value("proc_open").drop(); 97sp.disable_function.function("is_callable").param("var").value("proc_open").drop();
97sp.disable_function.function("is_callable").param("var").value("passthru").drop(); 98sp.disable_function.function("is_callable").param("var").value("passthru").drop();
98 99
99# Commenting sqli related stuff to improve performance. 100# Ghetto error-based sqli detection
100# TODO figure out why these functions can't be hooked at startup
101# Ghetto sqli hardening
102# sp.disable_function.function("mysql_query").param("query").value_r("/\\*").drop();
103# sp.disable_function.function("mysql_query").param("query").value_r("--").drop();
104# sp.disable_function.function("mysql_query").param("query").value_r("#").drop();
105# sp.disable_function.function("mysql_query").param("query").value_r(";.*;").drop();
106# sp.disable_function.function("mysql_query").param("query").value_r("benchmark").drop();
107# sp.disable_function.function("mysql_query").param("query").value_r("sleep").drop();
108# sp.disable_function.function("mysql_query").param("query").value_r("information_schema").drop();
109
110# sp.disable_function.function("mysqli_query").param("query").value_r("/\\*").drop();
111# sp.disable_function.function("mysqli_query").param("query").value_r("--").drop();
112# sp.disable_function.function("mysqli_query").param("query").value_r("#").drop();
113# sp.disable_function.function("mysqli_query").param("query").value_r(";.*;").drop();
114# sp.disable_function.function("mysqli_query").param("query").value_r("benchmark").drop();
115# sp.disable_function.function("mysqli_query").param("query").value_r("sleep").drop();
116# sp.disable_function.function("mysqli_query").param("query").value_r("information_schema").drop();
117
118# sp.disable_function.function("PDO::query").param("query").value_r("/\\*").drop();
119# sp.disable_function.function("PDO::query").param("query").value_r("--").drop();
120# sp.disable_function.function("PDO::query").param("query").value_r("#").drop();
121# sp.disable_function.function("PDO::query").param("query").value_r(";.*;").drop();
122# sp.disable_function.function("PDO::query").param("query").value_r("benchmark\\s*\\(").drop();
123# sp.disable_function.function("PDO::query").param("query").value_r("sleep\\s*\\(").drop();
124# sp.disable_function.function("PDO::query").param("query").value_r("information_schema").drop();
125
126# Ghetto sqli detection
127# sp.disable_function.function("mysql_query").ret("FALSE").drop(); 101# sp.disable_function.function("mysql_query").ret("FALSE").drop();
128# sp.disable_function.function("mysqli_query").ret("FALSE").drop(); 102# sp.disable_function.function("mysqli_query").ret("FALSE").drop();
129# sp.disable_function.function("PDO::query").ret("FALSE").drop(); 103# sp.disable_function.function("PDO::query").ret("FALSE").drop();
diff --git a/config/default_php8.rules b/config/default_php8.rules
new file mode 100644
index 0000000..c024176
--- /dev/null
+++ b/config/default_php8.rules
@@ -0,0 +1,120 @@
1# This is the default configuration file for Snuffleupagus (https://snuffleupagus.rtfd.io),
2# for php8.
3# It contains "reasonable" defaults that won't break your websites,
4# and a lot of commented directives that you can enable if you want to
5# have a better protection.
6
7# Harden the PRNG
8sp.harden_random.enable();
9
10# Disabled XXE
11sp.disable_xxe.enable();
12
13# Global configuration variables
14# sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS.");
15
16# Globally activate strict mode
17# https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.strict
18# sp.global_strict.enable();
19
20# Prevent unserialize-related exploits
21# sp.unserialize_hmac.enable();
22
23# Only allow execution of read-only files. This is a low-hanging fruit that you should enable.
24# sp.readonly_exec.enable();
25
26# Php has a lot of wrappers, most of them aren't usually useful, you should
27# only enable the ones you're using.
28# sp.wrappers_whitelist.list("file,php,phar");
29
30# Prevent sloppy comparisons.
31# sp.sloppy_comparison.enable();
32
33# use SameSite on session cookie
34# https://snuffleupagus.readthedocs.io/features.html#protection-against-cross-site-request-forgery
35sp.cookie.name("PHPSESSID").samesite("lax");
36
37# Harden the `chmod` function (0777 (oct = 511, 0666 = 438)
38sp.disable_function.function("chmod").param("permissions").value("438").drop();
39sp.disable_function.function("chmod").param("permissions").value("511").drop();
40
41# Prevent various `mail`-related vulnerabilities
42sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop();
43
44# Since it's now burned, me might as well mitigate it publicly
45sp.disable_function.function("putenv").param("assignment").value_r("LD_").drop()
46
47# This one was burned in Nov 2019 - https://gist.github.com/LoadLow/90b60bd5535d6c3927bb24d5f9955b80
48sp.disable_function.function("putenv").param("assignment").value_r("GCONV_").drop()
49
50# Since people are stupid enough to use `extract` on things like $_GET or $_POST, we might as well mitigate this vector
51sp.disable_function.function("extract").param("array").value_r("^_").drop()
52sp.disable_function.function("extract").param("flags").value("0").drop()
53
54# This is also burned:
55# ini_set('open_basedir','..');chdir('..');…;chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/etc/passwd'));
56# Since we have no way of matching on two parameters at the same time, we're
57# blocking calls to open_basedir altogether: nobody is using it via ini_set anyway.
58# Moreover, there are non-public bypasses that are also using this vector ;)
59sp.disable_function.function("ini_set").param("option").value_r("open_basedir").drop()
60
61##Prevent various `include`-related vulnerabilities
62sp.disable_function.function("require_once").value_r("\.(inc|phtml|php)$").allow();
63sp.disable_function.function("include_once").value_r("\.(inc|phtml|php)$").allow();
64sp.disable_function.function("require").value_r("\.(inc|phtml|php)$").allow();
65sp.disable_function.function("include").value_r("\.(inc|phtml|php)$").allow();
66sp.disable_function.function("require_once").drop()
67sp.disable_function.function("include_once").drop()
68sp.disable_function.function("require").drop()
69sp.disable_function.function("include").drop()
70
71# Prevent `system`-related injections
72sp.disable_function.function("system").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
73sp.disable_function.function("shell_exec").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
74sp.disable_function.function("exec").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
75sp.disable_function.function("proc_open").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
76
77# Prevent runtime modification of interesting things
78sp.disable_function.function("ini_set").param("option").value("assert.active").drop();
79sp.disable_function.function("ini_set").param("option").value("zend.assertions").drop();
80sp.disable_function.function("ini_set").param("option").value("memory_limit").drop();
81sp.disable_function.function("ini_set").param("option").value("include_path").drop();
82sp.disable_function.function("ini_set").param("option").value("open_basedir").drop();
83
84# Detect some backdoors via environment recon
85sp.disable_function.function("ini_get").param("option").value("allow_url_fopen").drop();
86sp.disable_function.function("ini_get").param("option").value("open_basedir").drop();
87sp.disable_function.function("ini_get").param("option").value_r("suhosin").drop();
88sp.disable_function.function("function_exists").param("function").value("eval").drop();
89sp.disable_function.function("function_exists").param("function").value("exec").drop();
90sp.disable_function.function("function_exists").param("function").value("system").drop();
91sp.disable_function.function("function_exists").param("function").value("shell_exec").drop();
92sp.disable_function.function("function_exists").param("function").value("proc_open").drop();
93sp.disable_function.function("function_exists").param("function").value("passthru").drop();
94sp.disable_function.function("is_callable").param("value").value("eval").drop();
95sp.disable_function.function("is_callable").param("value").value("exec").drop();
96sp.disable_function.function("is_callable").param("value").value("system").drop();
97sp.disable_function.function("is_callable").param("value").value("shell_exec").drop();
98sp.disable_function.function("is_callable").param("value").value("proc_open").drop();
99sp.disable_function.function("is_callable").param("value").value("passthru").drop();
100
101# Ghetto error-based sqli detection
102# sp.disable_function.function("mysql_query").ret("FALSE").drop();
103# sp.disable_function.function("mysqli_query").ret("FALSE").drop();
104# sp.disable_function.function("PDO::query").ret("FALSE").drop();
105
106# Ensure that certificates are properly verified
107sp.disable_function.function("curl_setopt").param("value").value("1").allow();
108sp.disable_function.function("curl_setopt").param("value").value("2").allow();
109# `81` is SSL_VERIFYHOST and `64` SSL_VERIFYPEER
110sp.disable_function.function("curl_setopt").param("option").value("64").drop().alias("Please don't turn CURLOPT_SSL_VERIFYCLIENT off.");
111sp.disable_function.function("curl_setopt").param("option").value("81").drop().alias("Please don't turn CURLOPT_SSL_VERIFYHOST off.");
112
113# File upload
114sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ph").drop();
115sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ht").drop();
116
117# Logging lockdown
118sp.disable_function.function("ini_set").param("option").value_r("error_log").drop()
119sp.disable_function.function("ini_set").param("option").value_r("error_reporting").drop()
120sp.disable_function.function("ini_set").param("option").value_r("display_errors").drop()
diff --git a/debian/control b/debian/control
index 6e48b93..9e313b6 100644
--- a/debian/control
+++ b/debian/control
@@ -1,7 +1,7 @@
1Source: snuffleupagus 1Source: snuffleupagus
2Priority: optional 2Priority: optional
3Maintainer: Julien (jvoisin) Voisin <julien.voisin+snuffleupagus@dustri.org> 3Maintainer: Julien (jvoisin) Voisin <julien.voisin+snuffleupagus@dustri.org>
4Build-Depends: debhelper (>= 9), php-curl, php-xml, php7.0-dev | php7.1-dev | php7.2-dev | php7.3-dev | php7.4-dev 4Build-Depends: debhelper (>= 9), php-curl, php-xml, php7.0-dev | php7.1-dev | php7.2-dev | php7.3-dev | php7.4-dev | php8.0-dev
5Standards-Version: 4.1.3 5Standards-Version: 4.1.3
6Homepage: https://github.com/jvoisin/snuffleupagus 6Homepage: https://github.com/jvoisin/snuffleupagus
7Section: php 7Section: php
diff --git a/doc/source/config.rst b/doc/source/config.rst
index 258b1ab..84e3fa9 100644
--- a/doc/source/config.rst
+++ b/doc/source/config.rst
@@ -164,6 +164,12 @@ It can either be ``enabled`` or ``disabled`` and can be used in ``simulation`` m
164 sp.unserialize_hmac.enable(); 164 sp.unserialize_hmac.enable();
165 sp.unserialize_hmac.disable(); 165 sp.unserialize_hmac.disable();
166 166
167
168.. warning::
169
170 This feature breaks web applications doing checks on the serialized
171 representation of data on their own, like `WordPress <https://wordpress.com/>`__.
172
167.. _config_cookie-encryption: 173.. _config_cookie-encryption:
168 174
169Cookies-related mitigations 175Cookies-related mitigations
diff --git a/doc/source/features.rst b/doc/source/features.rst
index 2eebc88..25fd62d 100644
--- a/doc/source/features.rst
+++ b/doc/source/features.rst
@@ -480,15 +480,9 @@ to see that people are disabling it on production too.
480We're detecting/preventing this by not allowing the ``CURLOPT_SSL_VERIFYPEER`` and 480We're detecting/preventing this by not allowing the ``CURLOPT_SSL_VERIFYPEER`` and
481``CURLOPT_SSL_VERIFYHOST`` options from being set to ``0``. 481``CURLOPT_SSL_VERIFYHOST`` options from being set to ``0``.
482 482
483*Cheap* SQL injections detection 483*Cheap* error-based SQL injections detection
484"""""""""""""""""""""""""""""""" 484""""""""""""""""""""""""""""""""""""""""""""
485 485
486In some SQL injections, attackers might need to use comments, a feature that is 486If a function performing a SQL query returns ``FALSE``
487often not used in production system, so it might be a good idea to filter
488queries that contains some. The same filtering idea can be used against
489SQL functions that are frequently used in SQL injections, like ``sleep``, ``benchmark``
490or strings like ``version_info``.
491
492On the topic of SQL injections, if a function performing a query returns ``FALSE``
493(indicating an error), it might be useful to dump the request for further analysis. 487(indicating an error), it might be useful to dump the request for further analysis.
494 488
diff --git a/doc/source/papers.rst b/doc/source/papers.rst
index 3cdb909..35905dd 100644
--- a/doc/source/papers.rst
+++ b/doc/source/papers.rst
@@ -101,6 +101,7 @@ Articles
101"""" 101""""
102 102
103- `Sortie de Snuffleupagus 0.7.0 - Los Elefantes <https://linuxfr.org/news/sortie-de-snuffleupagus-0-7-0-los-elefantes>`__ (fr) - linuxfr 103- `Sortie de Snuffleupagus 0.7.0 - Los Elefantes <https://linuxfr.org/news/sortie-de-snuffleupagus-0-7-0-los-elefantes>`__ (fr) - linuxfr
104- `Virtual patching CVE-2021-29447 with Snuffleupagus <https://dustri.org/b/virtual-patching-cve-2021-29447-with-snuffleupagus.html>`__ - dustri.org
104 105
105 106
106Papers 107Papers
diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h
index 248045c..5b2b414 100644
--- a/src/php_snuffleupagus.h
+++ b/src/php_snuffleupagus.h
@@ -56,7 +56,7 @@ typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS);
56#define TSRMLS_FETCH() 56#define TSRMLS_FETCH()
57#define TSRMLS_C 57#define TSRMLS_C
58#else 58#else
59#if ( !HAVE_PCRE && !HAVE_BUNDLED_PCRE ) 59#if (!HAVE_PCRE && !HAVE_BUNDLED_PCRE)
60#error Snuffleupagus requires PHP7+ with PCRE support 60#error Snuffleupagus requires PHP7+ with PCRE support
61#endif 61#endif
62#endif 62#endif
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c
index 9a5ac90..7bf3649 100644
--- a/src/snuffleupagus.c
+++ b/src/snuffleupagus.c
@@ -21,7 +21,7 @@ ZEND_DLEXPORT int sp_zend_startup(zend_extension *extension) {
21} 21}
22// LCOV_EXCL_END 22// LCOV_EXCL_END
23 23
24static inline void sp_op_array_handler(zend_op_array *op) { 24static inline void sp_op_array_handler(zend_op_array *const op) {
25 // We need a filename, and strict mode not already enabled on this op 25 // We need a filename, and strict mode not already enabled on this op
26 if (NULL == op->filename || op->fn_flags & ZEND_ACC_STRICT_TYPES) { 26 if (NULL == op->filename || op->fn_flags & ZEND_ACC_STRICT_TYPES) {
27 return; 27 return;
@@ -47,8 +47,7 @@ static PHP_INI_MH(StrictMode) {
47PHP_INI_BEGIN() 47PHP_INI_BEGIN()
48PHP_INI_ENTRY("sp.configuration_file", "", PHP_INI_SYSTEM, 48PHP_INI_ENTRY("sp.configuration_file", "", PHP_INI_SYSTEM,
49 OnUpdateConfiguration) 49 OnUpdateConfiguration)
50PHP_INI_ENTRY("sp.allow_broken_configuration", "0", PHP_INI_SYSTEM, 50PHP_INI_ENTRY("sp.allow_broken_configuration", "0", PHP_INI_SYSTEM, StrictMode)
51 StrictMode)
52PHP_INI_END() 51PHP_INI_END()
53 52
54ZEND_DLEXPORT zend_extension zend_extension_entry = { 53ZEND_DLEXPORT zend_extension zend_extension_entry = {
@@ -59,24 +58,24 @@ ZEND_DLEXPORT zend_extension zend_extension_entry = {
59 PHP_SNUFFLEUPAGUS_COPYRIGHT, 58 PHP_SNUFFLEUPAGUS_COPYRIGHT,
60 sp_zend_startup, 59 sp_zend_startup,
61 NULL, 60 NULL,
62 NULL, /* activate_func_t */ 61 NULL, /* activate_func_t */
63 NULL, /* deactivate_func_t */ 62 NULL, /* deactivate_func_t */
64 NULL, /* message_handler_func_t */ 63 NULL, /* message_handler_func_t */
65 sp_op_array_handler, /* op_array_handler_func_t */ 64 sp_op_array_handler, /* op_array_handler_func_t */
66 NULL, /* statement_handler_func_t */ 65 NULL, /* statement_handler_func_t */
67 NULL, /* fcall_begin_handler_func_t */ 66 NULL, /* fcall_begin_handler_func_t */
68 NULL, /* fcall_end_handler_func_t */ 67 NULL, /* fcall_end_handler_func_t */
69 NULL, /* op_array_ctor_func_t */ 68 NULL, /* op_array_ctor_func_t */
70 NULL, /* op_array_dtor_func_t */ 69 NULL, /* op_array_dtor_func_t */
71 STANDARD_ZEND_EXTENSION_PROPERTIES}; 70 STANDARD_ZEND_EXTENSION_PROPERTIES};
72 71
73PHP_GINIT_FUNCTION(snuffleupagus) { 72PHP_GINIT_FUNCTION(snuffleupagus) {
74 snuffleupagus_globals->is_config_valid = SP_CONFIG_NONE; 73 snuffleupagus_globals->is_config_valid = SP_CONFIG_NONE;
75 snuffleupagus_globals->in_eval = 0; 74 snuffleupagus_globals->in_eval = 0;
76 75
77#define SP_INIT_HT(F) snuffleupagus_globals->F = \ 76#define SP_INIT_HT(F) \
78 pemalloc(sizeof(*(snuffleupagus_globals->F)), 1); \ 77 snuffleupagus_globals->F = pemalloc(sizeof(*(snuffleupagus_globals->F)), 1); \
79 zend_hash_init(snuffleupagus_globals->F, 10, NULL, NULL, 1); 78 zend_hash_init(snuffleupagus_globals->F, 10, NULL, NULL, 1);
80 SP_INIT_HT(disabled_functions_hook); 79 SP_INIT_HT(disabled_functions_hook);
81 SP_INIT_HT(sp_internal_functions_hook); 80 SP_INIT_HT(sp_internal_functions_hook);
82 SP_INIT_HT(sp_eval_blacklist_functions_hook); 81 SP_INIT_HT(sp_eval_blacklist_functions_hook);
@@ -86,8 +85,9 @@ PHP_GINIT_FUNCTION(snuffleupagus) {
86 SP_INIT_HT(config.config_disabled_functions_ret_hooked); 85 SP_INIT_HT(config.config_disabled_functions_ret_hooked);
87#undef SP_INIT_HT 86#undef SP_INIT_HT
88 87
89#define SP_INIT(F) snuffleupagus_globals->config.F = \ 88#define SP_INIT(F) \
90 pecalloc(sizeof(*(snuffleupagus_globals->config.F)), 1, 1); 89 snuffleupagus_globals->config.F = \
90 pecalloc(sizeof(*(snuffleupagus_globals->config.F)), 1, 1);
91 SP_INIT(config_unserialize); 91 SP_INIT(config_unserialize);
92 SP_INIT(config_random); 92 SP_INIT(config_random);
93 SP_INIT(config_sloppy); 93 SP_INIT(config_sloppy);
@@ -121,23 +121,22 @@ PHP_MINIT_FUNCTION(snuffleupagus) {
121 return SUCCESS; 121 return SUCCESS;
122} 122}
123 123
124static void free_disabled_functions_hashtable(HashTable *ht) { 124static void free_disabled_functions_hashtable(HashTable *const ht) {
125 void *ptr = NULL; 125 void *ptr = NULL;
126 ZEND_HASH_FOREACH_PTR(ht, ptr) { sp_list_free(ptr); } 126 ZEND_HASH_FOREACH_PTR(ht, ptr) { sp_list_free(ptr); }
127 ZEND_HASH_FOREACH_END(); 127 ZEND_HASH_FOREACH_END();
128} 128}
129 129
130PHP_MSHUTDOWN_FUNCTION(snuffleupagus) { 130PHP_MSHUTDOWN_FUNCTION(snuffleupagus) {
131
132#define FREE_HT(F) \ 131#define FREE_HT(F) \
133 zend_hash_destroy(SNUFFLEUPAGUS_G(F)); \ 132 zend_hash_destroy(SNUFFLEUPAGUS_G(F)); \
134 pefree(SNUFFLEUPAGUS_G(F), 1); 133 pefree(SNUFFLEUPAGUS_G(F), 1);
135 FREE_HT(disabled_functions_hook); 134 FREE_HT(disabled_functions_hook);
136 FREE_HT(sp_eval_blacklist_functions_hook); 135 FREE_HT(sp_eval_blacklist_functions_hook);
137 136
138#define FREE_HT_LIST(F) \ 137#define FREE_HT_LIST(F) \
139 free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).F); \ 138 free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).F); \
140 FREE_HT(config.F); 139 FREE_HT(config.F);
141 FREE_HT_LIST(config_disabled_functions); 140 FREE_HT_LIST(config_disabled_functions);
142 FREE_HT_LIST(config_disabled_functions_hooked); 141 FREE_HT_LIST(config_disabled_functions_hooked);
143 FREE_HT_LIST(config_disabled_functions_ret); 142 FREE_HT_LIST(config_disabled_functions_ret);
@@ -145,18 +144,21 @@ PHP_MSHUTDOWN_FUNCTION(snuffleupagus) {
145#undef FREE_HT_LIST 144#undef FREE_HT_LIST
146#undef FREE_HT 145#undef FREE_HT
147 146
148#define FREE_LST_DISABLE(L) \ 147#define FREE_LST_DISABLE(L) \
149 do { \ 148 do { \
150 sp_list_node *_n = SNUFFLEUPAGUS_G(config).L; \ 149 sp_list_node *_n = SNUFFLEUPAGUS_G(config).L; \
151 sp_disabled_function_list_free(_n); \ 150 sp_disabled_function_list_free(_n); \
152 sp_list_free(_n); \ 151 sp_list_free(_n); \
153 } while (0) 152 } while (0)
154 FREE_LST_DISABLE(config_disabled_functions_reg->disabled_functions); 153 FREE_LST_DISABLE(config_disabled_functions_reg->disabled_functions);
155 FREE_LST_DISABLE(config_disabled_functions_reg_ret->disabled_functions); 154 FREE_LST_DISABLE(config_disabled_functions_reg_ret->disabled_functions);
156#undef FREE_LST_DISABLE 155#undef FREE_LST_DISABLE
157 156
157 sp_list_node *_n = SNUFFLEUPAGUS_G(config).config_cookie->cookies;
158 sp_cookie_list_free(_n);
159 sp_list_free(_n);
160
158#define FREE_LST(L) sp_list_free(SNUFFLEUPAGUS_G(config).L); 161#define FREE_LST(L) sp_list_free(SNUFFLEUPAGUS_G(config).L);
159 FREE_LST(config_cookie->cookies);
160 FREE_LST(config_eval->blacklist); 162 FREE_LST(config_eval->blacklist);
161 FREE_LST(config_eval->whitelist); 163 FREE_LST(config_eval->whitelist);
162 FREE_LST(config_wrapper->whitelist); 164 FREE_LST(config_wrapper->whitelist);
@@ -184,24 +186,26 @@ PHP_MSHUTDOWN_FUNCTION(snuffleupagus) {
184} 186}
185 187
186PHP_RINIT_FUNCTION(snuffleupagus) { 188PHP_RINIT_FUNCTION(snuffleupagus) {
187 const sp_config_wrapper* config_wrapper = 189 const sp_config_wrapper *const config_wrapper =
188 SNUFFLEUPAGUS_G(config).config_wrapper; 190 SNUFFLEUPAGUS_G(config).config_wrapper;
189#if defined(COMPILE_DL_SNUFFLEUPAGUS) && defined(ZTS) 191#if defined(COMPILE_DL_SNUFFLEUPAGUS) && defined(ZTS)
190 ZEND_TSRMLS_CACHE_UPDATE(); 192 ZEND_TSRMLS_CACHE_UPDATE();
191#endif 193#endif
192 194
193 if (!SNUFFLEUPAGUS_G(allow_broken_configuration)) { 195 if (!SNUFFLEUPAGUS_G(allow_broken_configuration)) {
194 if (SNUFFLEUPAGUS_G(is_config_valid) == SP_CONFIG_INVALID ) { 196 if (SNUFFLEUPAGUS_G(is_config_valid) == SP_CONFIG_INVALID) {
195 sp_log_err("config", "Invalid configuration file"); 197 sp_log_err("config", "Invalid configuration file");
196 } else if (SNUFFLEUPAGUS_G(is_config_valid) == SP_CONFIG_NONE) { 198 } else if (SNUFFLEUPAGUS_G(is_config_valid) == SP_CONFIG_NONE) {
197 sp_log_warn("config", "No configuration specificed via sp.configuration_file"); 199 sp_log_warn("config",
200 "No configuration specificed via sp.configuration_file");
198 } 201 }
199 } 202 }
200 203
201 // We need to disable wrappers loaded by extensions loaded after SNUFFLEUPAGUS. 204 // We need to disable wrappers loaded by extensions loaded after
205 // SNUFFLEUPAGUS.
202 if (config_wrapper->enabled && 206 if (config_wrapper->enabled &&
203 zend_hash_num_elements(php_stream_get_url_stream_wrappers_hash()) != 207 zend_hash_num_elements(php_stream_get_url_stream_wrappers_hash()) !=
204 config_wrapper->num_wrapper) { 208 config_wrapper->num_wrapper) {
205 sp_disable_wrapper(); 209 sp_disable_wrapper();
206 } 210 }
207 211
@@ -218,7 +222,7 @@ PHP_RSHUTDOWN_FUNCTION(snuffleupagus) { return SUCCESS; }
218 222
219PHP_MINFO_FUNCTION(snuffleupagus) { 223PHP_MINFO_FUNCTION(snuffleupagus) {
220 const char *valid_config; 224 const char *valid_config;
221 switch(SNUFFLEUPAGUS_G(is_config_valid)) { 225 switch (SNUFFLEUPAGUS_G(is_config_valid)) {
222 case SP_CONFIG_VALID: 226 case SP_CONFIG_VALID:
223 valid_config = "yes"; 227 valid_config = "yes";
224 break; 228 break;
@@ -230,10 +234,11 @@ PHP_MINFO_FUNCTION(snuffleupagus) {
230 valid_config = "no"; 234 valid_config = "no";
231 } 235 }
232 php_info_print_table_start(); 236 php_info_print_table_start();
233 php_info_print_table_row(2, "snuffleupagus support", 237 php_info_print_table_row(
234 SNUFFLEUPAGUS_G(is_config_valid)?"enabled":"disabled"); 238 2, "snuffleupagus support",
239 SNUFFLEUPAGUS_G(is_config_valid) ? "enabled" : "disabled");
235 php_info_print_table_row(2, "Version", PHP_SNUFFLEUPAGUS_VERSION); 240 php_info_print_table_row(2, "Version", PHP_SNUFFLEUPAGUS_VERSION);
236 php_info_print_table_row( 2, "Valid config", valid_config); 241 php_info_print_table_row(2, "Valid config", valid_config);
237 php_info_print_table_end(); 242 php_info_print_table_end();
238 DISPLAY_INI_ENTRIES(); 243 DISPLAY_INI_ENTRIES();
239} 244}
@@ -245,14 +250,15 @@ static PHP_INI_MH(OnUpdateConfiguration) {
245 return FAILURE; 250 return FAILURE;
246 } 251 }
247 252
248 glob_t globbuf; 253 char *str = new_value->val;
249 char *config_file;
250 char *rest = new_value->val;
251 254
252 while ((config_file = strtok_r(rest, ",", &rest))) { 255 while (1) {
253 int ret = glob(config_file, GLOB_NOCHECK, NULL, &globbuf); 256 // We don't care about overwriting new_value->val
257 const char *config_file = strsep(&str, ",");
258 if (config_file == NULL) break;
254 259
255 if (ret != 0) { 260 glob_t globbuf;
261 if (0 != glob(config_file, GLOB_NOCHECK, NULL, &globbuf)) {
256 SNUFFLEUPAGUS_G(is_config_valid) = SP_CONFIG_INVALID; 262 SNUFFLEUPAGUS_G(is_config_valid) = SP_CONFIG_INVALID;
257 globfree(&globbuf); 263 globfree(&globbuf);
258 return FAILURE; 264 return FAILURE;
@@ -315,11 +321,12 @@ static PHP_INI_MH(OnUpdateConfiguration) {
315 321
316 // If `zend_write_default` is not NULL it is already hooked. 322 // If `zend_write_default` is not NULL it is already hooked.
317 if ((zend_hash_str_find( 323 if ((zend_hash_str_find(
318 SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, "echo", 324 SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, "echo",
319 sizeof("echo") - 1) || 325 sizeof("echo") - 1) ||
320 zend_hash_str_find( 326 zend_hash_str_find(
321 SNUFFLEUPAGUS_G(config).config_disabled_functions_ret_hooked, "echo", 327 SNUFFLEUPAGUS_G(config).config_disabled_functions_ret_hooked, "echo",
322 sizeof("echo") - 1)) && NULL == zend_write_default) { 328 sizeof("echo") - 1)) &&
329 NULL == zend_write_default && zend_write != hook_echo) {
323 zend_write_default = zend_write; 330 zend_write_default = zend_write;
324 zend_write = hook_echo; 331 zend_write = hook_echo;
325 } 332 }
diff --git a/src/sp_config.c b/src/sp_config.c
index 69730e3..c12b435 100644
--- a/src/sp_config.c
+++ b/src/sp_config.c
@@ -6,7 +6,7 @@
6 6
7size_t sp_line_no; 7size_t sp_line_no;
8 8
9sp_config_tokens const sp_func[] = { 9static sp_config_tokens const sp_func[] = {
10 {.func = parse_unserialize, .token = SP_TOKEN_UNSERIALIZE_HMAC}, 10 {.func = parse_unserialize, .token = SP_TOKEN_UNSERIALIZE_HMAC},
11 {.func = parse_random, .token = SP_TOKEN_HARDEN_RANDOM}, 11 {.func = parse_random, .token = SP_TOKEN_HARDEN_RANDOM},
12 {.func = parse_log_media, .token = SP_TOKEN_LOG_MEDIA}, 12 {.func = parse_log_media, .token = SP_TOKEN_LOG_MEDIA},
@@ -73,7 +73,11 @@ int parse_list(char *restrict line, char *restrict keyword, void *list_ptr) {
73 } 73 }
74 74
75 tmp = ZSTR_VAL(value); 75 tmp = ZSTR_VAL(value);
76 while ((token = strtok_r(tmp, ",", &tmp))) { 76 while (1) {
77 token = strsep(&tmp, ",");
78 if (token == NULL) {
79 break;
80 }
77 *list = sp_list_insert(*list, zend_string_init(token, strlen(token), 1)); 81 *list = sp_list_insert(*list, zend_string_init(token, strlen(token), 1));
78 } 82 }
79 83
@@ -216,11 +220,32 @@ void sp_disabled_function_list_free(sp_list_node *list) {
216 sp_list_node *cursor = list; 220 sp_list_node *cursor = list;
217 while (cursor) { 221 while (cursor) {
218 sp_disabled_function *df = cursor->data; 222 sp_disabled_function *df = cursor->data;
219 if (df && df->functions_list) sp_list_free(df->functions_list);
220 if (df) { 223 if (df) {
224 sp_list_free(df->functions_list);
225 sp_list_free(df->param_array_keys);
226 sp_list_free(df->var_array_keys);
227
228 sp_pcre_free(df->r_filename);
229 sp_pcre_free(df->r_function);
230 sp_pcre_free(df->r_param);
231 sp_pcre_free(df->r_ret);
232 sp_pcre_free(df->r_value);
233 sp_pcre_free(df->r_key);
234
221 sp_tree_free(df->param); 235 sp_tree_free(df->param);
222 sp_tree_free(df->var); 236 sp_tree_free(df->var);
223 } 237 }
224 cursor = cursor->next; 238 cursor = cursor->next;
225 } 239 }
226} 240}
241
242void sp_cookie_list_free(sp_list_node *list) {
243 sp_list_node *cursor = list;
244 while (cursor) {
245 sp_cookie *c = cursor->data;
246 if (c) {
247 sp_pcre_free(c->name_r);
248 }
249 cursor = cursor->next;
250 }
251}
diff --git a/src/sp_config.h b/src/sp_config.h
index b06e8be..e7b1473 100644
--- a/src/sp_config.h
+++ b/src/sp_config.h
@@ -282,5 +282,6 @@ int parse_list(char *restrict, char *restrict, void *);
282 282
283// cleanup 283// cleanup
284void sp_disabled_function_list_free(sp_list_node *); 284void sp_disabled_function_list_free(sp_list_node *);
285void sp_cookie_list_free(sp_list_node *);
285 286
286#endif /* SP_CONFIG_H */ 287#endif /* SP_CONFIG_H */
diff --git a/src/sp_disable_xxe.c b/src/sp_disable_xxe.c
index 113d84b..f9712b5 100644
--- a/src/sp_disable_xxe.c
+++ b/src/sp_disable_xxe.c
@@ -1,26 +1,41 @@
1#include "php_snuffleupagus.h" 1#include "php_snuffleupagus.h"
2 2
3PHP_FUNCTION(sp_libxml_disable_entity_loader) { RETURN_TRUE; } 3PHP_FUNCTION(sp_libxml_disable_entity_loader) {
4 sp_log_warn("xxe",
5 "A call to libxml_disable_entity_loader was tried and nopped");
6 RETURN_TRUE;
7}
8
9PHP_FUNCTION(sp_libxml_set_external_entity_loader) {
10 sp_log_warn(
11 "xxe",
12 "A call to libxml_set_external_entity_loader was tried and nopped");
13 RETURN_TRUE;
14}
4 15
5int hook_libxml_disable_entity_loader() { 16int hook_libxml_disable_entity_loader() {
6 TSRMLS_FETCH(); 17 TSRMLS_FETCH();
7 18
8// External entities are disabled by default in PHP8+
9#if PHP_VERSION_ID < 80000
10 /* Call the php function here instead of re-implementing it is a bit
11 * ugly, but we do not want to introduce compile-time dependencies against
12 * libxml. */
13 zval func_name; 19 zval func_name;
14 zval hmac; 20 zval retval;
15 zval params[1]; 21 zval params[1];
16 22
23#if PHP_VERSION_ID < 80000
24 // This function is deprecated in PHP8, but better safe than sorry for php7.
17 ZVAL_STRING(&func_name, "libxml_disable_entity_loader"); 25 ZVAL_STRING(&func_name, "libxml_disable_entity_loader");
18 ZVAL_STRING(&params[0], "true"); 26 ZVAL_STRING(&params[0], "true");
19 call_user_function(CG(function_table), NULL, &func_name, &hmac, 1, params); 27 call_user_function(CG(function_table), NULL, &func_name, &retval, 1, params);
20#endif 28#endif
21 29
30 // This is now the recommended way to disable external entities
31 ZVAL_STRING(&func_name, "libxml_set_external_entity_loader");
32 ZVAL_NULL(&params[0]);
33 call_user_function(CG(function_table), NULL, &func_name, &retval, 1, params);
34
22 HOOK_FUNCTION("libxml_disable_entity_loader", sp_internal_functions_hook, 35 HOOK_FUNCTION("libxml_disable_entity_loader", sp_internal_functions_hook,
23 PHP_FN(sp_libxml_disable_entity_loader)); 36 PHP_FN(sp_libxml_disable_entity_loader));
37 HOOK_FUNCTION("libxml_set_external_entity_loader", sp_internal_functions_hook,
38 PHP_FN(sp_libxml_set_external_entity_loader));
24 39
25 return SUCCESS; 40 return SUCCESS;
26} 41}
diff --git a/src/sp_execute.c b/src/sp_execute.c
index de83a2a..7d078b0 100644
--- a/src/sp_execute.c
+++ b/src/sp_execute.c
@@ -274,17 +274,23 @@ int hook_execute(void) {
274 TSRMLS_FETCH(); 274 TSRMLS_FETCH();
275 275
276 if (NULL == orig_execute_ex && NULL == orig_zend_stream_open) { 276 if (NULL == orig_execute_ex && NULL == orig_zend_stream_open) {
277 /* zend_execute_ex is used for "user" function calls */ 277 if (zend_execute_ex != sp_execute_ex) {
278 orig_execute_ex = zend_execute_ex; 278 /* zend_execute_ex is used for "user" function calls */
279 zend_execute_ex = sp_execute_ex; 279 orig_execute_ex = zend_execute_ex;
280 zend_execute_ex = sp_execute_ex;
281 }
280 282
281 /* zend_execute_internal is used for "builtin" functions calls */ 283 if (zend_execute_internal != sp_zend_execute_internal) {
282 orig_zend_execute_internal = zend_execute_internal; 284 /* zend_execute_internal is used for "builtin" functions calls */
283 zend_execute_internal = sp_zend_execute_internal; 285 orig_zend_execute_internal = zend_execute_internal;
286 zend_execute_internal = sp_zend_execute_internal;
287 }
284 288
285 /* zend_stream_open_function is used for include-related stuff */ 289 if (zend_stream_open_function != sp_stream_open) {
286 orig_zend_stream_open = zend_stream_open_function; 290 /* zend_stream_open_function is used for include-related stuff */
287 zend_stream_open_function = sp_stream_open; 291 orig_zend_stream_open = zend_stream_open_function;
292 zend_stream_open_function = sp_stream_open;
293 }
288 } 294 }
289 295
290 return SUCCESS; 296 return SUCCESS;
diff --git a/src/sp_pcre_compat.c b/src/sp_pcre_compat.c
index 509a8ea..3bd00ca 100644
--- a/src/sp_pcre_compat.c
+++ b/src/sp_pcre_compat.c
@@ -1,5 +1,12 @@
1#include "php_snuffleupagus.h" 1#include "php_snuffleupagus.h"
2 2
3inline void sp_pcre_free(sp_pcre* regexp) {
4#ifdef SP_HAS_PCRE2
5 pcre2_code_free(regexp);
6#endif
7 regexp = NULL;
8}
9
3sp_pcre* sp_pcre_compile(const char* const pattern) { 10sp_pcre* sp_pcre_compile(const char* const pattern) {
4 assert(NULL != pattern); 11 assert(NULL != pattern);
5 12
@@ -34,7 +41,11 @@ bool ZEND_HOT sp_is_regexp_matching_len(const sp_pcre* regexp, const char* str,
34#ifdef SP_HAS_PCRE2 41#ifdef SP_HAS_PCRE2
35 pcre2_match_data* match_data = 42 pcre2_match_data* match_data =
36 pcre2_match_data_create_from_pattern(regexp, NULL); 43 pcre2_match_data_create_from_pattern(regexp, NULL);
44 if (NULL == match_data) {
45 sp_log_err("regexp", "Unable to get memory for a regxp.");
46 }
37 ret = pcre2_match(regexp, (PCRE2_SPTR)str, len, 0, 0, match_data, NULL); 47 ret = pcre2_match(regexp, (PCRE2_SPTR)str, len, 0, 0, match_data, NULL);
48 pcre2_match_data_free(match_data);
38#else 49#else
39 int vec[30]; 50 int vec[30];
40 ret = pcre_exec(regexp, NULL, str, len, 0, 0, vec, sizeof(vec) / sizeof(int)); 51 ret = pcre_exec(regexp, NULL, str, len, 0, 0, vec, sizeof(vec) / sizeof(int));
diff --git a/src/sp_pcre_compat.h b/src/sp_pcre_compat.h
index 14c33b2..725004d 100644
--- a/src/sp_pcre_compat.h
+++ b/src/sp_pcre_compat.h
@@ -17,6 +17,7 @@
17#endif 17#endif
18 18
19sp_pcre* sp_pcre_compile(const char* str); 19sp_pcre* sp_pcre_compile(const char* str);
20void sp_pcre_free(sp_pcre* regexp);
20#define sp_is_regexp_matching_zend(regexp, zstr) \ 21#define sp_is_regexp_matching_zend(regexp, zstr) \
21 sp_is_regexp_matching_len(regexp, ZSTR_VAL(zstr), ZSTR_LEN(zstr)) 22 sp_is_regexp_matching_len(regexp, ZSTR_VAL(zstr), ZSTR_LEN(zstr))
22#define sp_is_regexp_matching(regexp, str) \ 23#define sp_is_regexp_matching(regexp, str) \
diff --git a/src/sp_sloppy.c b/src/sp_sloppy.c
index f9ed718..ff2d644 100644
--- a/src/sp_sloppy.c
+++ b/src/sp_sloppy.c
@@ -99,12 +99,13 @@ PHP_FUNCTION(sp_array_keys) {
99void hook_sloppy() { 99void hook_sloppy() {
100 TSRMLS_FETCH(); 100 TSRMLS_FETCH();
101 101
102 if (NULL == orig_zend_compile_file) { 102 if (NULL == orig_zend_compile_file && zend_compile_file != sp_compile_file) {
103 orig_zend_compile_file = zend_compile_file; 103 orig_zend_compile_file = zend_compile_file;
104 zend_compile_file = sp_compile_file; 104 zend_compile_file = sp_compile_file;
105 } 105 }
106 106
107 if (NULL == orig_zend_compile_string) { 107 if (NULL == orig_zend_compile_string &&
108 zend_compile_string != sp_compile_string) {
108 orig_zend_compile_string = zend_compile_string; 109 orig_zend_compile_string = zend_compile_string;
109 zend_compile_string = sp_compile_string; 110 zend_compile_string = sp_compile_string;
110 } 111 }
diff --git a/src/sp_upload_validation.c b/src/sp_upload_validation.c
index f3ae311..cebab3e 100644
--- a/src/sp_upload_validation.c
+++ b/src/sp_upload_validation.c
@@ -103,6 +103,10 @@ int sp_rfc1867_callback(unsigned int event, void *event_data, void **extra) {
103#endif 103#endif
104 104
105void hook_upload() { 105void hook_upload() {
106 if (php_rfc1867_callback == sp_rfc1867_callback) {
107 return;
108 }
109
106 if (NULL == sp_rfc1867_orig_callback) { 110 if (NULL == sp_rfc1867_orig_callback) {
107 sp_rfc1867_orig_callback = php_rfc1867_callback; 111 sp_rfc1867_orig_callback = php_rfc1867_callback;
108 php_rfc1867_callback = sp_rfc1867_callback; 112 php_rfc1867_callback = sp_rfc1867_callback;
diff --git a/src/sp_utils.c b/src/sp_utils.c
index 73c0546..5f25920 100644
--- a/src/sp_utils.c
+++ b/src/sp_utils.c
@@ -19,24 +19,6 @@ const char* get_ipaddr() {
19 return fwd_ip; 19 return fwd_ip;
20 } 20 }
21 21
22 /* Some hosters (like heroku, see
23 * https://github.com/jvoisin/snuffleupagus/issues/336) are clearing the
24 * environment variables, so we don't have access to them, hence why we're
25 * resorting to $_SERVER['REMOTE_ADDR'].
26 */
27 if (!Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER])) {
28 const zval* const globals_client_ip =
29 zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]),
30 "REMOTE_ADDR", sizeof("REMOTE_ADDR") - 1);
31 if (globals_client_ip) {
32 if (Z_TYPE_P(globals_client_ip) == IS_STRING) {
33 if (Z_STRLEN_P(globals_client_ip) != 0) {
34 return estrdup(Z_STRVAL_P(globals_client_ip));
35 }
36 }
37 }
38 }
39
40 return default_ipaddr; 22 return default_ipaddr;
41} 23}
42 24
diff --git a/src/sp_var_parser.c b/src/sp_var_parser.c
index b066b84..bb5a5c0 100644
--- a/src/sp_var_parser.c
+++ b/src/sp_var_parser.c
@@ -20,7 +20,7 @@ static sp_list_node *parse_str_tokens(const char *str,
20 return tokens_list; 20 return tokens_list;
21} 21}
22 22
23static bool is_var_name_valid(const char *name) { 23static bool is_var_name_valid(const char *const name) {
24 static sp_pcre *regexp_const = NULL; 24 static sp_pcre *regexp_const = NULL;
25 static sp_pcre *regexp_var = NULL; 25 static sp_pcre *regexp_var = NULL;
26 26
diff --git a/src/sp_var_value.c b/src/sp_var_value.c
index b9ac357..fe37f99 100644
--- a/src/sp_var_value.c
+++ b/src/sp_var_value.c
@@ -1,6 +1,7 @@
1#include "php_snuffleupagus.h" 1#include "php_snuffleupagus.h"
2 2
3static zval *get_param_var(zend_execute_data *ed, const char *var_name) { 3static zval *get_param_var(const zend_execute_data *const ed,
4 const char *const var_name, bool print) {
4 unsigned int nb_param = ed->func->common.num_args; 5 unsigned int nb_param = ed->func->common.num_args;
5 6
6 for (unsigned int i = 0; i < nb_param; i++) { 7 for (unsigned int i = 0; i < nb_param; i++) {
@@ -13,11 +14,14 @@ static zval *get_param_var(zend_execute_data *ed, const char *var_name) {
13 if (0 == strcmp(arg_name, var_name)) { 14 if (0 == strcmp(arg_name, var_name)) {
14 return ZEND_CALL_VAR_NUM(ed, i); 15 return ZEND_CALL_VAR_NUM(ed, i);
15 } 16 }
17 if (print == true) {
18 sp_log_warn("config", " - %d parameter's name: '%s'", i, arg_name);
19 }
16 } 20 }
17 return NULL; 21 return NULL;
18} 22}
19 23
20static zval *get_local_var(zend_execute_data *ed, const char *var_name) { 24static zval *get_local_var(zend_execute_data *ed, const char *const var_name) {
21 zend_execute_data *orig_execute_data = ed; 25 zend_execute_data *orig_execute_data = ed;
22 zend_execute_data *current = ed; 26 zend_execute_data *current = ed;
23 27
@@ -48,7 +52,7 @@ static zval *get_local_var(zend_execute_data *ed, const char *var_name) {
48 return NULL; 52 return NULL;
49} 53}
50 54
51static zval *get_constant(const char *value) { 55static zval *get_constant(const char *const value) {
52 zend_string *name = zend_string_init(value, strlen(value), 0); 56 zend_string *name = zend_string_init(value, strlen(value), 0);
53 zval *zvalue = zend_get_constant_ex(name, NULL, ZEND_FETCH_CLASS_SILENT); 57 zval *zvalue = zend_get_constant_ex(name, NULL, ZEND_FETCH_CLASS_SILENT);
54 zend_string_release(name); 58 zend_string_release(name);
@@ -68,7 +72,7 @@ static zval *get_var_value(zend_execute_data *ed, const char *var_name,
68 } 72 }
69 73
70 if (is_param) { 74 if (is_param) {
71 zval *zvalue = get_param_var(ed, var_name); 75 zval *zvalue = get_param_var(ed, var_name, false);
72 if (!zvalue) { 76 if (!zvalue) {
73 char *complete_function_path = get_complete_function_path(ed); 77 char *complete_function_path = get_complete_function_path(ed);
74 sp_log_warn("config", 78 sp_log_warn("config",
@@ -76,6 +80,7 @@ static zval *get_var_value(zend_execute_data *ed, const char *var_name,
76 "'%s' of the function '%s', but the parameter does " 80 "'%s' of the function '%s', but the parameter does "
77 "not exists.", 81 "not exists.",
78 var_name, complete_function_path); 82 var_name, complete_function_path);
83 get_param_var(ed, var_name, true);
79 efree(complete_function_path); 84 efree(complete_function_path);
80 return NULL; 85 return NULL;
81 } 86 }
@@ -85,8 +90,8 @@ static zval *get_var_value(zend_execute_data *ed, const char *var_name,
85 } 90 }
86} 91}
87 92
88static void *get_entry_hashtable(const HashTable *ht, const char *entry, 93static void *get_entry_hashtable(const HashTable *const ht,
89 size_t entry_len) { 94 const char *const entry, size_t entry_len) {
90 zval *zvalue = zend_hash_str_find(ht, entry, entry_len); 95 zval *zvalue = zend_hash_str_find(ht, entry, entry_len);
91 96
92 if (!zvalue) { 97 if (!zvalue) {
@@ -104,8 +109,8 @@ static void *get_entry_hashtable(const HashTable *ht, const char *entry,
104 return zvalue; 109 return zvalue;
105} 110}
106 111
107static zval *get_array_value(zend_execute_data *ed, zval *zvalue, 112static zval *get_array_value(zend_execute_data *ed, const zval *const zvalue,
108 const sp_tree *tree) { 113 const sp_tree *const tree) {
109 zval *idx_value = sp_get_var_value(ed, tree->idx, false); 114 zval *idx_value = sp_get_var_value(ed, tree->idx, false);
110 115
111 if (!zvalue || !idx_value) { 116 if (!zvalue || !idx_value) {
@@ -113,7 +118,7 @@ static zval *get_array_value(zend_execute_data *ed, zval *zvalue,
113 } 118 }
114 119
115 if (Z_TYPE_P(zvalue) == IS_ARRAY) { 120 if (Z_TYPE_P(zvalue) == IS_ARRAY) {
116 const zend_string *idx = sp_zval_to_zend_string(idx_value); 121 const zend_string *const idx = sp_zval_to_zend_string(idx_value);
117 return get_entry_hashtable(Z_ARRVAL_P(zvalue), ZSTR_VAL(idx), 122 return get_entry_hashtable(Z_ARRVAL_P(zvalue), ZSTR_VAL(idx),
118 ZSTR_LEN(idx)); 123 ZSTR_LEN(idx));
119 } 124 }
@@ -123,10 +128,10 @@ static zval *get_array_value(zend_execute_data *ed, zval *zvalue,
123 128
124static zval *get_object_property(zend_execute_data *ed, zval *object, 129static zval *get_object_property(zend_execute_data *ed, zval *object,
125 const char *property, bool is_param) { 130 const char *property, bool is_param) {
126 char *class_name = object->value.obj->ce->name->val; 131 const char *const class_name = object->value.obj->ce->name->val;
127 HashTable *array = Z_OBJPROP_P(object); 132 HashTable *array = Z_OBJPROP_P(object);
128 zval *zvalue = NULL; 133 zval *zvalue = NULL;
129 zval *property_val = get_var_value(ed, property, is_param); 134 const zval *property_val = get_var_value(ed, property, is_param);
130 size_t len; 135 size_t len;
131 136
132 if (property_val) { 137 if (property_val) {
@@ -156,7 +161,7 @@ static zval *get_object_property(zend_execute_data *ed, zval *object,
156 return zvalue; 161 return zvalue;
157} 162}
158 163
159static zend_class_entry *get_class(const char *value) { 164static zend_class_entry *get_class(const char *const value) {
160 zend_string *name = zend_string_init(value, strlen(value), 0); 165 zend_string *name = zend_string_init(value, strlen(value), 0);
161 zend_class_entry *ce = zend_lookup_class(name); 166 zend_class_entry *ce = zend_lookup_class(name);
162 zend_string_release(name); 167 zend_string_release(name);
@@ -165,7 +170,7 @@ static zend_class_entry *get_class(const char *value) {
165 170
166static zval *get_unknown_type(const char *restrict value, zval *zvalue, 171static zval *get_unknown_type(const char *restrict value, zval *zvalue,
167 zend_class_entry *ce, zend_execute_data *ed, 172 zend_class_entry *ce, zend_execute_data *ed,
168 const sp_tree *tree, bool is_param) { 173 const sp_tree *const tree, bool is_param) {
169 if (ce) { 174 if (ce) {
170 zvalue = get_entry_hashtable(&ce->constants_table, value, strlen(value)); 175 zvalue = get_entry_hashtable(&ce->constants_table, value, strlen(value));
171 ce = NULL; 176 ce = NULL;
diff --git a/src/sp_wrapper.c b/src/sp_wrapper.c
index 277f23a..7610114 100644
--- a/src/sp_wrapper.c
+++ b/src/sp_wrapper.c
@@ -1,6 +1,6 @@
1#include "php_snuffleupagus.h" 1#include "php_snuffleupagus.h"
2 2
3static bool wrapper_is_whitelisted(const zend_string *zs) { 3static bool wrapper_is_whitelisted(const zend_string *const zs) {
4 const sp_list_node *list = SNUFFLEUPAGUS_G(config).config_wrapper->whitelist; 4 const sp_list_node *list = SNUFFLEUPAGUS_G(config).config_wrapper->whitelist;
5 5
6 if (!zs) { 6 if (!zs) {
@@ -18,7 +18,7 @@ static bool wrapper_is_whitelisted(const zend_string *zs) {
18 18
19void sp_disable_wrapper() { 19void sp_disable_wrapper() {
20 HashTable *orig = php_stream_get_url_stream_wrappers_hash(); 20 HashTable *orig = php_stream_get_url_stream_wrappers_hash();
21 HashTable *orig_complete = pemalloc(sizeof(*orig_complete), 1); 21 HashTable *orig_complete = pemalloc(sizeof(HashTable), 1);
22 zval *zv; 22 zval *zv;
23 zend_string *zs; 23 zend_string *zs;
24 24
diff --git a/src/tests/broken_configuration/broken_conf_config_invalid_param.phpt b/src/tests/broken_configuration/broken_conf_config_invalid_param.phpt
index ac85dea..45ccf24 100644
--- a/src/tests/broken_configuration/broken_conf_config_invalid_param.phpt
+++ b/src/tests/broken_configuration/broken_conf_config_invalid_param.phpt
@@ -13,4 +13,10 @@ function foo($blah, $x = null, $y = null) {
13foo("qwe"); 13foo("qwe");
14--EXPECTF-- 14--EXPECTF--
15Warning: [snuffleupagus][0.0.0.0][config][log] It seems that you are filtering on a parameter 'qwe' of the function 'foo', but the parameter does not exists. in %s/tests/broken_configuration/broken_conf_config_invalid_param.php on line %d 15Warning: [snuffleupagus][0.0.0.0][config][log] It seems that you are filtering on a parameter 'qwe' of the function 'foo', but the parameter does not exists. in %s/tests/broken_configuration/broken_conf_config_invalid_param.php on line %d
16
17Warning: [snuffleupagus][0.0.0.0][config][log] - 0 parameter's name: 'blah' in %s/tests/broken_configuration/broken_conf_config_invalid_param.php on line %d
18
19Warning: [snuffleupagus][0.0.0.0][config][log] - 1 parameter's name: 'x' in %s/tests/broken_configuration/broken_conf_config_invalid_param.php on line %d
20
21Warning: [snuffleupagus][0.0.0.0][config][log] - 2 parameter's name: 'y' in %s/tests/broken_configuration/broken_conf_config_invalid_param.php on line %d
16ok 22ok
diff --git a/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_encryption_key.phpt b/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_encryption_key.phpt
index 046dc7d..62ee41e 100644
--- a/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_encryption_key.phpt
+++ b/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_encryption_key.phpt
@@ -6,6 +6,7 @@ Broken configuration - encrypted session without encryption key
6--INI-- 6--INI--
7sp.configuration_file={PWD}/config/broken_conf_session_encryption_without_encryption_key.ini 7sp.configuration_file={PWD}/config/broken_conf_session_encryption_without_encryption_key.ini
8--FILE-- 8--FILE--
9--XFAIL--
9--EXPECT-- 10--EXPECT--
10 11
11Fatal error: [snuffleupagus][0.0.0.0][config][log] You're trying to use the session cookie encryption feature on line 2 without having set the `.secret_key` option in`sp.global`: please set it first in Unknown on line 0 12Fatal error: [snuffleupagus][0.0.0.0][config][log] You're trying to use the session cookie encryption feature on line 2 without having set the `.secret_key` option in`sp.global`: please set it first in Unknown on line 0
diff --git a/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_env_var.phpt b/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_env_var.phpt
index bb0f212..5acc1cd 100644
--- a/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_env_var.phpt
+++ b/src/tests/broken_configuration_php8/broken_conf_session_encryption_without_env_var.phpt
@@ -6,6 +6,7 @@ Broken configuration - encrypted session without env var
6--INI-- 6--INI--
7sp.configuration_file={PWD}/config/broken_conf_session_encryption_without_env_var.ini 7sp.configuration_file={PWD}/config/broken_conf_session_encryption_without_env_var.ini
8--FILE-- 8--FILE--
9--XFAIL--
9--EXPECT-- 10--EXPECT--
10 11
11Fatal error: [snuffleupagus][0.0.0.0][config][log] You're trying to use the session cookie encryption feature on line 2 without having set the `.cookie_env_var` option in`sp.global`: please set it first in Unknown on line 0 12Fatal error: [snuffleupagus][0.0.0.0][config][log] You're trying to use the session cookie encryption feature on line 2 without having set the `.cookie_env_var` option in`sp.global`: please set it first in Unknown on line 0
diff --git a/src/tests/deny_writable/deny_writable_execution_simulation.phpt b/src/tests/deny_writable/deny_writable_execution_simulation.phpt
index 30f8cb1..d4b8efc 100644
--- a/src/tests/deny_writable/deny_writable_execution_simulation.phpt
+++ b/src/tests/deny_writable/deny_writable_execution_simulation.phpt
@@ -3,7 +3,7 @@ Readonly execution attempt (simulation mode)
3--SKIPIF-- 3--SKIPIF--
4<?php if (PHP_VERSION_ID >= 80000) print "skip"; ?> 4<?php if (PHP_VERSION_ID >= 80000) print "skip"; ?>
5<?php 5<?php
6if (!extension_loaded("snuffleupagus")) { print "skip" }; 6if (!extension_loaded("snuffleupagus")) { print "skip"; };
7 7
8// root has write privileges on any file 8// root has write privileges on any file
9if (TRUE == function_exists("posix_getuid")) { 9if (TRUE == function_exists("posix_getuid")) {
diff --git a/src/tests/disable_function/config/disabled_functions.ini b/src/tests/disable_function/config/disabled_functions.ini
index df7013f..0758c98 100644
--- a/src/tests/disable_function/config/disabled_functions.ini
+++ b/src/tests/disable_function/config/disabled_functions.ini
@@ -7,3 +7,4 @@ sp.disable_function.function_r("^var_dump$").drop();
7sp.disable_function.function("sprintf").filename("/wrong file name").drop(); 7sp.disable_function.function("sprintf").filename("/wrong file name").drop();
8sp.disable_function.function("sprintf").filename("/wrong file name").drop(); 8sp.disable_function.function("sprintf").filename("/wrong file name").drop();
9sp.disable_function.function("eval").drop(); 9sp.disable_function.function("eval").drop();
10sp.disable_function.function("shell_exec").param("foo").value("bar").drop();
diff --git a/src/tests/disable_function/config/disabled_functions_chmod.ini b/src/tests/disable_function/config/disabled_functions_chmod.ini
new file mode 100644
index 0000000..e601900
--- /dev/null
+++ b/src/tests/disable_function/config/disabled_functions_chmod.ini
@@ -0,0 +1,4 @@
1# PHP7 and below
2sp.disable_function.function("chmod").param("mode").value("511").drop();
3# PHP8
4sp.disable_function.function("chmod").param("permissions").value("511").drop();
diff --git a/src/tests/disable_function/disabled_functions_chmod.phpt b/src/tests/disable_function/disabled_functions_chmod.phpt
new file mode 100644
index 0000000..28f948d
--- /dev/null
+++ b/src/tests/disable_function/disabled_functions_chmod.phpt
@@ -0,0 +1,14 @@
1--TEST--
2Disable functions - chmod
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5<?php if (PHP_VERSION_ID >= 80000) print "skip"; ?>
6--INI--
7sp.configuration_file={PWD}/config/disabled_functions_chmod.ini
8--FILE--
9<?php
10chmod( 'foo', 0777 );
11?>
12--XFAIL--
13--EXPECTF--
14Fatal error: [snuffleupagus][0.0.0.0][disabled_function][drop] Aborted execution on call of the function 'chmod', because its argument '$mode' content (511) matched a rule in %a/disabled_function_chmod.php on line %d
diff --git a/src/tests/disable_function/disabled_functions_chmod_php8.phpt b/src/tests/disable_function/disabled_functions_chmod_php8.phpt
new file mode 100644
index 0000000..71bb034
--- /dev/null
+++ b/src/tests/disable_function/disabled_functions_chmod_php8.phpt
@@ -0,0 +1,14 @@
1--TEST--
2Disable functions - chmod, in php8
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5<?php if (PHP_VERSION_ID < 80000) print "skip"; ?>
6--INI--
7sp.configuration_file={PWD}/config/disabled_functions_chmod.ini
8--FILE--
9<?php
10chmod( 'foo', 0777 );
11?>
12--XFAIL--
13--EXPECTF--
14Fatal error: [snuffleupagus][0.0.0.0][disabled_function][drop] Aborted execution on call of the function 'chmod', because its argument '$permissions' content (511) matched a rule in %a/disabled_function_chmod_php8.php on line %d
diff --git a/src/tests/disable_function/disabled_functions_shell_exec_wrong.phpt b/src/tests/disable_function/disabled_functions_shell_exec_wrong.phpt
new file mode 100644
index 0000000..fe8e73a
--- /dev/null
+++ b/src/tests/disable_function/disabled_functions_shell_exec_wrong.phpt
@@ -0,0 +1,14 @@
1--TEST--
2Disable functions - shell_exec, with a non-existing command
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/disabled_functions.ini
7--FILE--
8<?php
9$gs = exec( 'foo' );
10echo "YES";
11?>
12--EXPECTF--
13%snot found
14YES
diff --git a/src/tests/session_encryption/crypt_session_corrupted_session.phpt b/src/tests/session_encryption/crypt_session_corrupted_session.phpt
index a89faf4..a97dbca 100644
--- a/src/tests/session_encryption/crypt_session_corrupted_session.phpt
+++ b/src/tests/session_encryption/crypt_session_corrupted_session.phpt
@@ -2,6 +2,8 @@
2Set a custom session handler 2Set a custom session handler
3--SKIPIF-- 3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?> 4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5<?php if (PHP_VERSION_ID >= 80000) print "skip"; ?>
6<?php if (PHP_VERSION_ID >= 70400) print "skip"; ?>
5--INI-- 7--INI--
6sp.configuration_file={PWD}/config/config_crypt_session.ini 8sp.configuration_file={PWD}/config/config_crypt_session.ini
7session.save_path = "/tmp" 9session.save_path = "/tmp"
diff --git a/src/tests/session_encryption/crypt_session_invalid.phpt b/src/tests/session_encryption/crypt_session_invalid.phpt
index 9ec7c50..967d9d1 100644
--- a/src/tests/session_encryption/crypt_session_invalid.phpt
+++ b/src/tests/session_encryption/crypt_session_invalid.phpt
@@ -2,6 +2,8 @@
2SESSION crypt and bad decrypt 2SESSION crypt and bad decrypt
3--SKIPIF-- 3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?> 4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5<?php if (PHP_VERSION_ID >= 80000) print "skip"; ?>
6<?php if (PHP_VERSION_ID >= 70400) print "skip"; ?>
5--INI-- 7--INI--
6sp.configuration_file={PWD}/config/config_crypt_session.ini 8sp.configuration_file={PWD}/config/config_crypt_session.ini
7--ENV-- 9--ENV--
diff --git a/src/tests/xxe/disable_xxe_dom_disabled.phpt b/src/tests/xxe/disable_xxe_dom_disabled.phpt
index 493f5a3..a49e094 100644
--- a/src/tests/xxe/disable_xxe_dom_disabled.phpt
+++ b/src/tests/xxe/disable_xxe_dom_disabled.phpt
@@ -44,8 +44,13 @@ printf("without xxe: %s", $dom->getElementsByTagName('testing')->item(0)->nodeVa
44 44
45?> 45?>
46--EXPECTF-- 46--EXPECTF--
47Warning: [snuffleupagus][0.0.0.0][xxe][log] A call to libxml_disable_entity_loader was tried and nopped in %s/tests/xxe/disable_xxe_dom_disabled.php on line %d
47libxml_disable_entity to true: WARNING, external entity loaded! 48libxml_disable_entity to true: WARNING, external entity loaded!
49
50Warning: [snuffleupagus][0.0.0.0][xxe][log] A call to libxml_disable_entity_loader was tried and nopped in %s/tests/xxe/disable_xxe_dom_disabled.php on line %d
48libxml_disable_entity to false: WARNING, external entity loaded! 51libxml_disable_entity to false: WARNING, external entity loaded!
52
53Warning: [snuffleupagus][0.0.0.0][xxe][log] A call to libxml_disable_entity_loader was tried and nopped in %s/tests/xxe/disable_xxe_dom_disabled.php on line %d
49without xxe: foo 54without xxe: foo
50--CLEAN-- 55--CLEAN--
51<?php 56<?php