diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6b57831db9ff9..0c064cf935047 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,6 +46,7 @@ /ext/random @TimWolla @zeriyoshi /ext/session @Girgias /ext/simplexml @nielsdos +/ext/soap @nielsdos /ext/sockets @devnexen /ext/spl @Girgias /ext/standard @bukka diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml index 360c0a85c99f7..eeae02fde1c3f 100644 --- a/.github/actions/brew/action.yml +++ b/.github/actions/brew/action.yml @@ -19,6 +19,7 @@ runs: enchant \ libffi \ intltool \ + icu4c \ libiconv \ t1lib \ libxml2 \ diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml index 317d705645985..7312ff5d69f19 100644 --- a/.github/actions/configure-macos/action.yml +++ b/.github/actions/configure-macos/action.yml @@ -9,6 +9,7 @@ runs: - shell: bash run: | set -x + BREW_OPT="$(brew --prefix)"/opt export PATH="/usr/local/opt/bison/bin:$PATH" export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig" export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/curl/lib/pkgconfig" @@ -18,8 +19,10 @@ runs: export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxslt/lib/pkgconfig" export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/zlib/lib/pkgconfig" export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c/lib/pkgconfig" + sed -i -e 's/Requires.private:.*//g' "$BREW_OPT/curl/lib/pkgconfig/libcurl.pc" ./buildconf --force ./configure \ + CFLAGS="-Wno-strict-prototypes -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion" \ --enable-option-checking=fatal \ --prefix=/usr/local \ --enable-fpm \ diff --git a/.github/actions/configure-x64/action.yml b/.github/actions/configure-x64/action.yml index 3c3264ae07f5e..0610571b13210 100644 --- a/.github/actions/configure-x64/action.yml +++ b/.github/actions/configure-x64/action.yml @@ -3,6 +3,9 @@ inputs: configurationParameters: default: '' required: false + asan: + default: false + required: false runs: using: composite steps: diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml index 3329da8b641f2..350127da61e11 100644 --- a/.github/actions/test-linux/action.yml +++ b/.github/actions/test-linux/action.yml @@ -31,6 +31,10 @@ runs: export PDO_OCI_TEST_USER="system" export PDO_OCI_TEST_PASS="pass" export PDO_OCI_TEST_DSN="oci:dbname=localhost/XEPDB1;charset=AL32UTF8" + export PGSQL_TEST_CONNSTR="host=localhost dbname=test port=5432 user=postgres password=postgres" + if [[ -z "$PDO_PGSQL_TEST_DSN" ]]; then + export PDO_PGSQL_TEST_DSN="pgsql:host=localhost port=5432 dbname=test user=postgres password=postgres" + fi export SKIP_IO_CAPTURE_TESTS=1 sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ -d opcache.jit=${{ inputs.jitType }} \ diff --git a/.github/nightly_matrix.php b/.github/nightly_matrix.php index 23d391ffcff85..311176ef5e53e 100644 --- a/.github/nightly_matrix.php +++ b/.github/nightly_matrix.php @@ -1,23 +1,17 @@ 'master', 'version' => [8, 5]], + ['ref' => 'PHP-8.4', 'version' => [8, 4]], + ['ref' => 'PHP-8.3', 'version' => [8, 3]], + ['ref' => 'PHP-8.2', 'version' => [8, 2]], + ['ref' => 'PHP-8.1', 'version' => [8, 1]], +]; function get_branch_commit_cache_file_path(): string { return dirname(__DIR__) . '/branch-commit-cache.json'; } -function get_branch_matrix(array $branches) { - $result = array_map(function ($branch) { - $branch_key = strtoupper(str_replace('.', '', $branch)); - return [ - 'name' => $branch_key, - 'ref' => $branch, - ]; - }, $branches); - - return $result; -} - function get_branches() { $branch_commit_cache_file = get_branch_commit_cache_file_path(); $branch_commit_map = []; @@ -27,88 +21,45 @@ function get_branches() { $changed_branches = []; foreach (BRANCHES as $branch) { - $previous_commit_hash = $branch_commit_map[$branch] ?? null; - $current_commit_hash = trim(shell_exec('git rev-parse origin/' . $branch)); + $previous_commit_hash = $branch_commit_map[$branch['ref']] ?? null; + $current_commit_hash = trim(shell_exec('git rev-parse origin/' . $branch['ref'])); if ($previous_commit_hash !== $current_commit_hash) { $changed_branches[] = $branch; } - $branch_commit_map[$branch] = $current_commit_hash; + $branch_commit_map[$branch['ref']] = $current_commit_hash; } file_put_contents($branch_commit_cache_file, json_encode($branch_commit_map)); - return get_branch_matrix($changed_branches); + return $changed_branches; } -function get_matrix_include(array $branches) { - $jobs = []; - foreach ($branches as $branch) { - $jobs[] = [ - 'name' => '_ASAN_UBSAN', - 'branch' => $branch, - 'debug' => true, - 'zts' => true, - 'configuration_parameters' => "CFLAGS='-fsanitize=undefined,address -DZEND_TRACK_ARENA_ALLOC' LDFLAGS='-fsanitize=undefined,address'", - 'run_tests_parameters' => '--asan', - 'test_function_jit' => false, - ]; - if ($branch['ref'] !== 'PHP-8.0') { - $jobs[] = [ - 'name' => '_REPEAT', - 'branch' => $branch, - 'debug' => true, - 'zts' => false, - 'run_tests_parameters' => '--repeat 2', - 'timeout_minutes' => 360, - 'test_function_jit' => true, - ]; - $jobs[] = [ - 'name' => '_VARIATION', - 'branch' => $branch, - 'debug' => true, - 'zts' => true, - 'configuration_parameters' => "CFLAGS='-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1'", - 'run_tests_parameters' => '-d zend_test.observer.enabled=1 -d zend_test.observer.show_output=0', - 'timeout_minutes' => 360, - 'test_function_jit' => true, - ]; - } - } - return $jobs; -} - -function get_windows_matrix_include(array $branches) { - $jobs = []; - foreach ($branches as $branch) { - $jobs[] = [ - 'branch' => $branch, - 'x64' => true, - 'zts' => true, - 'opcache' => true, - ]; - $jobs[] = [ - 'branch' => $branch, - 'x64' => false, - 'zts' => false, - 'opcache' => false, - ]; - } - return $jobs; +function get_current_version(): array { + $file = dirname(__DIR__) . '/main/php_version.h'; + $content = file_get_contents($file); + preg_match('(^#define PHP_MAJOR_VERSION (?\d+)$)m', $content, $matches); + $major = (int) $matches['num']; + preg_match('(^#define PHP_MINOR_VERSION (?\d+)$)m', $content, $matches); + $minor = (int) $matches['num']; + return [$major, $minor]; } $trigger = $argv[1] ?? 'schedule'; $attempt = (int) ($argv[2] ?? 1); -$discard_cache = ($trigger === 'schedule' && $attempt !== 1) || $trigger === 'workflow_dispatch'; +$monday = date('w', time()) === '1'; +$discard_cache = $monday + || ($trigger === 'schedule' && $attempt !== 1) + || $trigger === 'workflow_dispatch'; if ($discard_cache) { @unlink(get_branch_commit_cache_file_path()); } +$branch = $argv[3] ?? 'master'; +$branches = $branch === 'master' + ? get_branches() + : [['ref' => $branch, 'version' => get_current_version()]]; -$branches = get_branches(); -$matrix_include = get_matrix_include($branches); -$windows_matrix_include = get_windows_matrix_include($branches); - -echo '::set-output name=branches::' . json_encode($branches, JSON_UNESCAPED_SLASHES) . "\n"; -echo '::set-output name=matrix-include::' . json_encode($matrix_include, JSON_UNESCAPED_SLASHES) . "\n"; -echo '::set-output name=windows-matrix-include::' . json_encode($windows_matrix_include, JSON_UNESCAPED_SLASHES) . "\n"; +$f = fopen(getenv('GITHUB_OUTPUT'), 'a'); +fwrite($f, 'branches=' . json_encode($branches, JSON_UNESCAPED_SLASHES) . "\n"); +fclose($f); diff --git a/.github/scripts/windows/test_task.bat b/.github/scripts/windows/test_task.bat index 7856eb0fb0fe2..dc02281bf7084 100644 --- a/.github/scripts/windows/test_task.bat +++ b/.github/scripts/windows/test_task.bat @@ -103,7 +103,7 @@ mkdir %~d0\usr\local\share\enchant\hunspell if %errorlevel% neq 0 exit /b 3 echo Fetching enchant dicts pushd %~d0\usr\local\share\enchant\hunspell -powershell -Command wget http://windows.php.net/downloads/qa/appveyor/ext/enchant/dict.zip -OutFile dict.zip +powershell -Command wget https://downloads.php.net/~windows/qa/appveyor/ext/enchant/dict.zip -OutFile dict.zip unzip dict.zip del /q dict.zip popd diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0ebbc2bb1cb61..b87b7389ef02d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,44 +1,90 @@ -name: Nightly +name: Test suite on: - schedule: - - cron: "0 1 * * *" - workflow_dispatch: ~ + workflow_call: + inputs: + asan_ubuntu_version: + required: true + type: string + branch: + required: true + type: string + community_verify_type_inference: + required: true + type: boolean + libmysqlclient_with_mysqli: + required: true + type: boolean + run_alpine: + required: true + type: boolean + run_macos_arm64: + required: true + type: boolean + ubuntu_version: + required: true + type: string + windows_version: + required: true + type: string +permissions: + contents: read jobs: - GENERATE_MATRIX: - name: Generate Matrix - if: github.repository_owner == 'php' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - branches: ${{ steps.set-matrix.outputs.branches }} - matrix-include: ${{ steps.set-matrix.outputs.matrix-include }} - windows-matrix-include: ${{ steps.set-matrix.outputs.windows-matrix-include }} + ALPINE: + if: inputs.run_alpine + name: ALPINE_X64_ASAN_UBSAN_DEBUG_ZTS + runs-on: ubuntu-22.04 + container: + image: 'alpine:3.20.1' steps: - - uses: actions/checkout@v4 - with: - # Set fetch-depth to 0 to clone the full repository - # including all branches. This is required to find - # the correct commit hashes. - fetch-depth: 0 - - name: Grab the commit mapping - uses: actions/cache@v3 - with: - path: branch-commit-cache.json - # The cache key needs to change every time for the - # cache to be updated after this job finishes. - key: nightly-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - nightly- - - name: Generate Matrix - id: set-matrix - run: php .github/nightly_matrix.php "${{ github.event_name }}" "${{ github.run_attempt }}" + - name: git checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + - name: apk + uses: ./.github/actions/apk + - name: LLVM 17 (ASAN-only) + # libclang_rt.asan-x86_64.a is provided by compiler-rt, and only for clang17: + # https://pkgs.alpinelinux.org/contents?file=libclang_rt.asan-x86_64.a&path=&name=&branch=v3.20 + run: | + apk add clang17 compiler-rt + - name: System info + run: | + echo "::group::Show host CPU info" + lscpu + echo "::endgroup::" + echo "::group::Show installed package versions" + apk list + echo "::endgroup::" + - name: ./configure + uses: ./.github/actions/configure-alpine + with: + configurationParameters: >- + CFLAGS="-fsanitize=undefined,address -fno-sanitize=function -DZEND_TRACK_ARENA_ALLOC" + LDFLAGS="-fsanitize=undefined,address -fno-sanitize=function" + CC=clang-17 + CXX=clang++-17 + --enable-debug + --enable-zts + skipSlow: true # FIXME: This should likely include slow extensions + - name: make + run: make -j$(/usr/bin/nproc) >/dev/null + - name: make install + uses: ./.github/actions/install-alpine + - name: Test Tracing JIT + uses: ./.github/actions/test-alpine + with: + jitType: tracing + runTestsParameters: >- + --asan -x + -d zend_extension=opcache.so + -d opcache.enable_cli=1 - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack with: token: ${{ secrets.ACTION_MONITORING_SLACK }} + LINUX_X64: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} services: mysql: image: mysql:8.3 @@ -53,30 +99,71 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test + firebird: + image: jacobalberty/firebird + ports: + - 3050:3050 + env: + ISC_PASSWORD: test + FIREBIRD_DATABASE: test.fdb + FIREBIRD_USER: test + FIREBIRD_PASSWORD: test strategy: fail-fast: false matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} configuration_parameters: [''] debug: [true, false] name: [''] run_tests_parameters: [''] test_function_jit: [true] zts: [true, false] - include: ${{ fromJson(needs.GENERATE_MATRIX.outputs.matrix-include) }} - name: "${{ matrix.branch.name }}_LINUX_X64${{ matrix.name }}_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" - runs-on: ubuntu-20.04 + include: + - name: _ASAN_UBSAN + debug: true + zts: true + configuration_parameters: >- + CFLAGS="-fsanitize=undefined,address -DZEND_TRACK_ARENA_ALLOC" + LDFLAGS="-fsanitize=undefined,address" + run_tests_parameters: '--asan' + test_function_jit: false + asan: true + - name: _REPEAT + debug: true + zts: false + run_tests_parameters: --repeat 2 + timeout_minutes: 360 + test_function_jit: true + asan: false + - name: _VARIATION + debug: true + zts: true + configuration_parameters: >- + CFLAGS="-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1 -DZEND_VERIFY_TYPE_INFERENCE" + run_tests_parameters: -d zend_test.observer.enabled=1 -d zend_test.observer.show_output=0 + timeout_minutes: 360 + test_function_jit: true + asan: false + name: "LINUX_X64${{ matrix.name }}_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + runs-on: ubuntu-${{ matrix.asan && inputs.asan_ubuntu_version || inputs.ubuntu_version }} steps: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: Create MSSQL container uses: ./.github/actions/setup-mssql - - name: Create Oracle container - uses: ./.github/actions/setup-oracle - name: apt uses: ./.github/actions/apt-x64 + with: + asan: ${{ matrix.asan && 'true' || 'false' }} + - name: System info + run: | + echo "::group::Show host CPU info" + lscpu + echo "::endgroup::" + echo "::group::Show installed package versions" + dpkg -l + echo "::endgroup::" - name: ./configure uses: ./.github/actions/configure-x64 with: @@ -84,6 +171,7 @@ jobs: ${{ matrix.configuration_parameters }} --${{ matrix.debug && 'enable' || 'disable' }}-debug --${{ matrix.zts && 'enable' || 'disable' }}-zts + asan: ${{ matrix.asan && 'true' || 'false' }} - name: make run: make -j$(/usr/bin/nproc) >/dev/null - name: make install @@ -130,25 +218,18 @@ jobs: with: token: ${{ secrets.ACTION_MONITORING_SLACK }} LINUX_X32: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} strategy: fail-fast: false matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} debug: [true, false] zts: [true, false] - name: "${{ matrix.branch.name }}_LINUX_X32_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + name: "LINUX_X32_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" runs-on: ubuntu-latest container: - image: ubuntu:20.04 - env: - MYSQL_TEST_HOST: mysql - PDO_MYSQL_TEST_DSN: mysql:host=mysql;dbname=test - PDO_MYSQL_TEST_HOST: mysql + image: ubuntu:${{ inputs.ubuntu_version }} services: mysql: - image: mysql:8 + image: mysql:8.3 ports: - 3306:3306 env: @@ -158,9 +239,17 @@ jobs: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: apt uses: ./.github/actions/apt-x32 + - name: System info + run: | + echo "::group::Show host CPU info" + lscpu + echo "::endgroup::" + echo "::group::Show installed package versions" + dpkg -l + echo "::endgroup::" - name: ./configure uses: ./.github/actions/configure-x32 with: @@ -205,21 +294,21 @@ jobs: with: token: ${{ secrets.ACTION_MONITORING_SLACK }} MACOS: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} strategy: fail-fast: false matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} debug: [true, false] zts: [true, false] - name: "${{ matrix.branch.name }}_MACOS_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" - runs-on: macos-12 + os: ['13', '14'] + exclude: + - os: ${{ !inputs.run_macos_arm64 && '14' || '*never*' }} + name: "MACOS_${{ matrix.os == '13' && 'X64' || 'ARM64' }}_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + runs-on: macos-${{ matrix.os }} steps: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: brew uses: ./.github/actions/brew - name: ./configure @@ -230,35 +319,34 @@ jobs: --${{ matrix.zts && 'enable' || 'disable' }}-zts - name: make run: |- - export PATH="/usr/local/opt/bison/bin:$PATH" + export PATH="$(brew --prefix)/opt/bison/bin:$PATH" make -j$(sysctl -n hw.logicalcpu) >/dev/null - name: make install run: sudo make install - name: Test uses: ./.github/actions/test-macos - name: Test Tracing JIT + if: matrix.os != '14' || !matrix.zts uses: ./.github/actions/test-macos with: jitType: tracing runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 - -d opcache.protect_memory=1 - name: Test OpCache uses: ./.github/actions/test-macos with: runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 - -d opcache.protect_memory=1 - name: Test Function JIT + if: matrix.os != '14' || !matrix.zts uses: ./.github/actions/test-macos with: jitType: function runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 - -d opcache.protect_memory=1 - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -267,8 +355,7 @@ jobs: with: token: ${{ secrets.ACTION_MONITORING_SLACK }} COVERAGE_DEBUG_NTS: - if: github.repository_owner == 'php' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-20.04 + if: inputs.branch == 'master' services: mysql: image: mysql:8.3 @@ -283,13 +370,23 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test + firebird: + image: jacobalberty/firebird + ports: + - 3050:3050 + env: + ISC_PASSWORD: test + FIREBIRD_DATABASE: test.fdb + FIREBIRD_USER: test + FIREBIRD_PASSWORD: test + runs-on: ubuntu-22.04 steps: - name: git checkout uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} - name: Create MSSQL container uses: ./.github/actions/setup-mssql - - name: Create Oracle container - uses: ./.github/actions/setup-oracle - name: apt uses: ./.github/actions/apt-x64 - name: Install gcovr @@ -308,27 +405,32 @@ jobs: - name: Test OpCache uses: ./.github/actions/test-linux with: + jitType: tracing runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 - - name: Upload Test Coverage to Codecov.io + - uses: codecov/codecov-action@v4 if: always() - run: bash <(curl -s https://codecov.io/bash) + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack with: token: ${{ secrets.ACTION_MONITORING_SLACK }} COMMUNITY: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} strategy: fail-fast: false matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} - name: "${{ matrix.branch.name }}_COMMUNITY" - runs-on: ubuntu-20.04 + type: ['asan', 'verify_type_inference'] + exclude: + - type: ${{ !inputs.community_verify_type_inference && 'verify_type_inference' || '*never*' }} + name: "COMMUNITY_${{ matrix.type }}" + runs-on: ubuntu-${{ inputs.ubuntu_version }} env: + ASAN_OPTIONS: exitcode=139 UBSAN_OPTIONS: print_stacktrace=1 USE_ZEND_ALLOC: 0 USE_TRACKED_ALLOC: 1 @@ -336,17 +438,17 @@ jobs: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: apt uses: ./.github/actions/apt-x64 - name: ./configure uses: ./.github/actions/configure-x64 with: + # CFLAGS removes O2, so we have to add it again... configurationParameters: >- - --enable-debug --enable-zts - CFLAGS='-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC' - LDFLAGS='-fsanitize=undefined,address' + ${{ matrix.type == 'asan' && '--enable-debug CFLAGS="-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC" LDFLAGS="-fsanitize=undefined,address"' || '' }} + ${{ matrix.type == 'verify_type_inference' && 'CFLAGS="-DZEND_VERIFY_TYPE_INFERENCE -O2"' || '' }} - name: make run: make -j$(/usr/bin/nproc) >/dev/null - name: make install @@ -356,14 +458,50 @@ jobs: sudo service mysql start mysql -uroot -proot -e "CREATE DATABASE IF NOT EXISTS test" mysql -uroot -proot -e "SET GLOBAL local_infile = true" - - name: Enable Opcache and JIT + - name: Enable Opcache run: | + echo memory_limit=-1 >> /etc/php.d/opcache.ini echo zend_extension=opcache.so > /etc/php.d/opcache.ini echo opcache.enable_cli=1 >> /etc/php.d/opcache.ini + echo opcache.enable=1 >> /etc/php.d/opcache.ini echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini + echo opcache.memory_consumption=256M >> /etc/php.d/opcache.ini + echo opcache.file_update_protection=0 >> /etc/php.d/opcache.ini + echo opcache.interned_strings_buffer=64 >> /etc/php.d/opcache.ini + echo opcache.max_accelerated_files=100000 >> /etc/php.d/opcache.ini + - name: Enable JIT + if: matrix.type != 'verify_type_inference' + run: | + echo opcache.jit=tracing >> /etc/php.d/opcache.ini echo opcache.jit_buffer_size=1G >> /etc/php.d/opcache.ini + echo opcache.jit_max_root_traces=100000 >> /etc/php.d/opcache.ini + echo opcache.jit_max_side_traces=100000 >> /etc/php.d/opcache.ini + echo opcache.jit_max_exit_counters=100000 >> /etc/php.d/opcache.ini + echo opcache.jit_hot_loop=1 >> /etc/php.d/opcache.ini + echo opcache.jit_hot_func=1 >> /etc/php.d/opcache.ini + echo opcache.jit_hot_return=1 >> /etc/php.d/opcache.ini + echo opcache.jit_hot_side_exit=1 >> /etc/php.d/opcache.ini + php -v + - name: Test AMPHP + if: always() + run: | + repositories="amp cache dns file http parallel parser pipeline process serialization socket sync websocket-client websocket-server" + X=0 + for repository in $repositories; do + printf "Testing amp/%s\n" "$repository" + git clone "https://github.com/amphp/$repository.git" "amphp-$repository" --depth 1 + cd "amphp-$repository" + git rev-parse HEAD + php /usr/bin/composer install --no-progress --ignore-platform-reqs + vendor/bin/phpunit || EXIT_CODE=$? + if [ ${EXIT_CODE:-0} -gt 128 ]; then + X=1; + fi + cd .. + done + exit $X - name: Test Laravel - if: matrix.branch.ref != 'PHP-8.0' + if: always() run: | git clone https://github.com/laravel/framework.git --branch=master --depth=1 cd framework @@ -371,13 +509,41 @@ jobs: php /usr/bin/composer install --no-progress --ignore-platform-reqs # Hack to disable a test that hangs php -r '$c = file_get_contents("tests/Filesystem/FilesystemTest.php"); $c = str_replace("public function testSharedGet()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testSharedGet()", $c); file_put_contents("tests/Filesystem/FilesystemTest.php", $c);' - export ASAN_OPTIONS=exitcode=139 php vendor/bin/phpunit --exclude-group skip || EXIT_CODE=$? - if [ $EXIT_CODE -gt 128 ]; then + if [ ${EXIT_CODE:-0} -gt 128 ]; then + exit 1 + fi + - name: Test ReactPHP + if: always() + run: | + repositories="async cache child-process datagram dns event-loop promise promise-stream promise-timer stream" + X=0 + for repository in $repositories; do + printf "Testing reactphp/%s\n" "$repository" + git clone "https://github.com/reactphp/$repository.git" "reactphp-$repository" --depth 1 + cd "reactphp-$repository" + git rev-parse HEAD + php /usr/bin/composer install --no-progress --ignore-platform-reqs + vendor/bin/phpunit || EXIT_CODE=$? + if [ $[EXIT_CODE:-0} -gt 128 ]; then + X=1; + fi + cd .. + done + exit $X + - name: Test Revolt PHP + if: always() + run: | + git clone https://github.com/revoltphp/event-loop.git --depth=1 + cd event-loop + git rev-parse HEAD + php /usr/bin/composer install --no-progress --ignore-platform-reqs + vendor/bin/phpunit || EXIT_CODE=$? + if [ ${EXIT_CODE:-0} -gt 128 ]; then exit 1 fi - name: Test Symfony - if: matrix.branch.ref != 'PHP-8.0' + if: always() run: | git clone https://github.com/symfony/symfony.git --depth=1 cd symfony @@ -388,12 +554,11 @@ jobs: php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "/** @group skip */\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' # Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668 php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "/** @group skip */\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' - export ASAN_OPTIONS=exitcode=139 export SYMFONY_DEPRECATIONS_HELPER=max[total]=999 X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip || EXIT_CODE=$? - if [ $EXIT_CODE -gt 128 ]; then + if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; fi done @@ -404,14 +569,13 @@ jobs: git clone https://github.com/sebastianbergmann/phpunit.git --branch=main --depth=1 cd phpunit git rev-parse HEAD - export ASAN_OPTIONS=exitcode=139 php /usr/bin/composer install --no-progress --ignore-platform-reqs php ./phpunit || EXIT_CODE=$? - if [ $EXIT_CODE -gt 128 ]; then + if [ ${EXIT_CODE:-0} -gt 128 ]; then exit 1 fi - name: 'Symfony Preloading' - if: matrix.branch.ref != 'PHP-8.0' + if: always() run: | php /usr/bin/composer create-project symfony/symfony-demo symfony_demo --no-progress --ignore-platform-reqs cd symfony_demo @@ -424,7 +588,6 @@ jobs: git clone https://github.com/WordPress/wordpress-develop.git wordpress --depth=1 cd wordpress git rev-parse HEAD - export ASAN_OPTIONS=exitcode=139 php /usr/bin/composer install --no-progress --ignore-platform-reqs cp wp-tests-config-sample.php wp-tests-config.php sed -i 's/youremptytestdbnamehere/test/g' wp-tests-config.php @@ -440,8 +603,6 @@ jobs: with: token: ${{ secrets.ACTION_MONITORING_SLACK }} OPCACHE_VARIATION: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} services: mysql: image: mysql:8.3 @@ -456,21 +617,24 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test - strategy: - fail-fast: false - matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} - name: "${{ matrix.branch.name }}_OPCACHE_VARIATION" - runs-on: ubuntu-20.04 + firebird: + image: jacobalberty/firebird + ports: + - 3050:3050 + env: + ISC_PASSWORD: test + FIREBIRD_DATABASE: test.fdb + FIREBIRD_USER: test + FIREBIRD_PASSWORD: test + name: OPCACHE_VARIATION + runs-on: ubuntu-${{ inputs.ubuntu_version }} steps: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: Create MSSQL container uses: ./.github/actions/setup-mssql - - name: Create Oracle container - uses: ./.github/actions/setup-oracle - name: apt uses: ./.github/actions/apt-x64 - name: ./configure @@ -530,19 +694,13 @@ jobs: with: token: ${{ secrets.ACTION_MONITORING_SLACK }} MSAN: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} - strategy: - fail-fast: false - matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} - name: "${{ matrix.branch.name }}_MSAN" - runs-on: ubuntu-22.04 + name: MSAN + runs-on: ubuntu-${{ inputs.ubuntu_version }} steps: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: apt uses: ./.github/actions/apt-x64 - name: ./configure @@ -589,7 +747,8 @@ jobs: --enable-werror \ --enable-memory-sanitizer \ --with-config-file-path=/etc \ - --with-config-file-scan-dir=/etc/php.d + --with-config-file-scan-dir=/etc/php.d \ + --enable-dl-test=shared - name: make run: make -j$(/usr/bin/nproc) >/dev/null - name: make install @@ -627,21 +786,13 @@ jobs: with: token: ${{ secrets.ACTION_MONITORING_SLACK }} LIBMYSQLCLIENT: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} - strategy: - fail-fast: false - matrix: - branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} - exclude: - - branch: { name: 'PHP-80', ref: 'PHP-8.0' } - name: "${{ matrix.branch.name }}_LIBMYSQLCLIENT" - runs-on: ubuntu-20.04 + name: LIBMYSQLCLIENT + runs-on: ubuntu-${{ inputs.ubuntu_version }} steps: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: apt run: | sudo apt-get update -y | true @@ -652,33 +803,26 @@ jobs: mysql -uroot -proot -e "CREATE DATABASE IF NOT EXISTS test" # Ensure local_infile tests can run. mysql -uroot -proot -e "SET GLOBAL local_infile = true" - # Does not support caching_sha2_auth :( - # - name: Build mysql-5.6 - # uses: ./.github/actions/build-libmysqlclient - # with: - # libmysql: mysql-5.6.49-linux-glibc2.12-x86_64.tar.gz - # - name: Test mysql-5.6 - # uses: ./.github/actions/test-libmysqlclient - - name: Build mysql-5.7 + - name: Build mysql-8.0 uses: ./.github/actions/build-libmysqlclient with: - libmysql: mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz - withMysqli: ${{ matrix.branch.ref == 'PHP-8.1' }} - - name: Test mysql-5.7 + configurationParameters: ${{ !inputs.libmysqlclient_with_mysqli && '--enable-werror' || '' }} + libmysql: mysql-8.0.37-linux-glibc2.28-x86_64.tar.xz + withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} + - name: Test mysql-8.0 uses: ./.github/actions/test-libmysqlclient with: - withMysqli: ${{ matrix.branch.ref == 'PHP-8.1' }} - - name: Build mysql-8.0 + withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} + - name: Build mysql-8.4 uses: ./.github/actions/build-libmysqlclient with: - # FIXME: There are new warnings - # configurationParameters: --enable-werror - libmysql: mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz - withMysqli: ${{ matrix.branch.ref == 'PHP-8.1' }} - - name: Test mysql-8.0 + configurationParameters: ${{ !inputs.libmysqlclient_with_mysqli && '--enable-werror' || '' }} + libmysql: mysql-8.4.0-linux-glibc2.28-x86_64.tar.xz + withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} + - name: Test mysql-8.4 uses: ./.github/actions/test-libmysqlclient with: - withMysqli: ${{ matrix.branch.ref == 'PHP-8.1' }} + withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -686,21 +830,137 @@ jobs: uses: ./.github/actions/notify-slack with: token: ${{ secrets.ACTION_MONITORING_SLACK }} + PECL: + if: inputs.branch == 'master' + runs-on: ubuntu-22.04 + env: + CC: ccache gcc + CXX: ccache g++ + steps: + - name: git checkout PHP + uses: actions/checkout@v4 + with: + path: php + ref: ${{ inputs.branch }} + - name: git checkout apcu + uses: actions/checkout@v4 + with: + repository: krakjoe/apcu + path: apcu + - name: git checkout imagick + uses: actions/checkout@v4 + with: + repository: Imagick/imagick + path: imagick + - name: git checkout memcached + uses: actions/checkout@v4 + with: + repository: php-memcached-dev/php-memcached + path: memcached + - name: git checkout redis + uses: actions/checkout@v4 + with: + repository: phpredis/phpredis + path: redis + - name: git checkout xdebug + if: false + uses: actions/checkout@v4 + with: + repository: xdebug/xdebug + path: xdebug + - name: git checkout yaml + uses: actions/checkout@v4 + with: + repository: php/pecl-file_formats-yaml + path: yaml + - name: apt + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + ccache \ + libmemcached-dev \ + bison \ + re2c + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: "${{github.job}}-${{hashFiles('php/main/php_version.h')}}" + append-timestamp: false + save: ${{ github.event_name != 'pull_request' }} + - name: build PHP + run: | + cd php + ./buildconf --force + ./configure \ + --enable-option-checking=fatal \ + --prefix=/opt/php \ + --enable-cli \ + --disable-all \ + --enable-session \ + --enable-werror + make -j$(/usr/bin/nproc) + sudo make install + - name: build apcu + run: | + cd apcu + /opt/php/bin/phpize + ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config + make -j$(/usr/bin/nproc) + - name: build imagick + run: | + cd imagick + /opt/php/bin/phpize + ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config + make -j$(/usr/bin/nproc) + - name: build memcached + run: | + cd memcached + /opt/php/bin/phpize + ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config + make -j$(/usr/bin/nproc) + - name: build redis + run: | + cd redis + /opt/php/bin/phpize + ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config + make -j$(/usr/bin/nproc) + - name: build xdebug + if: false + run: | + cd xdebug + /opt/php/bin/phpize + ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config + make -j$(/usr/bin/nproc) + - name: build yaml + run: | + cd yaml + /opt/php/bin/phpize + ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config + make -j$(/usr/bin/nproc) + - name: Notify Slack + if: failure() + uses: ./.github/actions/notify-slack + with: + token: ${{ secrets.ACTION_MONITORING_SLACK }} WINDOWS: - needs: GENERATE_MATRIX - if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} strategy: fail-fast: false matrix: - include: ${{ fromJson(needs.GENERATE_MATRIX.outputs.windows-matrix-include) }} - name: "${{ matrix.branch.name }}_WINDOWS_${{ matrix.x64 && 'X64' || 'X86' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" - runs-on: windows-2019 + include: + - x64: true + zts: true + opcache: true + - x64: false + zts: false + opcache: false + name: "WINDOWS_${{ matrix.x64 && 'X64' || 'X86' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + runs-on: windows-${{ inputs.windows_version }} env: PHP_BUILD_CACHE_BASE_DIR: C:\build-cache PHP_BUILD_OBJ_DIR: C:\obj PHP_BUILD_CACHE_SDK_DIR: C:\build-cache\sdk PHP_BUILD_SDK_BRANCH: php-sdk-2.3.0 - PHP_BUILD_CRT: vs16 + PHP_BUILD_CRT: ${{ inputs.windows_version == '2022' && 'vs17' || 'vs16' }} PLATFORM: ${{ matrix.x64 && 'x64' || 'x86' }} THREAD_SAFE: "${{ matrix.zts && '1' || '0' }}" INTRINSICS: "${{ matrix.zts && 'AVX2' || '' }}" @@ -712,7 +972,7 @@ jobs: - name: git checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch.ref }} + ref: ${{ inputs.branch }} - name: Setup uses: ./.github/actions/setup-windows - name: Build diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 8baa2a1108e16..3840aefa2a5de 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -34,6 +34,7 @@ on: - .circleci/** branches: - '**' + workflow_dispatch: ~ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.url || github.run_id }} cancel-in-progress: true @@ -158,7 +159,7 @@ jobs: -d opcache.enable_cli=1 MACOS_DEBUG_NTS: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' - runs-on: macos-12 + runs-on: macos-13 steps: - name: git checkout uses: actions/checkout@v4 diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml new file mode 100644 index 0000000000000..f526e9bea30d5 --- /dev/null +++ b/.github/workflows/root.yml @@ -0,0 +1,57 @@ +name: Nightly +on: + schedule: + - cron: "0 1 * * *" + workflow_dispatch: ~ +permissions: + contents: read +jobs: + GENERATE_MATRIX: + name: Generate Matrix + if: github.repository == 'php/php-src' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + outputs: + branches: ${{ steps.set-matrix.outputs.branches }} + steps: + - uses: actions/checkout@v4 + with: + # Set fetch-depth to 0 to clone the full repository + # including all branches. This is required to find + # the correct commit hashes. + fetch-depth: 0 + - name: Grab the commit mapping + uses: actions/cache@v4 + with: + path: branch-commit-cache.json + # The cache key needs to change every time for the + # cache to be updated after this job finishes. + key: nightly-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + nightly- + - name: Generate Matrix + id: set-matrix + run: php .github/nightly_matrix.php "${{ github.event_name }}" "${{ github.run_attempt }}" "${{ github.head_ref || github.ref_name }}" + - name: Notify Slack + if: failure() + uses: ./.github/actions/notify-slack + with: + token: ${{ secrets.ACTION_MONITORING_SLACK }} + NIGHTLY: + needs: GENERATE_MATRIX + name: ${{ matrix.branch.ref }} + if: ${{ needs.GENERATE_MATRIX.outputs.branches != '[]' }} + uses: ./.github/workflows/nightly.yml + strategy: + fail-fast: false + matrix: + branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} + with: + asan_ubuntu_version: '20.04' + branch: ${{ matrix.branch.ref }} + community_verify_type_inference: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }} + libmysqlclient_with_mysqli: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] == 1) }} + run_alpine: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }} + run_macos_arm64: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }} + ubuntu_version: ${{ ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 3) || matrix.branch.version[0] >= 9) && '22.04' || '20.04' }} + windows_version: ${{ ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9) && '2022' || '2019' }} + secrets: inherit diff --git a/EXTENSIONS b/EXTENSIONS index 1b745557bf5c8..8daafda2ae2b5 100644 --- a/EXTENSIONS +++ b/EXTENSIONS @@ -193,7 +193,8 @@ SINCE: 5.0 ------------------------------------------------------------------------------- EXTENSION: soap PRIMARY MAINTAINER: Dmitry Stogov (2004 - 2018) -MAINTENANCE: Maintained + Niels Dossche (2024 - 2024) +MAINTENANCE: Odd fixes STATUS: Working ------------------------------------------------------------------------------- EXTENSION: xml diff --git a/NEWS b/NEWS index 5d3c1e46ce304..6e03791492b11 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,179 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.25 +21 Nov 2024, PHP 8.2.26 + +- CLI: + . Fixed bug GH-16373 (Shebang is not skipped for router script in cli-server + started through shebang). (ilutov) + . Fixed bug GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data + Processing in CLI SAPI Interface). (nielsdos) + +- COM: + . Fixed out of bound writes to SafeArray data. (cmb) + +- Core: + . Fixed bug GH-16168 (php 8.1 and earlier crash immediately when compiled + with Xcode 16 clang on macOS 15). (nielsdos) + . Fixed bug GH-16371 (Assertion failure in Zend/zend_weakrefs.c:646). (Arnaud) + . Fixed bug GH-16515 (Incorrect propagation of ZEND_ACC_RETURN_REFERENCE for + call trampoline). (ilutov) + . Fixed bug GH-16509 (Incorrect line number in function redeclaration error). + (ilutov) + . Fixed bug GH-16508 (Incorrect line number in inheritance errors of delayed + early bound classes). (ilutov) + . Fixed bug GH-16648 (Use-after-free during array sorting). (ilutov) + +- Curl: + . Fixed bug GH-16302 (CurlMultiHandle holds a reference to CurlHandle if + curl_multi_add_handle fails). (timwolla) + +- Date: + . Fixed bug GH-16454 (Unhandled INF in date_sunset() with tiny $utcOffset). + (cmb) + . Fixed bug GH-16037 (Assertion failure in ext/date/php_date.c). (Derick) + . Fixed bug GH-14732 (date_sun_info() fails for non-finite values). (cmb) + +- DBA: + . Fixed bug GH-16390 (dba_open() can segfault for "pathless" streams). (cmb) + +- DOM: + . Fixed bug GH-16316 (DOMXPath breaks when not initialized properly). + (nielsdos) + . Fixed bug GH-16473 (dom_import_simplexml stub is wrong). (nielsdos) + . Fixed bug GH-16533 (Segfault when adding attribute to parent that is not + an element). (nielsdos) + . Fixed bug GH-16535 (UAF when using document as a child). (nielsdos) + . Fixed bug GH-16593 (Assertion failure in DOM->replaceChild). (nielsdos) + . Fixed bug GH-16595 (Another UAF in DOM -> cloneNode). (nielsdos) + +- EXIF: + . Fixed bug GH-16409 (Segfault in exif_thumbnail when not dealing with a + real file). (nielsdos, cmb) + +- FFI: + . Fixed bug GH-16397 (Segmentation fault when comparing FFI object). + (nielsdos) + +- Filter: + . Fixed bug GH-16523 (FILTER_FLAG_HOSTNAME accepts ending hyphen). (cmb) + +- FPM: + . Fixed bug GH-16628 (FPM logs are getting corrupted with this log + statement). (nielsdos) + +- GD: + . Fixed bug GH-16334 (imageaffine overflow on matrix elements). + (David Carlier) + . Fixed bug GH-16427 (Unchecked libavif return values). (cmb) + . Fixed bug GH-16559 (UBSan abort in ext/gd/libgd/gd_interpolation.c:1007). + (nielsdos) + +- GMP: + . Fixed floating point exception bug with gmp_pow when using + large exposant values. (David Carlier). + . Fixed bug GH-16411 (gmp_export() can cause overflow). (cmb) + . Fixed bug GH-16501 (gmp_random_bits() can cause overflow). + (David Carlier) + . Fixed gmp_pow() overflow bug with large base/exponents. + (David Carlier) + . Fixed segfaults and other issues related to operator overloading with + GMP objects. (Girgias) + +- LDAP: + . Fixed bug GHSA-g665-fm4p-vhff (OOB access in ldap_escape). (CVE-2024-8932) + (nielsdos) + +- MBstring: + . Fixed bug GH-16361 (mb_substr overflow on start/length arguments). + (David Carlier) + +- MySQLnd: + . Fixed bug GHSA-h35g-vwh6-m678 (Leak partial content of the heap through + heap buffer over-read). (CVE-2024-8929) (Jakub Zelenka) + +- OpenSSL: + . Fixed bug GH-16357 (openssl may modify member types of certificate arrays). + (cmb) + . Fixed bug GH-16433 (Large values for openssl_csr_sign() $days overflow). + (cmb) + . Fix various memory leaks on error conditions in openssl_x509_parse(). + (nielsdos) + +- PDO DBLIB: + . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing + OOB writes). (CVE-2024-11236) (nielsdos) + +- PDO Firebird: + . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the firebird quoter + causing OOB writes). (CVE-2024-11236) (nielsdos) + +- PDO ODBC: + . Fixed bug GH-16450 (PDO_ODBC can inject garbage into field values). (cmb) + +- Phar: + . Fixed bug GH-16406 (Assertion failure in ext/phar/phar.c:2808). (nielsdos) + +- PHPDBG: + . Fixed bug GH-16174 (Empty string is an invalid expression for ev). (cmb) + +- Reflection: + . Fixed bug GH-16601 (Memory leak in Reflection constructors). (nielsdos) + +- Session: + . Fixed bug GH-16385 (Unexpected null returned by session_set_cookie_params). + (nielsdos) + . Fixed bug GH-16290 (overflow on cookie_lifetime ini value). + (David Carlier) + +- SOAP: + . Fixed bug GH-16429 (Segmentation fault access null pointer in SoapClient). + (nielsdos) + +- Sockets: + . Fixed bug with overflow socket_recvfrom $length argument. (David Carlier) + +- SPL: + . Fixed bug GH-16337 (Use-after-free in SplHeap). (nielsdos) + . Fixed bug GH-16464 (Use-after-free in SplDoublyLinkedList::offsetSet()). + (ilutov) + . Fixed bug GH-16479 (Use-after-free in SplObjectStorage::setInfo()). (ilutov) + . Fixed bug GH-16478 (Use-after-free in SplFixedArray::unset()). (ilutov) + . Fixed bug GH-16588 (UAF in Observer->serialize). (nielsdos) + . Fix GH-16477 (Segmentation fault when calling __debugInfo() after failed + SplFileObject::__constructor). (Girgias) + . Fixed bug GH-16589 (UAF in SplDoublyLinked->serialize()). (nielsdos) + . Fixed bug GH-14687 (segfault on SplObjectIterator instance). + (David Carlier) + . Fixed bug GH-16604 (Memory leaks in SPL constructors). (nielsdos) + . Fixed bug GH-16646 (UAF in ArrayObject::unset() and + ArrayObject::exchangeArray()). (ilutov) + +- Standard: + . Fixed bug GH-16293 (Failed assertion when throwing in assert() callback with + bail enabled). (ilutov) + +- Streams: + . Fixed bug GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context + might allow for CRLF injection in URIs). (CVE-2024-11234) (Jakub Zelenka) + . Fixed bug GHSA-r977-prxv-hc43 (Single byte overread with + convert.quoted-printable-decode filter). (CVE-2024-11233) (nielsdos) + +- SysVMsg: + . Fixed bug GH-16592 (msg_send() crashes when a type does not properly + serialized). (David Carlier / cmb) + +- SysVShm: + . Fixed bug GH-16591 (Assertion error in shm_put_var). (nielsdos, cmb) + +- XMLReader: + . Fixed bug GH-16292 (Segmentation fault in ext/xmlreader/php_xmlreader.c). + (nielsdos) + +- Zlib: + . Fixed bug GH-16326 (Memory management is broken for bad dictionaries.) + (cmb) + +24 Oct 2024, PHP 8.2.25 - Calendar: . Fixed GH-16240: jdtounix overflow on argument value. (David Carlier) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 322c10e6eebf6..39e04cd941f58 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -3895,6 +3895,9 @@ static zend_always_inline zend_result _zend_update_type_info( } else { zend_arg_info *ret_info = op_array->arg_info - 1; tmp = zend_fetch_arg_info_type(script, ret_info, &ce); + if ((tmp & MAY_BE_NULL) && opline->op1_type == IS_CV) { + tmp |= MAY_BE_UNDEF; + } tmp |= (t1 & MAY_BE_INDIRECT); // TODO: We could model more precisely how illegal types are converted. diff --git a/Zend/tests/gh16293_001.phpt b/Zend/tests/gh16293_001.phpt new file mode 100644 index 0000000000000..a43c9adc45e65 --- /dev/null +++ b/Zend/tests/gh16293_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-16293: Exception in assert() callback with bail enabled +--FILE-- + +--EXPECTF-- +Warning: assert(): assert(false) failed in %s on line %d + +Warning: Uncaught Error: Invalid callback f1, function "f1" not found or invalid function name in %s:%d +Stack trace: +#0 %s(%d): assert(false, 'assert(false)') +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh16293_002.phpt b/Zend/tests/gh16293_002.phpt new file mode 100644 index 0000000000000..a0cd78eedbb51 --- /dev/null +++ b/Zend/tests/gh16293_002.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-16293: Exception in assert() callback with bail enabled +--FILE-- + +--EXPECTF-- +Warning: assert(): assert(false) failed in %s on line %d + +Warning: Uncaught Exception: Boo in %s:%d +Stack trace: +%a diff --git a/Zend/tests/gh16371.phpt b/Zend/tests/gh16371.phpt new file mode 100644 index 0000000000000..568bb8faf12f9 --- /dev/null +++ b/Zend/tests/gh16371.phpt @@ -0,0 +1,47 @@ +--TEST-- +GH-16371: Assertion failure in zend_weakmap_iterator_get_current_key() for invalid iterator +--FILE-- +getIterator(); + +print "# Empty WeakMap\n"; + +var_dump($it->key()); +var_dump($it->current()); +var_dump($it->valid()); + +$map = new WeakMap(); +$obj = new stdClass; +$map[$obj] = 0; + +print "# Valid iterator\n"; + +$it = $map->getIterator(); +var_dump($it->key()); +var_dump($it->current()); +var_dump($it->valid()); + +print "# End of iterator\n"; + +$it->next(); +var_dump($it->key()); +var_dump($it->current()); +var_dump($it->valid()); + +?> +--EXPECTF-- +# Empty WeakMap +NULL +NULL +bool(false) +# Valid iterator +object(stdClass)#%d (0) { +} +int(0) +bool(true) +# End of iterator +NULL +NULL +bool(false) diff --git a/Zend/tests/gh16508.phpt b/Zend/tests/gh16508.phpt new file mode 100644 index 0000000000000..e5b89b6029040 --- /dev/null +++ b/Zend/tests/gh16508.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-16508: Missing lineno in inheritance errors of delayed early bound classes +--EXTENSIONS-- +opcache +--INI-- +opcache.enable_cli=1 +--FILE-- + +--EXPECTF-- +Fatal error: Class Test2 contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Test::foo) in %s on line 5 diff --git a/Zend/tests/gh16509.inc b/Zend/tests/gh16509.inc new file mode 100644 index 0000000000000..297699fb671ea --- /dev/null +++ b/Zend/tests/gh16509.inc @@ -0,0 +1,7 @@ + +--EXPECTF-- +Fatal error: Cannot redeclare test() (previously declared in %sgh16509.inc:3) in %sgh16509.inc on line 3 diff --git a/Zend/tests/gh16515.phpt b/Zend/tests/gh16515.phpt new file mode 100644 index 0000000000000..63bb447e7ba2e --- /dev/null +++ b/Zend/tests/gh16515.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-16515: Incorrect propagation of ZEND_ACC_RETURN_REFERENCE for call trampoline +--FILE-- +bar(...)); + +?> +--EXPECTF-- +Notice: Only variable references should be returned by reference in %s on line %d diff --git a/Zend/tests/gh16648.phpt b/Zend/tests/gh16648.phpt new file mode 100644 index 0000000000000..9fdceba959456 --- /dev/null +++ b/Zend/tests/gh16648.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-16648: Use-after-free during array sorting +--FILE-- + '1', '3' => new C, '2' => '2']; +asort($arr); +var_dump($arr); + +?> +--EXPECT-- +array(11) { + ["a"]=> + string(1) "1" + [3]=> + int(3) + [2]=> + int(2) + [0]=> + int(0) + [1]=> + int(1) + [4]=> + int(4) + [5]=> + int(5) + [6]=> + int(6) + [7]=> + int(7) + [8]=> + int(8) + [9]=> + int(9) +} diff --git a/Zend/zend.c b/Zend/zend.c index 097018bf8bf73..d14b90a8d9cc1 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -803,6 +803,8 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ executor_globals->record_errors = false; executor_globals->num_errors = 0; executor_globals->errors = NULL; + executor_globals->filename_override = NULL; + executor_globals->lineno_override = -1; #ifdef ZEND_MAX_EXECUTION_TIMERS executor_globals->pid = 0; executor_globals->oldact = (struct sigaction){0}; diff --git a/Zend/zend.h b/Zend/zend.h index 8210737da9218..6ef5bd0b7a1cc 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.2.25-dev" +#define ZEND_VERSION "4.2.26" #define ZEND_ENGINE_3 diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index bbd3b85e6fba0..f261edb22bc90 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -845,7 +845,7 @@ void zend_closure_from_frame(zval *return_value, zend_execute_data *call) { /* { memset(&trampoline, 0, sizeof(zend_internal_function)); trampoline.type = ZEND_INTERNAL_FUNCTION; - trampoline.fn_flags = mptr->common.fn_flags & ZEND_ACC_STATIC; + trampoline.fn_flags = mptr->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_RETURN_REFERENCE); trampoline.handler = zend_closure_call_magic; trampoline.function_name = mptr->common.function_name; trampoline.scope = mptr->common.scope; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8662e188aaafb..5e79152f2578e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1072,7 +1072,7 @@ static zend_never_inline ZEND_COLD ZEND_NORETURN void do_bind_function_error(zen zend_error_noreturn(error_level, "Cannot redeclare %s() (previously declared in %s:%d)", op_array ? ZSTR_VAL(op_array->function_name) : ZSTR_VAL(old_function->common.function_name), ZSTR_VAL(old_function->op_array.filename), - old_function->op_array.opcodes[0].lineno); + old_function->op_array.line_start); } else { zend_error_noreturn(error_level, "Cannot redeclare %s()", op_array ? ZSTR_VAL(op_array->function_name) : ZSTR_VAL(old_function->common.function_name)); @@ -7516,6 +7516,7 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel) } else if (toplevel) { /* Only register the function after a successful compile */ if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) { + CG(zend_lineno) = decl->start_lineno; do_bind_function_error(lcname, op_array, true); } } diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 1c06104944f5c..35d3736bf68d0 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -2863,13 +2863,12 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q) q->h = h; } -ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber) +static void zend_hash_sort_internal(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber) { Bucket *p; uint32_t i, j; IS_CONSISTENT(ht); - HT_ASSERT_RC1(ht); if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) { /* Doesn't require sorting */ @@ -2956,6 +2955,33 @@ ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, b } } +ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber) +{ + HT_ASSERT_RC1(ht); + zend_hash_sort_internal(ht, sort, compar, renumber); +} + +void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber) +{ + HT_ASSERT_RC1(ht); + + /* Unpack the array early to avoid RCn assertion failures. */ + if (HT_IS_PACKED(ht)) { + zend_hash_packed_to_hash(ht); + } + + /* Adding a refcount prevents the array from going away. */ + GC_ADDREF(ht); + + zend_hash_sort_internal(ht, sort, compar, renumber); + + if (UNEXPECTED(GC_DELREF(ht) == 0)) { + zend_array_destroy(ht); + } else { + gc_check_possible_root((zend_refcounted *)ht); + } +} + static zend_always_inline int zend_hash_compare_impl(HashTable *ht1, HashTable *ht2, compare_func_t compar, bool ordered) { uint32_t idx1, idx2; zend_string *key1, *key2; diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index 5306fe34c79b4..12a633438b3d6 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -296,12 +296,20 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q); typedef int (*bucket_compare_func_t)(Bucket *a, Bucket *b); ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t compar, bool ordered); ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber); +void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber); ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag); static zend_always_inline void ZEND_FASTCALL zend_hash_sort(HashTable *ht, bucket_compare_func_t compare_func, zend_bool renumber) { zend_hash_sort_ex(ht, zend_sort, compare_func, renumber); } +/* Use this variant over zend_hash_sort() when sorting user arrays that may + * trigger user code. It will ensure the user code cannot free the array during + * sorting. */ +static zend_always_inline void zend_array_sort(HashTable *ht, bucket_compare_func_t compare_func, bool renumber) { + zend_array_sort_ex(ht, zend_sort, compare_func, renumber); +} + static zend_always_inline uint32_t zend_hash_num_elements(const HashTable *ht) { return ht->nNumOfElements; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 2e9bf25b59c48..8548d7fe3a010 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3222,6 +3222,8 @@ ZEND_API zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_ CG(current_linking_class) = is_cacheable ? ce : NULL; zend_try{ + CG(zend_lineno) = ce->info.user.line_start; + if (is_cacheable) { zend_begin_record_errors(); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index def9695ed98bd..3446fb2c96434 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1281,7 +1281,10 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend func->arg_flags[0] = 0; func->arg_flags[1] = 0; func->arg_flags[2] = 0; - func->fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_PUBLIC | ZEND_ACC_VARIADIC; + func->fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE + | ZEND_ACC_PUBLIC + | ZEND_ACC_VARIADIC + | (fbc->common.fn_flags & ZEND_ACC_RETURN_REFERENCE); if (is_static) { func->fn_flags |= ZEND_ACC_STATIC; } diff --git a/Zend/zend_string.c b/Zend/zend_string.c index 5e0fbea529b7e..c90af6e28884c 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -392,32 +392,32 @@ ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_va ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) { const char *ptr = ZSTR_VAL(s1); - size_t delta = (const char*)s2 - (const char*)s1; + uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; size_t len = ZSTR_LEN(s1); zend_ulong ret; __asm__ ( - ".LL0%=:\n\t" + "0:\n\t" "movl (%2,%3), %0\n\t" "xorl (%2), %0\n\t" - "jne .LL1%=\n\t" + "jne 1f\n\t" "addl $0x4, %2\n\t" "subl $0x4, %1\n\t" - "ja .LL0%=\n\t" + "ja 0b\n\t" "movl $0x1, %0\n\t" - "jmp .LL3%=\n\t" - ".LL1%=:\n\t" + "jmp 3f\n\t" + "1:\n\t" "cmpl $0x4,%1\n\t" - "jb .LL2%=\n\t" + "jb 2f\n\t" "xorl %0, %0\n\t" - "jmp .LL3%=\n\t" - ".LL2%=:\n\t" + "jmp 3f\n\t" + "2:\n\t" "negl %1\n\t" "lea 0x20(,%1,8), %1\n\t" "shll %b1, %0\n\t" "sete %b0\n\t" "movzbl %b0, %0\n\t" - ".LL3%=:\n" + "3:\n" : "=&a"(ret), "+c"(len), "+r"(ptr) @@ -430,32 +430,32 @@ ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) { const char *ptr = ZSTR_VAL(s1); - size_t delta = (const char*)s2 - (const char*)s1; + uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; size_t len = ZSTR_LEN(s1); zend_ulong ret; __asm__ ( - ".LL0%=:\n\t" + "0:\n\t" "movq (%2,%3), %0\n\t" "xorq (%2), %0\n\t" - "jne .LL1%=\n\t" + "jne 1f\n\t" "addq $0x8, %2\n\t" "subq $0x8, %1\n\t" - "ja .LL0%=\n\t" + "ja 0b\n\t" "movq $0x1, %0\n\t" - "jmp .LL3%=\n\t" - ".LL1%=:\n\t" + "jmp 3f\n\t" + "1:\n\t" "cmpq $0x8,%1\n\t" - "jb .LL2%=\n\t" + "jb 2f\n\t" "xorq %0, %0\n\t" - "jmp .LL3%=\n\t" - ".LL2%=:\n\t" + "jmp 3f\n\t" + "2:\n\t" "negq %1\n\t" "lea 0x40(,%1,8), %1\n\t" "shlq %b1, %0\n\t" "sete %b0\n\t" "movzbq %b0, %0\n\t" - ".LL3%=:\n" + "3:\n" : "=&a"(ret), "+c"(len), "+r"(ptr) diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index 3f2e517f3fe4e..bd25b5b839190 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -530,6 +530,10 @@ static void zend_weakmap_iterator_get_current_key(zend_object_iterator *obj_iter zend_string *string_key; zend_ulong num_key; int key_type = zend_hash_get_current_key_ex(&wm->ht, &string_key, &num_key, pos); + if (key_type == HASH_KEY_NON_EXISTENT) { + ZVAL_NULL(key); + return; + } if (key_type != HASH_KEY_IS_LONG) { ZEND_ASSERT(0 && "Must have integer key"); } diff --git a/configure.ac b/configure.ac index 9c71f6503311d..00f1b7f0e022d 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.2.25-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.26],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/ext/com_dotnet/com_variant.c b/ext/com_dotnet/com_variant.c index b40ac6a5c31f9..edb9d771aea8f 100644 --- a/ext/com_dotnet/com_variant.c +++ b/ext/com_dotnet/com_variant.c @@ -26,8 +26,7 @@ /* create an automation SafeArray from a PHP array. * Only creates a single-dimensional array of variants. - * The keys of the PHP hash MUST be numeric. If the array - * is sparse, then the gaps will be filled with NULL variants */ + * The keys of the PHP hash MUST be numeric. */ static void safe_array_from_zval(VARIANT *v, zval *z, int codepage) { SAFEARRAY *sa = NULL; @@ -71,7 +70,9 @@ static void safe_array_from_zval(VARIANT *v, zval *z, int codepage) break; } zend_hash_get_current_key_ex(Z_ARRVAL_P(z), &strindex, &intindex, &pos); - php_com_variant_from_zval(&va[intindex], item, codepage); + if (intindex < bound.cElements) { + php_com_variant_from_zval(&va[intindex], item, codepage); + } } /* Unlock it and stuff it into our variant */ diff --git a/ext/com_dotnet/tests/variant_variation.phpt b/ext/com_dotnet/tests/variant_variation.phpt new file mode 100644 index 0000000000000..c1f821715a14d --- /dev/null +++ b/ext/com_dotnet/tests/variant_variation.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing variant arrays +--EXTENSIONS-- +com_dotnet +--FILE-- + [2 => 1, 1 => 2, 0 => 3], + "off" => [2 => 1, 1 => 2, 3], + "negative" => [-1 => 42], +]; +foreach ($arrays as $desc => $array) { + echo "-- $desc --\n"; + $v = new variant($array); + foreach ($v as $val) { + var_dump($val); + } +} +?> +--EXPECTF-- +-- order -- +int(3) +int(2) +int(1) +-- off -- +NULL +int(2) +int(1) +-- negative -- +%ANULL diff --git a/ext/curl/multi.c b/ext/curl/multi.c index 70cc7e0366410..41df6bcd7b1ed 100644 --- a/ext/curl/multi.c +++ b/ext/curl/multi.c @@ -97,12 +97,14 @@ PHP_FUNCTION(curl_multi_add_handle) _php_curl_cleanup_handle(ch); - Z_ADDREF_P(z_ch); - zend_llist_add_element(&mh->easyh, z_ch); - error = curl_multi_add_handle(mh->multi, ch->cp); SAVE_CURLM_ERROR(mh, error); + if (error == CURLM_OK) { + Z_ADDREF_P(z_ch); + zend_llist_add_element(&mh->easyh, z_ch); + } + RETURN_LONG((zend_long) error); } /* }}} */ @@ -164,9 +166,11 @@ PHP_FUNCTION(curl_multi_remove_handle) error = curl_multi_remove_handle(mh->multi, ch->cp); SAVE_CURLM_ERROR(mh, error); - RETVAL_LONG((zend_long) error); - zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *))curl_compare_objects); + if (error == CURLM_OK) { + zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *))curl_compare_objects); + } + RETURN_LONG((zend_long) error); } /* }}} */ diff --git a/ext/curl/tests/curl_multi_add_handle_lifecycle.phpt b/ext/curl/tests/curl_multi_add_handle_lifecycle.phpt new file mode 100644 index 0000000000000..3027f0c331521 --- /dev/null +++ b/ext/curl/tests/curl_multi_add_handle_lifecycle.phpt @@ -0,0 +1,49 @@ +--TEST-- +curl_multi_add_handle does not hold onto the handle on failure +--EXTENSIONS-- +curl +--FILE-- + $ch) { + curl_multi_remove_handle($mh, $ch); + unset($ch); + unset($toRemove[$i]); +} +echo "Removed", PHP_EOL; + +?> +--EXPECTF-- +Removing +MyClass::__destruct +MyClass::__destruct +Removed diff --git a/ext/date/php_date.c b/ext/date/php_date.c index dc9eb995b8f65..9108a7c246a81 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2307,7 +2307,7 @@ static void add_common_properties(HashTable *myht, zend_object *zobj) common = zend_std_get_properties(zobj); - ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_IND(common, name, prop) { + ZEND_HASH_FOREACH_STR_KEY_VAL_IND(common, name, prop) { if (zend_hash_add(myht, name, prop) != NULL) { Z_TRY_ADDREF_P(prop); } @@ -2840,7 +2840,7 @@ static void restore_custom_datetime_properties(zval *object, HashTable *myht) zend_string *prop_name; zval *prop_val; - ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { + ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_time_is_internal_property(prop_name)) { continue; } @@ -3883,7 +3883,7 @@ static void restore_custom_datetimezone_properties(zval *object, HashTable *myht zend_string *prop_name; zval *prop_val; - ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { + ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_timezone_is_internal_property(prop_name)) { continue; } @@ -4512,7 +4512,7 @@ static void restore_custom_dateinterval_properties(zval *object, HashTable *myht zend_string *prop_name; zval *prop_val; - ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { + ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_interval_is_internal_property(prop_name)) { continue; } @@ -5111,6 +5111,10 @@ static void php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAMETERS, bool calc_s } altitude = 90 - zenith; + if (!zend_finite(latitude) || !zend_finite(longitude)) { + RETURN_FALSE; + } + /* Initialize time struct */ tzi = get_timezone_info(); if (!tzi) { @@ -5140,6 +5144,9 @@ static void php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAMETERS, bool calc_s if (N > 24 || N < 0) { N -= floor(N / 24) * 24; } + if (N > 24 || N < 0) { + RETURN_FALSE; + } switch (retformat) { case SUNFUNCS_RET_STRING: @@ -5185,6 +5192,15 @@ PHP_FUNCTION(date_sun_info) Z_PARAM_DOUBLE(longitude) ZEND_PARSE_PARAMETERS_END(); + if (!zend_finite(latitude)) { + zend_argument_value_error(2, "must be finite"); + RETURN_THROWS(); + } + if (!zend_finite(longitude)) { + zend_argument_value_error(3, "must be finite"); + RETURN_THROWS(); + } + /* Initialize time struct */ tzi = get_timezone_info(); if (!tzi) { @@ -5501,7 +5517,7 @@ static void restore_custom_dateperiod_properties(zval *object, HashTable *myht) zend_string *prop_name; zval *prop_val; - ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { + ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) { if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_period_is_internal_property(prop_name)) { continue; } diff --git a/ext/date/tests/bug-gh16037.phpt b/ext/date/tests/bug-gh16037.phpt new file mode 100644 index 0000000000000..834bc653c2fb5 --- /dev/null +++ b/ext/date/tests/bug-gh16037.phpt @@ -0,0 +1,9 @@ +--TEST-- +Test for bug GH-16037: Assertion failure in ext/date/php_date.c +--FILE-- +__unserialize([[]]); +echo gettype($di); +?> +--EXPECT-- +NULL diff --git a/ext/date/tests/gh14732.phpt b/ext/date/tests/gh14732.phpt new file mode 100644 index 0000000000000..a41d2885dd520 --- /dev/null +++ b/ext/date/tests/gh14732.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-14732 (date_sun_info() fails for non-finite values) +--FILE-- +getMessage(), "\n"; +} +try { + date_sun_info(1, -INF, 1); +} catch (ValueError $ex) { + echo $ex->getMessage(), "\n"; +} +try { + date_sun_info(1, 1, NAN); +} catch (ValueError $ex) { + echo $ex->getMessage(), "\n"; +} +try { + date_sun_info(1, 1, INF); +} catch (ValueError $ex) { + echo $ex->getMessage(), "\n"; +} +var_dump(date_sunset(1, SUNFUNCS_RET_STRING, NAN, 1)); +var_dump(date_sunrise(1, SUNFUNCS_RET_STRING, 1, NAN)); +?> +--EXPECTF-- +date_sun_info(): Argument #2 ($latitude) must be finite +date_sun_info(): Argument #2 ($latitude) must be finite +date_sun_info(): Argument #3 ($longitude) must be finite +date_sun_info(): Argument #3 ($longitude) must be finite + +Deprecated: Function date_sunset() is deprecated in %s on line %d +bool(false) + +Deprecated: Function date_sunrise() is deprecated in %s on line %d +bool(false) diff --git a/ext/date/tests/gh16454.phpt b/ext/date/tests/gh16454.phpt new file mode 100644 index 0000000000000..3d57276ddf5d3 --- /dev/null +++ b/ext/date/tests/gh16454.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-16454 (Unhandled INF in date_sunset() with tiny $utcOffset) +--FILE-- + +--EXPECTF-- +Deprecated: Function date_sunrise() is deprecated in %s on line %d +bool(false) + +Deprecated: Function date_sunrise() is deprecated in %s on line %d +bool(false) + +Deprecated: Function date_sunset() is deprecated in %s on line %d +bool(false) + +Deprecated: Function date_sunset() is deprecated in %s on line %d +bool(false) diff --git a/ext/dba/dba.c b/ext/dba/dba.c index 7e0f56b443e09..e25a801323349 100644 --- a/ext/dba/dba.c +++ b/ext/dba/dba.c @@ -772,11 +772,18 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent) info->lock.fp = php_stream_open_wrapper(lock_name, lock_file_mode, STREAM_MUST_SEEK|REPORT_ERRORS|IGNORE_PATH|persistent_flag, &opened_path); if (info->lock.fp) { if (is_db_lock) { - /* replace the path info with the real path of the opened file */ - pefree(info->path, persistent); - info->path = pestrndup(ZSTR_VAL(opened_path), ZSTR_LEN(opened_path), persistent); + if (opened_path) { + /* replace the path info with the real path of the opened file */ + pefree(info->path, persistent); + info->path = pestrndup(ZSTR_VAL(opened_path), ZSTR_LEN(opened_path), persistent); + } else { + error = "Unable to determine path for locking"; + } } + } + if (opened_path) { zend_string_release_ex(opened_path, 0); + opened_path = NULL; } } if (!is_db_lock) { @@ -788,10 +795,10 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent) FREE_PERSISTENT_RESOURCE_KEY(); RETURN_FALSE; } - if (!php_stream_supports_lock(info->lock.fp)) { + if (!error && !php_stream_supports_lock(info->lock.fp)) { error = "Stream does not support locking"; } - if (php_stream_lock(info->lock.fp, lock_mode)) { + if (!error && php_stream_lock(info->lock.fp, lock_mode)) { error = "Unable to establish lock"; /* force failure exit */ } } diff --git a/ext/dba/tests/gh16390.phpt b/ext/dba/tests/gh16390.phpt new file mode 100644 index 0000000000000..a5e4d9810964c --- /dev/null +++ b/ext/dba/tests/gh16390.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-16390 (dba_open() can segfault for "pathless" streams) +--EXTENSIONS-- +dba +--FILE-- + +--EXPECTF-- +Warning: dba_open(): Driver initialization failed for handler: inifile: Unable to determine path for locking in %s on line %d diff --git a/ext/dom/node.c b/ext/dom/node.c index bb80408f2689f..fc668415daecf 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -872,6 +872,17 @@ static bool dom_node_check_legacy_insertion_validity(xmlNodePtr parentp, xmlNode php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); return false; } + /* Attributes must be in elements. */ + if (child->type == XML_ATTRIBUTE_NODE && parentp->type != XML_ELEMENT_NODE) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); + return false; + } + + /* Documents can never be a child. */ + if (child->type == XML_DOCUMENT_NODE || child->type == XML_HTML_DOCUMENT_NODE) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); + return false; + } return true; } @@ -882,7 +893,7 @@ static bool dom_node_check_legacy_insertion_validity(xmlNodePtr parentp, xmlNode PHP_METHOD(DOMNode, insertBefore) { zval *id, *node, *ref = NULL; - xmlNodePtr child, new_child, parentp, refp; + xmlNodePtr child, new_child, parentp, refp = NULL; dom_object *intern, *childobj, *refpobj; int ret, stricterror; @@ -907,18 +918,21 @@ PHP_METHOD(DOMNode, insertBefore) RETURN_FALSE; } - if (child->doc == NULL && parentp->doc != NULL) { - childobj->document = intern->document; - php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL); - } - + /* Fetch and perform sanity checks before modifying reference pointers. */ if (ref != NULL) { DOM_GET_OBJ(refp, ref, xmlNodePtr, refpobj); if (refp->parent != parentp) { php_dom_throw_error(NOT_FOUND_ERR, stricterror); RETURN_FALSE; } + } + + if (child->doc == NULL && parentp->doc != NULL) { + childobj->document = intern->document; + php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL); + } + if (ref != NULL) { if (child->parent != NULL) { xmlUnlinkNode(child); } @@ -1082,6 +1096,13 @@ PHP_METHOD(DOMNode, replaceChild) RETURN_FALSE; } + /* This is already disallowed by libxml, but we should check it here to avoid + * breaking assumptions and assertions. */ + if ((oldchild->type == XML_ATTRIBUTE_NODE) != (newchild->type == XML_ATTRIBUTE_NODE)) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); + RETURN_FALSE; + } + if (oldchild->parent != nodep) { php_dom_throw_error(NOT_FOUND_ERR, stricterror); RETURN_FALSE; diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 0ed7b4544a501..ea80d206bb352 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -933,4 +933,4 @@ public function registerPhpFunctions(string|array|null $restrict = null): void { } #endif -function dom_import_simplexml(object $node): DOMElement {} +function dom_import_simplexml(object $node): DOMAttr|DOMElement {} diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 6aa7f2eb8a388..de1e201fc1e8d 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,7 +1,7 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7d4dc9e1a3736f2ac9082c32bf5260dfa58b1aa0 */ + * Stub hash: 6d1c16a61f23de241f72f3559a9987d2f4a9c6fd */ -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_dom_import_simplexml, 0, 1, DOMAttr|DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) ZEND_END_ARG_INFO() diff --git a/ext/dom/tests/gh16316.phpt b/ext/dom/tests/gh16316.phpt new file mode 100644 index 0000000000000..43368746bc462 --- /dev/null +++ b/ext/dom/tests/gh16316.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-16316 (DOMXPath breaks when not initialized properly) +--EXTENSIONS-- +dom +--FILE-- +getMessage(), "\n"; +} + +try { + var_dump($demo->document); +} catch (DOMException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +object(Demo)#1 (1) { + ["registerNodeNamespaces"]=> + bool(true) +} +Invalid State Error +Invalid State Error diff --git a/ext/dom/tests/gh16473.phpt b/ext/dom/tests/gh16473.phpt new file mode 100644 index 0000000000000..f4f7308ca901c --- /dev/null +++ b/ext/dom/tests/gh16473.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-16473 (dom_import_simplexml stub is wrong) +--EXTENSIONS-- +dom +simplexml +--FILE-- +'); +$attr = $root->attributes('urn:x'); +var_dump(dom_import_simplexml($attr)->textContent); +?> +--EXPECT-- +string(3) "foo" diff --git a/ext/dom/tests/gh16533.phpt b/ext/dom/tests/gh16533.phpt new file mode 100644 index 0000000000000..dad40e88b4d16 --- /dev/null +++ b/ext/dom/tests/gh16533.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-16533 (Segfault when adding attribute to parent that is not an element) +--EXTENSIONS-- +dom +--FILE-- +appendChild($doc->createAttribute('foo')); +} catch (DOMException $e) { + echo $e->getMessage(), "\n"; +} + +echo $doc->saveXML(); + +?> +--EXPECT-- +Hierarchy Request Error + diff --git a/ext/dom/tests/gh16535.phpt b/ext/dom/tests/gh16535.phpt new file mode 100644 index 0000000000000..1c8d282303c88 --- /dev/null +++ b/ext/dom/tests/gh16535.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-16535 (UAF when using document as a child) +--EXTENSIONS-- +dom +--FILE-- +loadHTML("t"); +$v4 = $v2->createElement('foo'); +try { + $v4->appendChild($v2); +} catch (DOMException $e) { + echo $e->getMessage(), "\n"; +} +$v2->loadHTML("oU"); +echo $v2->saveXML(); + +?> +--EXPECT-- +Hierarchy Request Error + + +

oU

diff --git a/ext/dom/tests/gh16593.phpt b/ext/dom/tests/gh16593.phpt new file mode 100644 index 0000000000000..416d794a4f0d9 --- /dev/null +++ b/ext/dom/tests/gh16593.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-16593 (Assertion failure in DOM->replaceChild) +--EXTENSIONS-- +dom +--FILE-- +appendChild($doc->createElement('root')); +$child = $root->appendChild($doc->createElement('child')); +try { + $root->replaceChild($doc->createAttribute('foo'), $child); +} catch (DOMException $e) { + echo $e->getMessage(), "\n"; +} +echo $doc->saveXML(); + +?> +--EXPECT-- +Hierarchy Request Error + + diff --git a/ext/dom/xml_common.h b/ext/dom/xml_common.h index da210aae96707..b0a41398b6be9 100644 --- a/ext/dom/xml_common.h +++ b/ext/dom/xml_common.h @@ -60,21 +60,23 @@ PHP_DOM_EXPORT xmlNodePtr dom_object_get_node(dom_object *obj); (const xmlChar *) "http://www.w3.org/2000/xmlns/" #define NODE_GET_OBJ(__ptr, __id, __prtype, __intern) { \ - __intern = Z_LIBXML_NODE_P(__id); \ + zval *__zv = (__id); \ + __intern = Z_LIBXML_NODE_P(__zv); \ if (__intern->node == NULL || !(__ptr = (__prtype)__intern->node->node)) { \ - php_error_docref(NULL, E_WARNING, "Couldn't fetch %s", \ - ZSTR_VAL(__intern->std.ce->name));\ - RETURN_NULL();\ + php_error_docref(NULL, E_WARNING, "Couldn't fetch %s", \ + ZSTR_VAL(Z_OBJCE_P(__zv)->name));\ + RETURN_NULL();\ } \ } #define DOC_GET_OBJ(__ptr, __id, __prtype, __intern) { \ - __intern = Z_LIBXML_NODE_P(__id); \ + zval *__zv = (__id); \ + __intern = Z_LIBXML_NODE_P(__zv); \ if (__intern->document != NULL) { \ if (!(__ptr = (__prtype)__intern->document->ptr)) { \ - php_error_docref(NULL, E_WARNING, "Couldn't fetch %s", __intern->std.ce->name);\ - RETURN_NULL();\ - } \ + php_error_docref(NULL, E_WARNING, "Couldn't fetch %s", ZSTR_VAL(Z_OBJCE_P(__zv)->name));\ + RETURN_NULL();\ + } \ } \ } diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c index 22a7e0ecc5a91..cad08e0a8d8a6 100644 --- a/ext/dom/xpath.c +++ b/ext/dom/xpath.c @@ -264,6 +264,11 @@ int dom_xpath_document_read(dom_object *obj, zval *retval) docp = (xmlDocPtr) ctx->doc; } + if (UNEXPECTED(!docp)) { + php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true); + return FAILURE; + } + php_dom_create_object((xmlNodePtr) docp, retval, obj); return SUCCESS; } diff --git a/ext/exif/exif.c b/ext/exif/exif.c index bf5fed01db52f..3081ad9a1b2dd 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -4419,7 +4419,7 @@ static bool exif_read_from_impl(image_info_type *ImageInfo, php_stream *stream, ImageInfo->FileName = NULL; if (php_stream_is(ImageInfo->infile, PHP_STREAM_IS_STDIO)) { - if (VCWD_STAT(stream->orig_path, &st) >= 0) { + if (stream->orig_path && VCWD_STAT(stream->orig_path, &st) >= 0) { zend_string *base; if ((st.st_mode & S_IFMT) != S_IFREG) { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file"); diff --git a/ext/exif/tests/gh16409.phpt b/ext/exif/tests/gh16409.phpt new file mode 100644 index 0000000000000..c2c54d839e011 --- /dev/null +++ b/ext/exif/tests/gh16409.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-16409 (Segfault in exif_thumbnail when not dealing with a real file) +--EXTENSIONS-- +exif +--FILE-- + +--EXPECTF-- +Warning: exif_thumbnail(): File too small (0) in %s on line %d +bool(false)%A diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 8b0b58105d320..7f9d70febcc31 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -2922,6 +2922,12 @@ static zend_function *zend_ffi_get_func(zend_object **obj, zend_string *name, co } /* }}} */ +static int zend_fake_compare_objects(zval *o1, zval *o2) +{ + zend_throw_error(zend_ffi_exception_ce, "Cannot compare FFI objects"); + return ZEND_UNCOMPARABLE; +} + static zend_never_inline int zend_ffi_disabled(void) /* {{{ */ { zend_throw_error(zend_ffi_exception_ce, "FFI API is restricted by \"ffi.enable\" configuration directive"); @@ -5367,7 +5373,7 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_handlers.has_dimension = zend_fake_has_dimension; zend_ffi_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_handlers.get_method = zend_ffi_get_func; - zend_ffi_handlers.compare = NULL; + zend_ffi_handlers.compare = zend_fake_compare_objects; zend_ffi_handlers.cast_object = zend_fake_cast_object; zend_ffi_handlers.get_debug_info = NULL; zend_ffi_handlers.get_closure = NULL; diff --git a/ext/ffi/tests/gh16397.phpt b/ext/ffi/tests/gh16397.phpt new file mode 100644 index 0000000000000..19ddf7ecc62ae --- /dev/null +++ b/ext/ffi/tests/gh16397.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-16397 (Segmentation fault when comparing FFI object) +--EXTENSIONS-- +ffi +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +Cannot compare FFI objects diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index ca8e65c1f75f6..7ac64444fa7f9 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -542,7 +542,7 @@ static int _php_filter_validate_domain(char * domain, size_t len, zend_long flag /* Reset label length counter */ i = 1; } else { - if (i > 63 || (hostname && *s != '-' && !isalnum((int)*(unsigned char *)s))) { + if (i > 63 || (hostname && (*s != '-' || *(s + 1) == '\0') && !isalnum((int)*(unsigned char *)s))) { return 0; } diff --git a/ext/filter/tests/gh16523.phpt b/ext/filter/tests/gh16523.phpt new file mode 100644 index 0000000000000..b2985c12a7827 --- /dev/null +++ b/ext/filter/tests/gh16523.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-16523 (FILTER_FLAG_HOSTNAME accepts ending hyphen) +--EXTENSIONS-- +filter +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(false) diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 3b824430597b6..6b41efd949a2c 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -3687,13 +3687,25 @@ PHP_FUNCTION(imageaffine) if ((zval_affine_elem = zend_hash_index_find(Z_ARRVAL_P(z_affine), i)) != NULL) { switch (Z_TYPE_P(zval_affine_elem)) { case IS_LONG: - affine[i] = Z_LVAL_P(zval_affine_elem); + affine[i] = Z_LVAL_P(zval_affine_elem); + if (affine[i] < INT_MIN || affine[i] > INT_MAX) { + zend_argument_value_error(2, "element %i must be between %d and %d", i, INT_MIN, INT_MAX); + RETURN_THROWS(); + } break; case IS_DOUBLE: affine[i] = Z_DVAL_P(zval_affine_elem); + if (affine[i] < INT_MIN || affine[i] > INT_MAX) { + zend_argument_value_error(2, "element %i must be between %d and %d", i, INT_MIN, INT_MAX); + RETURN_THROWS(); + } break; case IS_STRING: affine[i] = zval_get_double(zval_affine_elem); + if (affine[i] < INT_MIN || affine[i] > INT_MAX) { + zend_argument_value_error(2, "element %i must be between %d and %d", i, INT_MIN, INT_MAX); + RETURN_THROWS(); + } break; default: zend_argument_type_error(3, "contains invalid type for element %i", i); diff --git a/ext/gd/libgd/gd_avif.c b/ext/gd/libgd/gd_avif.c index 30075d2a899c8..9c1ffdc34bc68 100644 --- a/ext/gd/libgd/gd_avif.c +++ b/ext/gd/libgd/gd_avif.c @@ -393,7 +393,13 @@ gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) // (While AVIF image pixel depth can be 8, 10, or 12 bits, GD truecolor images are 8-bit.) avifRGBImageSetDefaults(&rgb, decoder->image); rgb.depth = 8; +#if AVIF_VERSION >= 1000000 + result = avifRGBImageAllocatePixels(&rgb); + if (isAvifError(result, "Allocating RGB pixels failed")) + goto cleanup; +#else avifRGBImageAllocatePixels(&rgb); +#endif result = avifImageYUVToRGB(decoder->image, &rgb); if (isAvifError(result, "Conversion from YUV to RGB failed")) @@ -522,14 +528,25 @@ void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) // Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from RGB to YUV. avifImage *avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling); - +#if AVIF_VERSION >= 1000000 + if (avifIm == NULL) { + gd_error("avif error - Creating image failed\n"); + goto cleanup; + } +#endif avifIm->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709; avifIm->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; avifIm->matrixCoefficients = lossless ? AVIF_MATRIX_COEFFICIENTS_IDENTITY : AVIF_MATRIX_COEFFICIENTS_BT709; avifRGBImageSetDefaults(&rgb, avifIm); // this allocates memory, and sets rgb.rowBytes and rgb.pixels. +#if AVIF_VERSION >= 1000000 + result = avifRGBImageAllocatePixels(&rgb); + if (isAvifError(result, "Allocating RGB pixels failed")) + goto cleanup; +#else avifRGBImageAllocatePixels(&rgb); +#endif // Parse RGB data from the GD image, and copy it into the AVIF RGB image. // Convert 7-bit GD alpha channel values to 8-bit AVIF values. @@ -555,6 +572,12 @@ void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) // Encode the image in AVIF format. encoder = avifEncoderCreate(); +#if AVIF_VERSION >= 1000000 + if (encoder == NULL) { + gd_error("avif error - Creating encoder failed\n"); + goto cleanup; + } +#endif int quantizerQuality = quality == QUALITY_DEFAULT ? QUANTIZER_DEFAULT : quality2Quantizer(quality); diff --git a/ext/gd/libgd/gd_interpolation.c b/ext/gd/libgd/gd_interpolation.c index 2fde78be19895..4b1583a8d6e82 100644 --- a/ext/gd/libgd/gd_interpolation.c +++ b/ext/gd/libgd/gd_interpolation.c @@ -929,21 +929,29 @@ static inline LineContribType *_gdContributionsCalc(unsigned int line_size, unsi return res; } +/* Convert a double to an unsigned char, rounding to the nearest + * integer and clamping the result between 0 and max. The absolute + * value of clr must be less than the maximum value of an unsigned + * short. */ static inline unsigned char -uchar_clamp(double clr) { +uchar_clamp(double clr, unsigned char max) { unsigned short result; - assert(fabs(clr) <= SHRT_MAX); + + //assert(fabs(clr) <= SHRT_MAX); + /* Casting a negative float to an unsigned short is undefined. * However, casting a float to a signed truncates toward zero and * casting a negative signed value to an unsigned of the same size * results in a bit-identical value (assuming twos-complement * arithmetic). This is what we want: all legal negative values * for clr will be greater than 255. */ + /* Convert and clamp. */ result = (unsigned short)(short)(clr + 0.5); - if (result > 255) { - result = (clr < 0) ? 0 : 255; + if (result > max) { + result = (clr < 0) ? 0 : max; }/* if */ + return result; }/* uchar_clamp*/ @@ -967,7 +975,9 @@ static inline void _gdScaleRow(gdImagePtr pSrc, unsigned int src_width, gdImage b += contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetBlue(p_src_row[i])); a += contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetAlpha(p_src_row[i])); } - p_dst_row[x] = gdTrueColorAlpha(uchar_clamp(r), uchar_clamp(g), uchar_clamp(b), uchar_clamp(a)); + p_dst_row[x] = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF), + uchar_clamp(b, 0xFF), + uchar_clamp(a, 0x7F)); /* alpha is 0..127 */ } } @@ -1014,7 +1024,9 @@ static inline void _gdScaleCol (gdImagePtr pSrc, unsigned int src_width, gdImag b += contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetBlue(pCurSrc)); a += contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetAlpha(pCurSrc)); } - pRes->tpixels[y][uCol] = gdTrueColorAlpha(uchar_clamp(r), uchar_clamp(g), uchar_clamp(b), uchar_clamp(a)); + pRes->tpixels[y][uCol] = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF), + uchar_clamp(b, 0xFF), + uchar_clamp(a, 0x7F)); /* alpha is 0..127 */ } } diff --git a/ext/gd/tests/gh16322.phpt b/ext/gd/tests/gh16322.phpt new file mode 100644 index 0000000000000..1edc27285d2db --- /dev/null +++ b/ext/gd/tests/gh16322.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-16322 (imageaffine overflow/underflow on affine matrix) +--EXTENSIONS-- +gd +--INI-- +memory_limit=-1 +--FILE-- +getMessage() . PHP_EOL; +} +$matrix[0] = 1; +$matrix[3] = -INF; +try { + imageaffine($src, $matrix); +} catch (\ValueError $e) { + echo $e->getMessage(); +} +?> +--EXPECTF-- +imageaffine(): Argument #2 ($affine) element 0 must be between %s and %d +imageaffine(): Argument #2 ($affine) element 3 must be between %s and %d diff --git a/ext/gd/tests/gh16559.phpt b/ext/gd/tests/gh16559.phpt new file mode 100644 index 0000000000000..4481311c4c459 --- /dev/null +++ b/ext/gd/tests/gh16559.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-16559 (UBSan abort in ext/gd/libgd/gd_interpolation.c:1007) +--EXTENSIONS-- +gd +--FILE-- + +--EXPECT-- +object(GdImage)#2 (0) { +} diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 02eb30bacb730..c9603c8fb21e8 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -334,8 +334,34 @@ static zend_object *gmp_clone_obj(zend_object *obj) /* {{{ */ } /* }}} */ -static void shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, zend_uchar opcode) { - zend_long shift = zval_get_long(op2); +static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, zend_uchar opcode) { + zend_long shift = 0; + + if (UNEXPECTED(Z_TYPE_P(op2) != IS_LONG)) { + if (UNEXPECTED(!IS_GMP(op2))) { + // For PHP 8.3 and up use zend_try_get_long() + switch (Z_TYPE_P(op2)) { + case IS_DOUBLE: + shift = zval_get_long(op2); + if (UNEXPECTED(EG(exception))) { + return FAILURE; + } + break; + case IS_STRING: + if (is_numeric_str_function(Z_STR_P(op2), &shift, NULL) != IS_LONG) { + goto valueof_op_failure; + } + break; + default: + goto typeof_op_failure; + } + } else { + // TODO We shouldn't cast the GMP object to int here + shift = zval_get_long(op2); + } + } else { + shift = Z_LVAL_P(op2); + } if (shift < 0) { zend_throw_error( @@ -343,16 +369,54 @@ static void shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zva opcode == ZEND_POW ? "Exponent" : "Shift" ); ZVAL_UNDEF(return_value); - return; + return FAILURE; } else { mpz_ptr gmpnum_op, gmpnum_result; gmp_temp_t temp; - FETCH_GMP_ZVAL(gmpnum_op, op1, temp, 1); + /* We do not use FETCH_GMP_ZVAL(...); here as we don't use convert_to_gmp() + * as we want to handle the emitted exception ourself. */ + if (UNEXPECTED(!IS_GMP(op1))) { + if (UNEXPECTED(Z_TYPE_P(op1) != IS_LONG)) { + goto typeof_op_failure; + } + mpz_init(temp.num); + mpz_set_si(temp.num, Z_LVAL_P(op1)); + temp.is_used = 1; + gmpnum_op = temp.num; + } else { + gmpnum_op = GET_GMP_FROM_ZVAL(op1); + temp.is_used = 0; + } INIT_GMP_RETVAL(gmpnum_result); op(gmpnum_result, gmpnum_op, (gmp_ulong) shift); FREE_GMP_TEMP(temp); + return SUCCESS; + } + +typeof_op_failure: ; + /* Returning FAILURE without throwing an exception would emit the + * Unsupported operand types: GMP OP TypeOfOp2 + * However, this leads to the engine trying to interpret the GMP object as an integer + * and doing the operation that way, which is not something we want. */ + const char *op_sigil; + switch (opcode) { + case ZEND_POW: + op_sigil = "**"; + break; + case ZEND_SL: + op_sigil = "<<"; + break; + case ZEND_SR: + op_sigil = ">>"; + break; + EMPTY_SWITCH_DEFAULT_CASE(); } + zend_type_error("Unsupported operand types: %s %s %s", zend_zval_type_name(op1), op_sigil, zend_zval_type_name(op2)); + return FAILURE; +valueof_op_failure: + zend_value_error("Number is not an integer string"); + return FAILURE; } #define DO_BINARY_UI_OP_EX(op, uop, check_b_zero) \ @@ -381,18 +445,15 @@ static zend_result gmp_do_operation_ex(zend_uchar opcode, zval *result, zval *op case ZEND_MUL: DO_BINARY_UI_OP(mpz_mul); case ZEND_POW: - shift_operator_helper(mpz_pow_ui, result, op1, op2, opcode); - return SUCCESS; + return shift_operator_helper(mpz_pow_ui, result, op1, op2, opcode); case ZEND_DIV: DO_BINARY_UI_OP_EX(mpz_tdiv_q, gmp_mpz_tdiv_q_ui, 1); case ZEND_MOD: DO_BINARY_UI_OP_EX(mpz_mod, gmp_mpz_mod_ui, 1); case ZEND_SL: - shift_operator_helper(mpz_mul_2exp, result, op1, op2, opcode); - return SUCCESS; + return shift_operator_helper(mpz_mul_2exp, result, op1, op2, opcode); case ZEND_SR: - shift_operator_helper(mpz_fdiv_q_2exp, result, op1, op2, opcode); - return SUCCESS; + return shift_operator_helper(mpz_fdiv_q_2exp, result, op1, op2, opcode); case ZEND_BW_OR: DO_BINARY_OP(mpz_ior); case ZEND_BW_AND: @@ -619,6 +680,13 @@ static zend_result convert_to_gmp(mpz_t gmpnumber, zval *val, zend_long base, ui case IS_STRING: { return convert_zstr_to_gmp(gmpnumber, Z_STR_P(val), base, arg_pos); } + case IS_NULL: + /* Just reject null for operator overloading */ + if (arg_pos == 0) { + zend_type_error("Number must be of type GMP|string|int, %s given", zend_zval_type_name(val)); + return FAILURE; + } + ZEND_FALLTHROUGH; default: { zend_long lval; if (!zend_parse_arg_long_slow(val, &lval, arg_pos)) { @@ -1002,8 +1070,14 @@ ZEND_FUNCTION(gmp_export) if (mpz_sgn(gmpnumber) == 0) { RETVAL_EMPTY_STRING(); } else { - size_t bits_per_word = size * 8; - size_t count = (mpz_sizeinbase(gmpnumber, 2) + bits_per_word - 1) / bits_per_word; + ZEND_ASSERT(size > 0); + size_t size_in_base_2 = mpz_sizeinbase(gmpnumber, 2); + if (size > ZEND_LONG_MAX / 4 || size_in_base_2 > SIZE_MAX - (size_t) size * 8 + 1) { + zend_argument_value_error(2, "is too large for argument #1 ($num)"); + RETURN_THROWS(); + } + size_t bits_per_word = (size_t) size * 8; + size_t count = (size_in_base_2 + bits_per_word - 1) / bits_per_word; zend_string *out_string = zend_string_safe_alloc(count, size, 0, 0); mpz_export(ZSTR_VAL(out_string), NULL, order, size, endian, 0, gmpnumber); @@ -1276,13 +1350,26 @@ ZEND_FUNCTION(gmp_pow) RETURN_THROWS(); } + double powmax = log((double)ZEND_LONG_MAX); + if (Z_TYPE_P(base_arg) == IS_LONG && Z_LVAL_P(base_arg) >= 0) { INIT_GMP_RETVAL(gmpnum_result); + if ((log(Z_LVAL_P(base_arg)) * exp) > powmax) { + zend_value_error("base and exponent overflow"); + RETURN_THROWS(); + } mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp); } else { mpz_ptr gmpnum_base; + zend_ulong gmpnum; FETCH_GMP_ZVAL(gmpnum_base, base_arg, temp_base, 1); INIT_GMP_RETVAL(gmpnum_result); + gmpnum = mpz_get_ui(gmpnum_base); + if ((log(gmpnum) * exp) > powmax) { + FREE_GMP_TEMP(temp_base); + zend_value_error("base and exponent overflow"); + RETURN_THROWS(); + } mpz_pow_ui(gmpnum_result, gmpnum_base, exp); FREE_GMP_TEMP(temp_base); } @@ -1772,15 +1859,21 @@ ZEND_FUNCTION(gmp_random_bits) RETURN_THROWS(); } - if (bits <= 0) { - zend_argument_value_error(1, "must be greater than or equal to 1"); +#if SIZEOF_SIZE_T == 4 + const zend_long maxbits = ULONG_MAX / GMP_NUMB_BITS; +#else + const zend_long maxbits = INT_MAX; +#endif + + if (bits <= 0 || bits > maxbits) { + zend_argument_value_error(1, "must be between 1 and " ZEND_LONG_FMT, maxbits); RETURN_THROWS(); } INIT_GMP_RETVAL(gmpnum_result); gmp_init_random(); - mpz_urandomb(gmpnum_result, GMPG(rand_state), bits); + mpz_urandomb(gmpnum_result, GMPG(rand_state), (mp_bitcnt_t)bits); } /* }}} */ diff --git a/ext/gmp/tests/gh16411.phpt b/ext/gmp/tests/gh16411.phpt new file mode 100644 index 0000000000000..798943002cfce --- /dev/null +++ b/ext/gmp/tests/gh16411.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-16411 (gmp_export() can cause overflow) +--EXTENSIONS-- +gmp +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught ValueError: gmp_export(): Argument #2 ($word_size) is too large for argument #1 ($num) in %s:%d +%A diff --git a/ext/gmp/tests/gh16501.phpt b/ext/gmp/tests/gh16501.phpt new file mode 100644 index 0000000000000..325be85d1917e --- /dev/null +++ b/ext/gmp/tests/gh16501.phpt @@ -0,0 +1,14 @@ +--TEST-- +GH-16501 (gmp_random_bits overflow) +--EXTENSIONS-- +gmp +--FILE-- +getMessage(); +} +?> +--EXPECTF-- +gmp_random_bits(): Argument #1 ($bits) must be between 1 and %d diff --git a/ext/gmp/tests/gmp_pow.phpt b/ext/gmp/tests/gmp_pow.phpt index f42e44e31abed..1d77bd5e96c80 100644 --- a/ext/gmp/tests/gmp_pow.phpt +++ b/ext/gmp/tests/gmp_pow.phpt @@ -2,6 +2,8 @@ gmp_pow() basic tests --EXTENSIONS-- gmp +--SKIPIF-- + --FILE-- +--FILE-- +getMessage() . "\n"; +} +var_dump(gmp_strval(gmp_pow("-2",10))); +try { + gmp_pow(20,10); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} +try { + gmp_pow(50,10); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} +try { + gmp_pow(50,-5); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} +try { + $n = gmp_init("20"); + gmp_pow($n,10); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} +try { + $n = gmp_init("-20"); + gmp_pow($n,10); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} +try { + var_dump(gmp_pow(2,array())); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(gmp_pow(array(),10)); +} catch (\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +echo "Done\n"; +?> +--EXPECT-- +string(4) "1024" +string(4) "1024" +string(5) "-2048" +string(4) "1024" +string(1) "1" +gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0 +string(4) "1024" +base and exponent overflow +base and exponent overflow +gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0 +base and exponent overflow +base and exponent overflow +gmp_pow(): Argument #2 ($exponent) must be of type int, array given +gmp_pow(): Argument #1 ($num) must be of type GMP|string|int, array given +Done diff --git a/ext/gmp/tests/gmp_pow_fpe.phpt b/ext/gmp/tests/gmp_pow_fpe.phpt new file mode 100644 index 0000000000000..248922e80514d --- /dev/null +++ b/ext/gmp/tests/gmp_pow_fpe.phpt @@ -0,0 +1,35 @@ +--TEST-- +gmp_pow() floating point exception +--EXTENSIONS-- +gmp +--FILE-- +getMessage() . PHP_EOL; +} +try { + gmp_pow(256, PHP_INT_MAX); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} + +try { + gmp_pow(gmp_add(gmp_mul(gmp_init(PHP_INT_MAX), gmp_init(PHP_INT_MAX)), 3), 256); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} +try { + gmp_pow(gmp_init(PHP_INT_MAX), 256); +} catch (\ValueError $e) { + echo $e->getMessage(); +} +?> +--EXPECTF-- +base and exponent overflow +base and exponent overflow +base and exponent overflow +base and exponent overflow diff --git a/ext/gmp/tests/gmp_random_bits.phpt b/ext/gmp/tests/gmp_random_bits.phpt index 3dbfc097d28d5..4e7f337983891 100644 --- a/ext/gmp/tests/gmp_random_bits.phpt +++ b/ext/gmp/tests/gmp_random_bits.phpt @@ -40,7 +40,7 @@ while (1) { echo "Done\n"; ?> ---EXPECT-- -gmp_random_bits(): Argument #1 ($bits) must be greater than or equal to 1 -gmp_random_bits(): Argument #1 ($bits) must be greater than or equal to 1 +--EXPECTF-- +gmp_random_bits(): Argument #1 ($bits) must be between 1 and %d +gmp_random_bits(): Argument #1 ($bits) must be between 1 and %d Done diff --git a/ext/gmp/tests/overloading_cmp_op_with_null.phpt b/ext/gmp/tests/overloading_cmp_op_with_null.phpt new file mode 100644 index 0000000000000..d84ef967868e1 --- /dev/null +++ b/ext/gmp/tests/overloading_cmp_op_with_null.phpt @@ -0,0 +1,53 @@ +--TEST-- +GMP comparison operator overloading supports null +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num > null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num <= null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num >= null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num == null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num <=> null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +int(1) diff --git a/ext/gmp/tests/overloading_with_array.phpt b/ext/gmp/tests/overloading_with_array.phpt new file mode 100644 index 0000000000000..a23df9381dcde --- /dev/null +++ b/ext/gmp/tests/overloading_with_array.phpt @@ -0,0 +1,84 @@ +--TEST-- +GMP operator overloading does not support [] +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: Number must be of type GMP|string|int, array given +TypeError: Number must be of type GMP|string|int, array given +TypeError: Number must be of type GMP|string|int, array given +TypeError: Number must be of type GMP|string|int, array given +TypeError: Number must be of type GMP|string|int, array given +TypeError: Unsupported operand types: GMP ** array +TypeError: Number must be of type GMP|string|int, array given +TypeError: Number must be of type GMP|string|int, array given +TypeError: Number must be of type GMP|string|int, array given +TypeError: Unsupported operand types: GMP << array +TypeError: Unsupported operand types: GMP >> array diff --git a/ext/gmp/tests/overloading_with_float.phpt b/ext/gmp/tests/overloading_with_float.phpt new file mode 100644 index 0000000000000..f2bcfa7d51485 --- /dev/null +++ b/ext/gmp/tests/overloading_with_float.phpt @@ -0,0 +1,117 @@ +--TEST-- +GMP operator overloading does support float with no fractional +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> 42.0); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +object(GMP)#2 (1) { + ["num"]=> + string(2) "84" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#2 (1) { + ["num"]=> + string(4) "1764" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "1" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#2 (1) { + ["num"]=> + string(69) "150130937545296572356771972164254457814047970568738777235893533016064" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "42" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "42" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#2 (1) { + ["num"]=> + string(15) "184717953466368" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} diff --git a/ext/gmp/tests/overloading_with_float_fractional.phpt b/ext/gmp/tests/overloading_with_float_fractional.phpt new file mode 100644 index 0000000000000..fc078eeec3e98 --- /dev/null +++ b/ext/gmp/tests/overloading_with_float_fractional.phpt @@ -0,0 +1,132 @@ +--TEST-- +GMP operator overloading support for float with fractional is deprecated +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> 42.5); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECTF-- +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(2) "84" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(4) "1764" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(1) "1" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#2 (1) { + ["num"]=> + string(69) "150130937545296572356771972164254457814047970568738777235893533016064" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(2) "42" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(2) "42" +} + +Deprecated: Implicit conversion from float 42.5 to int loses precision in %s on line %d +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#2 (1) { + ["num"]=> + string(15) "184717953466368" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} diff --git a/ext/gmp/tests/overloading_with_float_string.phpt b/ext/gmp/tests/overloading_with_float_string.phpt new file mode 100644 index 0000000000000..b01c99e969c23 --- /dev/null +++ b/ext/gmp/tests/overloading_with_float_string.phpt @@ -0,0 +1,84 @@ +--TEST-- +GMP operator overloading does not support float strings +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> "2.0"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string diff --git a/ext/gmp/tests/overloading_with_int_string.phpt b/ext/gmp/tests/overloading_with_int_string.phpt new file mode 100644 index 0000000000000..252916ef9a531 --- /dev/null +++ b/ext/gmp/tests/overloading_with_int_string.phpt @@ -0,0 +1,117 @@ +--TEST-- +GMP operator overloading does support int strings +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> "2"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +object(GMP)#2 (1) { + ["num"]=> + string(2) "44" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "40" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "84" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "21" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#2 (1) { + ["num"]=> + string(4) "1764" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "42" +} +object(GMP)#2 (1) { + ["num"]=> + string(1) "2" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "40" +} +object(GMP)#2 (1) { + ["num"]=> + string(3) "168" +} +object(GMP)#2 (1) { + ["num"]=> + string(2) "10" +} diff --git a/ext/gmp/tests/overloading_with_non_numeric_string.phpt b/ext/gmp/tests/overloading_with_non_numeric_string.phpt new file mode 100644 index 0000000000000..2f6c2d072342c --- /dev/null +++ b/ext/gmp/tests/overloading_with_non_numeric_string.phpt @@ -0,0 +1,84 @@ +--TEST-- +GMP operator overloading does not support non-numeric strings +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> "string"); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string +ValueError: Number is not an integer string diff --git a/ext/gmp/tests/overloading_with_null.phpt b/ext/gmp/tests/overloading_with_null.phpt new file mode 100644 index 0000000000000..95f8153e4e7a5 --- /dev/null +++ b/ext/gmp/tests/overloading_with_null.phpt @@ -0,0 +1,84 @@ +--TEST-- +GMP operator overloading does not support null +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> null); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: Number must be of type GMP|string|int, null given +TypeError: Number must be of type GMP|string|int, null given +TypeError: Number must be of type GMP|string|int, null given +TypeError: Number must be of type GMP|string|int, null given +TypeError: Number must be of type GMP|string|int, null given +TypeError: Unsupported operand types: GMP ** null +TypeError: Number must be of type GMP|string|int, null given +TypeError: Number must be of type GMP|string|int, null given +TypeError: Number must be of type GMP|string|int, null given +TypeError: Unsupported operand types: GMP << null +TypeError: Unsupported operand types: GMP >> null diff --git a/ext/gmp/tests/overloading_with_object_not_stringable.phpt b/ext/gmp/tests/overloading_with_object_not_stringable.phpt new file mode 100644 index 0000000000000..c8fb924824dd4 --- /dev/null +++ b/ext/gmp/tests/overloading_with_object_not_stringable.phpt @@ -0,0 +1,85 @@ +--TEST-- +GMP operator overloading does not support non-stringable objects +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Unsupported operand types: GMP ** stdClass +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Number must be of type GMP|string|int, stdClass given +TypeError: Unsupported operand types: GMP << stdClass +TypeError: Unsupported operand types: GMP >> stdClass diff --git a/ext/gmp/tests/overloading_with_object_stringable.phpt b/ext/gmp/tests/overloading_with_object_stringable.phpt new file mode 100644 index 0000000000000..4425661b98924 --- /dev/null +++ b/ext/gmp/tests/overloading_with_object_stringable.phpt @@ -0,0 +1,91 @@ +--TEST-- +GMP operator overloading does not support stringable objects +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> $o); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECTF-- +TypeError: Number must be of type GMP|string|int, T given +TypeError: Number must be of type GMP|string|int, T given +TypeError: Number must be of type GMP|string|int, T given +TypeError: Number must be of type GMP|string|int, T given +TypeError: Number must be of type GMP|string|int, T given +TypeError: Unsupported operand types: GMP ** T +TypeError: Number must be of type GMP|string|int, T given +TypeError: Number must be of type GMP|string|int, T given +TypeError: Number must be of type GMP|string|int, T given +TypeError: Unsupported operand types: GMP << T +TypeError: Unsupported operand types: GMP >> T diff --git a/ext/gmp/tests/overloading_with_resource.phpt b/ext/gmp/tests/overloading_with_resource.phpt new file mode 100644 index 0000000000000..50b7af5d32746 --- /dev/null +++ b/ext/gmp/tests/overloading_with_resource.phpt @@ -0,0 +1,84 @@ +--TEST-- +GMP operator overloading does not support resources +--EXTENSIONS-- +gmp +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($num - STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num * STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num / STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num % STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num ** STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + var_dump($num | STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num & STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num ^ STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num << STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + var_dump($num >> STDERR); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Unsupported operand types: GMP ** resource +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Number must be of type GMP|string|int, resource given +TypeError: Unsupported operand types: GMP << resource +TypeError: Unsupported operand types: GMP >> resource diff --git a/ext/intl/tests/bug62070_3.phpt b/ext/intl/tests/bug62070_3.phpt index 08c1bbf45f8ba..60e0593acfd3d 100644 --- a/ext/intl/tests/bug62070_3.phpt +++ b/ext/intl/tests/bug62070_3.phpt @@ -4,6 +4,7 @@ Bug #62070: Collator::getSortKey() returns garbage intl --SKIPIF-- = 62.1'); ?> += 0) die('skip for ICU < 76.1'); ?> --FILE-- = 76.1'); ?> +--FILE-- + +--EXPECT-- +93AAG%01%09%01%DC%08 diff --git a/ext/intl/tests/collator_get_sort_key_variant7.phpt b/ext/intl/tests/collator_get_sort_key_variant7.phpt index 44be0bea3fd65..f342a413be5cf 100644 --- a/ext/intl/tests/collator_get_sort_key_variant7.phpt +++ b/ext/intl/tests/collator_get_sort_key_variant7.phpt @@ -4,6 +4,7 @@ collator_get_sort_key() icu >= 62.1 intl --SKIPIF-- = 62.1'); ?> += 0) die('skip for ICU < 76.1'); ?> --FILE-- = 62.1 +--EXTENSIONS-- +intl +--SKIPIF-- += 76.1'); ?> +--FILE-- + +--EXPECT-- +source: abc +key: 2b2d2f01070107 +source: abd +key: 2b2d3101070107 +source: aaa +key: 2b2b2b01070107 +source: аа +key: 62060601060106 +source: а +key: 620601050105 +source: z +key: 5d01050105 +source: +key: 0101 +source: 3 +key: 1801050105 +source: y +key: 5b01050105 +source: i +key: 3b01050105 +source: k +key: 3f01050105 +source: абг +key: 28060c1001070107 +source: абв +key: 28060c0e01070107 +source: жжж +key: 282c2c2c01070107 +source: эюя +key: 28eef0f401070107 +source: абг +key: 62060c1001070107 +source: абв +key: 62060c0e01070107 +source: жжж +key: 622c2c2c01070107 +source: эюя +key: 62eef0f401070107 diff --git a/ext/intl/tests/locale_get_display_name8.phpt b/ext/intl/tests/locale_get_display_name8.phpt index b6b855c6d8eca..e8c1ed958ac1c 100644 --- a/ext/intl/tests/locale_get_display_name8.phpt +++ b/ext/intl/tests/locale_get_display_name8.phpt @@ -317,14 +317,14 @@ disp_locale=fr : display_name=anglais #États-Unis, attribute=islamcal# disp_locale=de : display_name=Englisch #Vereinigte Staaten, attribute=islamcal# ----------------- locale='zh-CN-a-myExt-x-private' -disp_locale=en : display_name=Chinese #China, a=myext, Private-Use=private# -disp_locale=fr : display_name=chinois #Chine, a=myext, usage privé=private# -disp_locale=de : display_name=Chinesisch #China, a=myext, Privatnutzung=private# +disp_locale=en : display_name=Chinese #China(, A_MYEXT_X_PRIVATE)?, a=myext, Private-Use=private# +disp_locale=fr : display_name=chinois #Chine(, A_MYEXT_X_PRIVATE)?, a=myext, usage privé=private# +disp_locale=de : display_name=Chinesisch #China(, A_MYEXT_X_PRIVATE)?, a=myext, Privatnutzung=private# ----------------- locale='en-a-myExt-b-another' -disp_locale=en : display_name=English #a=myext, b=another# -disp_locale=fr : display_name=anglais #a=myext, b=another# -disp_locale=de : display_name=Englisch #a=myext, b=another# +disp_locale=en : display_name=English #(A_MYEXT_B_ANOTHER, )?a=myext, b=another# +disp_locale=fr : display_name=anglais #(A_MYEXT_B_ANOTHER, )?a=myext, b=another# +disp_locale=de : display_name=Englisch #(A_MYEXT_B_ANOTHER, )?a=myext, b=another# ----------------- locale='de-419-DE' disp_locale=en : display_name=German #Latin America, DE# @@ -337,7 +337,7 @@ disp_locale=fr : display_name=a #Allemagne# disp_locale=de : display_name=a #Deutschland# ----------------- locale='ar-a-aaa-b-bbb-a-ccc' -disp_locale=en : display_name=Arabic #a=aaa, b=bbb# -disp_locale=fr : display_name=arabe #a=aaa, b=bbb# -disp_locale=de : display_name=Arabisch #a=aaa, b=bbb# +disp_locale=en : display_name=Arabic #(A_AAA_B_BBB_A_CCC, )?a=aaa, b=bbb# +disp_locale=fr : display_name=arabe #(A_AAA_B_BBB_A_CCC, )?a=aaa, b=bbb# +disp_locale=de : display_name=Arabisch #(A_AAA_B_BBB_A_CCC, )?a=aaa, b=bbb# ----------------- diff --git a/ext/intl/tests/locale_get_display_variant2.phpt b/ext/intl/tests/locale_get_display_variant2.phpt index a743ed5ea3b85..e56154902dde9 100644 --- a/ext/intl/tests/locale_get_display_variant2.phpt +++ b/ext/intl/tests/locale_get_display_variant2.phpt @@ -248,14 +248,14 @@ disp_locale=fr : display_variant= disp_locale=de : display_variant= ----------------- locale='zh-CN-a-myExt-x-private' -disp_locale=en : display_variant= -disp_locale=fr : display_variant= -disp_locale=de : display_variant= +disp_locale=en : display_variant=(A_MYEXT_X_PRIVATE)? +disp_locale=fr : display_variant=(A_MYEXT_X_PRIVATE)? +disp_locale=de : display_variant=(A_MYEXT_X_PRIVATE)? ----------------- locale='en-a-myExt-b-another' -disp_locale=en : display_variant=(MYEXT_B_ANOTHER)? -disp_locale=fr : display_variant=(MYEXT_B_ANOTHER)? -disp_locale=de : display_variant=(MYEXT_B_ANOTHER)? +disp_locale=en : display_variant=((A_)?MYEXT_B_ANOTHER)? +disp_locale=fr : display_variant=((A_)?MYEXT_B_ANOTHER)? +disp_locale=de : display_variant=((A_)?MYEXT_B_ANOTHER)? ----------------- locale='de-419-DE' disp_locale=en : display_variant=DE @@ -268,7 +268,7 @@ disp_locale=fr : display_variant= disp_locale=de : display_variant= ----------------- locale='ar-a-aaa-b-bbb-a-ccc' -disp_locale=en : display_variant=(AAA_B_BBB_A_CCC)? -disp_locale=fr : display_variant=(AAA_B_BBB_A_CCC)? -disp_locale=de : display_variant=(AAA_B_BBB_A_CCC)? +disp_locale=en : display_variant=((A_)?AAA_B_BBB_A_CCC)? +disp_locale=fr : display_variant=((A_)?AAA_B_BBB_A_CCC)? +disp_locale=de : display_variant=((A_)?AAA_B_BBB_A_CCC)? ----------------- diff --git a/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt b/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt index aeb9b16899157..60e6f73e37970 100644 --- a/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt +++ b/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt @@ -4,6 +4,7 @@ IntlTimeZone::getIDForWindowsID basic test intl --SKIPIF-- = 58.1'); ?> += 0) die('skip for ICU <= 76.1'); ?> --FILE-- = 76.1'); ?> +--FILE-- + array(NULL), + 'India Standard Time' => array(NULL), + 'Pacific Standard Time' => array('001', 'CA', 'MX', 'US', 'ZZ'), + 'Romance Standard Time' => array('001', 'BE', 'DK', 'ES', 'FR'), +); + +foreach ($tzs as $tz => $regions) { + echo "** $tz\n"; + foreach ($regions as $region) { + var_dump(IntlTimeZone::getIDForWindowsID($tz, $region)); + if (intl_get_error_code() != U_ZERO_ERROR) { + echo "Error: ", intl_get_error_message(), "\n"; + } + } +} +?> +--EXPECTF-- +** Gnomeregan +bool(false) +Error: %snknown windows timezone: U_ILLEGAL_ARGUMENT_ERROR +** India Standard Time +string(13) "Asia/Calcutta" +** Pacific Standard Time +string(19) "America/Los_Angeles" +string(17) "America/Vancouver" +string(19) "America/Los_Angeles" +string(19) "America/Los_Angeles" +string(19) "America/Los_Angeles" +** Romance Standard Time +string(12) "Europe/Paris" +string(15) "Europe/Brussels" +string(17) "Europe/Copenhagen" +string(13) "Europe/Madrid" +string(12) "Europe/Paris" diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index e33201f10d154..7adc3753a0342 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -3701,13 +3701,23 @@ static zend_string* php_ldap_do_escape(const bool *map, const char *value, size_ zend_string *ret; for (i = 0; i < valuelen; i++) { - len += (map[(unsigned char) value[i]]) ? 3 : 1; + size_t addend = (map[(unsigned char) value[i]]) ? 3 : 1; + if (len > ZSTR_MAX_LEN - addend) { + return NULL; + } + len += addend; } /* Per RFC 4514, a leading and trailing space must be escaped */ if ((flags & PHP_LDAP_ESCAPE_DN) && (value[0] == ' ')) { + if (len > ZSTR_MAX_LEN - 2) { + return NULL; + } len += 2; } if ((flags & PHP_LDAP_ESCAPE_DN) && ((valuelen > 1) && (value[valuelen - 1] == ' '))) { + if (len > ZSTR_MAX_LEN - 2) { + return NULL; + } len += 2; } @@ -3774,7 +3784,13 @@ PHP_FUNCTION(ldap_escape) php_ldap_escape_map_set_chars(map, ignores, ignoreslen, 0); } - RETURN_NEW_STR(php_ldap_do_escape(map, value, valuelen, flags)); + zend_string *result = php_ldap_do_escape(map, value, valuelen, flags); + if (UNEXPECTED(!result)) { + zend_argument_value_error(1, "is too long"); + RETURN_THROWS(); + } + + RETURN_NEW_STR(result); } #ifdef STR_TRANSLATION diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt new file mode 100644 index 0000000000000..8e2c4fb160de3 --- /dev/null +++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt @@ -0,0 +1,28 @@ +--TEST-- +GHSA-g665-fm4p-vhff (OOB access in ldap_escape) +--EXTENSIONS-- +ldap +--INI-- +memory_limit=-1 +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +try { + ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +ldap_escape(): Argument #1 ($value) is too long +ldap_escape(): Argument #1 ($value) is too long diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt new file mode 100644 index 0000000000000..a69597084be6c --- /dev/null +++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt @@ -0,0 +1,29 @@ +--TEST-- +GHSA-g665-fm4p-vhff (OOB access in ldap_escape) +--EXTENSIONS-- +ldap +--INI-- +memory_limit=-1 +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +// would allocate a string of length 2 +try { + ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +ldap_escape(): Argument #1 ($value) is too long +ldap_escape(): Argument #1 ($value) is too long diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 70125dc909748..7efefb77e570a 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -2172,6 +2172,16 @@ PHP_FUNCTION(mb_substr) Z_PARAM_STR_OR_NULL(encoding) ZEND_PARSE_PARAMETERS_END(); + if (from == ZEND_LONG_MIN) { + zend_argument_value_error(2, "must be between " ZEND_LONG_FMT " and " ZEND_LONG_FMT, (ZEND_LONG_MIN + 1), ZEND_LONG_MAX); + RETURN_THROWS(); + } + + if (!len_is_null && len == ZEND_LONG_MIN) { + zend_argument_value_error(3, "must be between " ZEND_LONG_FMT " and " ZEND_LONG_FMT, (ZEND_LONG_MIN + 1), ZEND_LONG_MAX); + RETURN_THROWS(); + } + string.encoding = php_mb_get_encoding(encoding, 4); if (!string.encoding) { RETURN_THROWS(); diff --git a/ext/mbstring/tests/gh16360.phpt b/ext/mbstring/tests/gh16360.phpt new file mode 100644 index 0000000000000..4e2e8fb4bfc63 --- /dev/null +++ b/ext/mbstring/tests/gh16360.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-16320 mb_substr overflow from negative length +--EXTENSIONS-- +mbstring +--FILE-- +getMessage() . PHP_EOL; +} +try { + mb_substr("abcd", 0, PHP_INT_MIN, "UTF-8"); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} +var_dump(mb_substr("abcd", PHP_INT_MAX, PHP_INT_MAX, "UTF-8")); +?> +--EXPECTF-- +mb_substr(): Argument #2 ($start) must be between %s and %s +mb_substr(): Argument #3 ($length) must be between %s and %s +string(0) "" + diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc new file mode 100644 index 0000000000000..b02fabc584c5d --- /dev/null +++ b/ext/mysqli/tests/fake_server.inc @@ -0,0 +1,856 @@ + [ + 'type' => '03', + 'charset' => '3f00', + 'length' => '0b000000', + 'flags' => '0110', + 'decimal' => '00', + 'query_data_packet_length' => '080000', + 'query_data_value' => '023134', + 'stmt_data_packet_length' => '0b0000', + 'stmt_data_value' => '0e000000' + ], + 'fltval' => [ + 'type' => '04', + 'charset' => '3f00', + 'length' => '0c000000', + 'flags' => '0110', + 'decimal' => '1f', + 'query_data_packet_length' => '090000', + 'query_data_value' => '03322e33', + 'stmt_data_packet_length' => '0b0000', + 'stmt_data_value' => '33331340', + ], + 'dblval' => [ + 'type' => '05', + 'charset' => '3f00', + 'length' => '16000000', + 'flags' => '0110', + 'decimal' => '1f', + 'query_data_packet_length' => '090000', + 'query_data_value' => '03312e32', + 'stmt_data_packet_length' => '0f0000', + 'stmt_data_value' => '333333333333f33f' + ], + 'datval' => [ + 'type' => '0a', + 'charset' => '3f00', + 'length' => '0a000000', + 'flags' => '8110', + 'decimal' => '00', + 'query_data_packet_length' => '100000', + 'query_data_value' => '0a323031342d31322d3135', + 'stmt_data_packet_length' => '0c0000', + 'stmt_data_value' => '04de070c0f' + ], + 'timval' => [ + 'type' => '0b', + 'charset' => '3f00', + 'length' => '0a000000', + 'flags' => '8110', + 'decimal' => '00', + 'query_data_packet_length' => '0e0000', + 'query_data_value' => '0831333a30303a3032', + 'stmt_data_packet_length' => '100000', + 'stmt_data_value' => '080000000000150801' + ], + 'dtival' => [ + 'type' => '0c', + 'charset' => '3f00', + 'length' => '13000000', + 'flags' => '8110', + 'decimal' => '00', + 'query_data_packet_length' => '190000', + 'query_data_value' => '13323031342d31322d31362031333a30303a3031', + 'stmt_data_packet_length' => '0f0000', + 'stmt_data_value' => '07de070c100d0001' + ], + 'bitval' => [ + 'type' => '10', + 'charset' => '3f00', + 'length' => '40000000', + 'flags' => '2110', + 'decimal' => '00', + 'query_data_packet_length' => '0e0000', + 'query_data_value' => '080808080808080808', + 'stmt_data_packet_length' => '100000', + 'stmt_data_value' => '080808080808080808' + ], + 'strval' => [ + 'type' => 'fd', + 'charset' => 'e000', + 'length' => 'c8000000', + 'flags' => '0110', + 'decimal' => '00', + 'query_data_packet_length' => '0a0000', + 'query_data_value' => '0474657374', + 'stmt_data_packet_length' => '0c0000', + 'stmt_data_value' => '0474657374' + ], + ]; +} + +function my_mysqli_data_field(string $field): array +{ + $fields = my_mysqli_data_fields(); + if (!isset($fields[$field])) { + throw new Exception("Unknown field $field"); + } + return $fields[$field]; +} + + + +class my_mysqli_fake_packet_item +{ + public function __construct(public string|null $name, public string $value, public bool $is_hex = true) + { + } +} + +class my_mysqli_fake_packet +{ + private array $data = array(); + + public function __get(string $name) + { + foreach ($this->data as $item) { + if ($item->name === $name) { + return $item->value; + } + } + return null; + } + + public function __set(string $name, string|my_mysqli_fake_packet_item $value) + { + if ($value instanceof my_mysqli_fake_packet_item) { + if ($value->name === null) { + $value->name = $name; + } + } else { + $value = new my_mysqli_fake_packet_item($name, $value, true); + } + + for ($i = 0; $i < count($this->data); $i++) { + if ($this->data[$i]->name === $name) { + $this->data[$i] = $value; + return; + } + } + + $this->data[] = $value; + } + + public function to_bytes(): string + { + $bytes = ''; + foreach ($this->data as $item) { + $bytes .= $item->is_hex ? hex2bin($item->value) : $item->value; + } + return $bytes; + } +} + +class my_mysqli_fake_packet_generator +{ + public static function create_packet_item(int|string $value, bool $is_hex = false, string $format = 'v'): my_mysqli_fake_packet_item + { + if (is_string($value)) { + $packed_value = $value; + } else { + $packed_value = pack($format, $value); + } + return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex); + } + + public function server_ok(): my_mysqli_fake_packet + { + $packet = new my_mysqli_fake_packet(); + $packet->packet_length = "070000"; + $packet->packet_number = "02"; + $packet->header = "00"; // OK + $packet->affected_rows = "00"; + $packet->last_insert_id = "00"; + $packet->server_status = "0200"; + $packet->warning_count = "0000"; + return $packet; + } + + public function server_greetings(): my_mysqli_fake_packet + { + $packet = new my_mysqli_fake_packet(); + $packet->packet_length = "580000"; + $packet->packet_number = "00"; + $packet->proto_version = "0a"; + $packet->version = self::create_packet_item('5.5.5-10.5.18-MariaDB' . chr(0)); + $packet->thread_id = "03000000"; + $packet->salt = "473e3f6047257c67"; + $packet->filler = "00"; + $packet->server_capabilities = self::create_packet_item(0b1111011111111110); + $packet->server_character_set = "08"; + $packet->server_status = self::create_packet_item(0b000000000000010); + $packet->extended_server_capabilities = self::create_packet_item(0b1000000111111111); + $packet->auth_plugin = "15"; + $packet->unused = "000000000000"; + $packet->mariadb_extended_server_capabilities = self::create_packet_item(0b1111, false, 'V'); + $packet->mariadb_extended_server_capabilities_salt = "6c6b55463f49335f686c643100"; + $packet->mariadb_extended_server_capabilities_auth_plugin = self::create_packet_item('mysql_native_password'); + + return $packet; + } + + public function server_tabular_query_response(): array + { + $qr1 = new my_mysqli_fake_packet(); + $qr1->packet_length = "010000"; + $qr1->packet_number = "01"; + $qr1->field_count = "01"; + + $qr2 = new my_mysqli_fake_packet(); + $qr2->packet_length = "190000"; + $qr2->packet_number = "02"; + $qr2->catalog_length_plus_name = "0164"; + $qr2->db_length_plus_name = "0164"; + $qr2->table_length_plus_name = "0164"; + $qr2->original_t = "0164"; + $qr2->name_length_plus_name = "0164"; + $qr2->original_n = "0164"; + $qr2->canary = "0c"; + $qr2->charset = "3f00"; + $qr2->length = "0b000000"; + $qr2->type = "03"; + $qr2->flags = "0350"; + $qr2->decimals = "000000"; + + $qr3 = new my_mysqli_fake_packet(); + $qr3->full = "05000003fe00002200"; + + $qr4 = new my_mysqli_fake_packet(); + $qr4->full = "0400000401350174"; + + $qr5 = new my_mysqli_fake_packet(); + $qr5->full = "05000005fe00002200"; + + return [$qr1, $qr2, $qr3, $qr4, $qr5]; + } + + public function server_upsert_query_response(): array + { + $qr1 = new my_mysqli_fake_packet(); + $qr1->packet_length = "010000"; + $qr1->packet_number = "01"; + $qr1->field_count = "00"; // UPSERT + $qr1->affected_rows = "00"; + $qr1->affected_rows = "00"; + $qr1->last_insert_id = "00"; + $qr1->server_status = "0000"; + $qr1->warning_count = "0000"; + $qr1->len = "01"; + $qr1->filename = "65"; + $qr1->packet_length = sprintf("%02x0000", strlen($qr1->to_bytes())-4); + + return [$qr1]; + } + + public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet + { + $pr1 = new my_mysqli_fake_packet(); + $pr1->packet_length = "0c0000"; + $pr1->packet_number = "01"; + $pr1->response_code = '00'; // OK + $pr1->statement_id = '01000000'; + $pr1->num_fields = $num_field; + $pr1->num_params = '0000'; + $pr1->filler = '00'; + $pr1->warnings = '0000'; + + return $pr1; + } + + public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet + { + $pr3 = new my_mysqli_fake_packet(); + $pr3->packet_length = "050000"; + $pr3->packet_number = $packer_number; + $pr3->packet_type = 'fe'; // EOF + $pr3->warnings = '0000'; + $pr3->server_status = '0200'; + + return $pr3; + } + + public function server_stmt_prepare_items_response(): array + { + $pr1 = $this->server_stmt_prepare_response_start('0100'); + + $pr2 = new my_mysqli_fake_packet(); + $pr2->packet_length = "300000"; + $pr2->packet_number = "02"; + $pr2->catalogue_len = '03'; + $pr2->catalogue = '646566'; // def + $pr2->db_len = '08'; + $pr2->db = '7068705f74657374'; // php_test + $pr2->table_len = '05'; + $pr2->table = '6974656d73'; // items + $pr2->orig_table_len = '05'; + $pr2->orig_table = '6974656d73'; // items + $pr2->name_len = '04'; + $pr2->name = '6974656d'; + $pr2->orig_name_len = '04'; + $pr2->orig_name = '6974656d'; + $pr2->something = '0c'; + $pr2->charset = 'e000'; + $pr2->length = 'c8000000'; + $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING + $pr2->flags = '0110'; + $pr2->decimal = '00'; + $pr2->padding = '0000'; + + $pr3 = $this->server_stmt_prepare_response_end('03'); + + return [$pr1, $pr2, $pr3]; + } + + public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet + { + if (strlen($field_name) != 6) { + throw new Exception("Invalid field length - only 6 is allowed"); + } + + $field = my_mysqli_data_field($field_name); + + $pr = new my_mysqli_fake_packet(); + $pr->packet_length = "320000"; + $pr->packet_number = $packet_number; + $pr->catalogue_len = '03'; + $pr->catalogue = bin2hex('def'); + $pr->db_len = '08'; + $pr->db = bin2hex('php_test'); + $pr->table_len = '04'; + $pr->table = bin2hex('data'); + $pr->orig_table_len = '04'; + $pr->orig_table = bin2hex('data'); + $pr->name_len = '06'; + $pr->name = bin2hex($field_name); + $pr->orig_name_len = '06'; + $pr->orig_name = bin2hex($field_name); + $pr->something = '0c'; + $pr->charset = $field['charset']; + $pr->length = $field['length']; + $pr->field_type = $field['type']; + $pr->flags = $field['flags']; + $pr->decimal = $field['decimal']; + $pr->padding = '0000'; + + return $pr; + } + + public function server_stmt_prepare_data_response(string $field_name): array + { + $pr1 = $this->server_stmt_prepare_response_start('0200'); + + $pr2 = $this->server_stmt_prepare_data_response_field('02', 'strval'); + $pr3 = $this->server_stmt_prepare_data_response_field('03', $field_name); + + $pr4 = $this->server_stmt_prepare_response_end('04'); + + return [$pr1, $pr2, $pr3, $pr4]; + } + + public function server_stmt_execute_items_response(): array + { + $pr1 = new my_mysqli_fake_packet(); + $pr1->packet_length = "010000"; + $pr1->packet_number = "01"; + $pr1->num_fields = '01'; + + $pr2 = new my_mysqli_fake_packet(); + $pr2->packet_length = "300000"; + $pr2->packet_number = "02"; + $pr2->catalogue_len = '03'; + $pr2->catalogue = '646566'; // def + $pr2->db_len = '08'; + $pr2->db = '7068705f74657374'; // php_test + $pr2->table_len = '05'; + $pr2->table = '6974656d73'; // items + $pr2->orig_table_len = '05'; + $pr2->orig_table = '6974656d73'; // items + $pr2->name_len = '04'; + $pr2->name = '6974656d'; + $pr2->orig_name_len = '04'; + $pr2->orig_name = '6974656d'; + $pr2->something = '0c'; + $pr2->charset = 'e000'; + $pr2->length = 'c8000000'; + $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING + $pr2->flags = '0110'; + $pr2->decimal = '00'; + $pr2->padding = '0000'; + + $pr3 = new my_mysqli_fake_packet(); + $pr3->packet_length = "050000"; + $pr3->packet_number = "03"; + $pr3->packet_type = 'fe'; // EOF + $pr3->warnings = '0000'; + $pr3->server_status = '2200'; + + $pr4 = new my_mysqli_fake_packet(); + $pr4->packet_length = "070000"; + $pr4->packet_number = "04"; + $pr4->packet_type = '00'; // OK + $pr4->affected_rows = '00'; + $pr4->row_data_len = '04'; + $pr4->row_data = '74657374'; // item + + $pr5 = new my_mysqli_fake_packet(); + $pr5->full = '05000005fe00002200'; + + return [$pr1, $pr2, $pr3, $pr4, $pr5]; + } + + private function server_execute_data_response_start(string $field_name): array + { + $pr1 = new my_mysqli_fake_packet(); + $pr1->packet_length = "010000"; + $pr1->packet_number = "01"; + $pr1->num_fields = '02'; + + $pr2 = new my_mysqli_fake_packet(); + $pr2->packet_length = "320000"; + $pr2->packet_number = "02"; + $pr2->catalogue_len = '03'; + $pr2->catalogue = '646566'; // def + $pr2->db_len = '08'; + $pr2->db = '7068705f74657374'; // php_test + $pr2->table_len = '04'; + $pr2->table = bin2hex('data'); + $pr2->orig_table_len = '04'; + $pr2->orig_table = bin2hex('data'); + $pr2->name_len = '06'; + $pr2->name = bin2hex('strval'); + $pr2->orig_name_len = '06'; + $pr2->orig_name = bin2hex('strval'); + $pr2->something = '0c'; + $pr2->charset = 'e000'; + $pr2->length = 'c8000000'; + $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING + $pr2->flags = '0110'; + $pr2->decimal = '00'; + $pr2->padding = '0000'; + + $field = my_mysqli_data_field($field_name); + + $pr3 = new my_mysqli_fake_packet(); + $pr3->packet_length = "320000"; + $pr3->packet_number = "03"; + $pr3->catalogue_len = '03'; + $pr3->catalogue = '646566'; // def + $pr3->db_len = '08'; + $pr3->db = '7068705f74657374'; // php_test + $pr3->table_len = '04'; + $pr3->table = bin2hex('data'); + $pr3->orig_table_len = '04'; + $pr3->orig_table = bin2hex('data'); + $pr3->name_len = '06'; + $pr3->name = bin2hex($field_name); + $pr3->orig_name_len = '06'; + $pr3->orig_name = bin2hex($field_name); + $pr3->something = '0c'; + $pr3->charset = $field['charset']; + $pr3->length = $field['length']; + $pr3->field_type = $field['type']; + $pr3->flags = $field['flags']; + $pr3->decimal = $field['decimal']; + $pr3->padding = '0000'; + + $pr4 = new my_mysqli_fake_packet(); + $pr4->packet_length = "050000"; + $pr4->packet_number = "04"; + $pr4->packet_type = 'fe'; // EOF + $pr4->warnings = '0000'; + $pr4->server_status = '2200'; + + return [$field, $pr1, $pr2, $pr3, $pr4]; + } + + private function server_execute_data_response_end(): my_mysqli_fake_packet + { + $pr6 = new my_mysqli_fake_packet(); + $pr6->packet_length = '050000'; + $pr6->packet_number = "06"; + $pr6->packet_type = 'fe'; // EOF + $pr6->warnings = '0000'; + $pr6->server_status = '2200'; + + return $pr6; + } + + public function server_stmt_execute_data_response(string $field_name): array + { + [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); + + $pr5 = new my_mysqli_fake_packet(); + $pr5->packet_length = $field['stmt_data_packet_length']; + $pr5->packet_number = "05"; + $pr5->packet_type = '00'; // OK + $pr5->affected_rows = '00'; + $pr5->row_field1_len = '04'; + $pr5->row_field1_data = '74657374'; // test + $pr5->row_field2 = $field['stmt_data_value']; + + return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; + } + + public function server_query_execute_data_response(string $field_name): array + { + [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); + + $pr5 = new my_mysqli_fake_packet(); + $pr5->packet_length = $field['query_data_packet_length']; + $pr5->packet_number = "05"; + $pr5->row_field1_len = '04'; + $pr5->row_field1_data = '74657374'; // test + $pr5->row_field2 = $field['query_data_value']; + + return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; + } +} + +class my_mysqli_fake_server_conn +{ + private $conn; + public $packet_generator; + + public function __construct($socket) + { + $this->packet_generator = new my_mysqli_fake_packet_generator(); + $this->conn = stream_socket_accept($socket); + if ($this->conn) { + fprintf(STDERR, "[*] Connection established\n"); + } else { + fprintf(STDERR, "[*] Failed to establish connection\n"); + } + } + + public function packets_to_bytes(array $packets): string + { + return implode('', array_map(fn($s) => $s->to_bytes(), $packets)); + } + + public function send($payload, $message = null): void + { + if ($message) { + fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload)); + } + fwrite($this->conn, $payload); + } + + public function read($bytes_len = 1024) + { + // wait 10ms to fill the buffer + usleep(10000); + $data = fread($this->conn, $bytes_len); + if ($data) { + fprintf(STDERR, "[*] Received: %s\n", bin2hex($data)); + } + } + + public function close() + { + fclose($this->conn); + } + + public function send_server_greetings() + { + $this->send($this->packet_generator->server_greetings()->to_bytes(), "Server Greeting"); + } + + public function send_server_ok() + { + $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK"); + } + + public function send_server_tabular_query_response(): void + { + $packets = $this->packet_generator->server_tabular_query_response(); + $this->send($this->packets_to_bytes($packets), "Tabular response"); + } + + public function send_server_stmt_prepare_items_response(): void + { + $packets = $this->packet_generator->server_stmt_prepare_items_response(); + $this->send($this->packets_to_bytes($packets), "Stmt prepare items"); + } + + + public function send_server_stmt_prepare_data_response(string $field_name): void + { + $packets = $this->packet_generator->server_stmt_prepare_data_response($field_name); + $this->send($this->packets_to_bytes($packets), "Stmt prepare data $field_name"); + } + + public function send_server_stmt_execute_items_response(): void + { + $packets = $this->packet_generator->server_stmt_execute_items_response(); + $this->send($this->packets_to_bytes($packets), "Stmt execute items"); + } + + public function send_server_stmt_execute_data_response(string $field_name): void + { + $packets = $this->packet_generator->server_stmt_execute_data_response($field_name); + $this->send($this->packets_to_bytes($packets), "Stmt execute data $field_name"); + } + + public function send_server_query_execute_data_response(string $field_name): void + { + $packets = $this->packet_generator->server_query_execute_data_response($field_name); + $this->send($this->packets_to_bytes($packets), "Query execute data $field_name"); + } +} + +class my_mysqli_fake_server_process +{ + public function __construct(private $process, private array $pipes) {} + + public function terminate(bool $wait = false) + { + if ($wait) { + $this->wait(); + } + proc_terminate($this->process); + } + + public function wait() + { + echo fgets($this->pipes[1]); + } +} + +function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_tabular_query_response(); + + // Length of the packet is modified to include the next added data + $rh[1]->packet_length = "1e0000"; + + // We add a length field encoded on 4 bytes which evaluates to 65536. If the process crashes because + // the heap has been overread, lower this value. + $rh[1]->extra_def_size = "fd000001"; # 65536 + + // Filler + $rh[1]->extra_def_data = "aa"; + + $trrh = $conn->packets_to_bytes($rh); + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]"); + $conn->read(65536); +} + +function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_upsert_query_response(); + + // Set extra length to overread + $rh[0]->len = "fa"; + + $trrh = $conn->packets_to_bytes($rh); + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]"); + $conn->read(65536); +} + +function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void +{ + $p = $conn->packet_generator->server_ok(); + $p->packet_length = "090000"; + $p->message_len = "fcff"; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send($p->to_bytes(), "Malicious OK Auth Response [Extract heap through buffer over-read]"); + $conn->read(); +} + +function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_stmt_execute_items_response(); + + // Set extra length to overread + $rh[3]->row_data_len = "fa"; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send_server_stmt_prepare_items_response(); + $conn->read(); + $conn->send($conn->packets_to_bytes($rh), "Malicious Stmt Response for items [Extract heap through buffer over-read]"); + $conn->read(65536); +} + +function my_mysqli_test_stmt_response_row_over_read_two_fields( + my_mysqli_fake_server_conn $conn, + string $field_name, + string $row_field1_len = '06' +): void { + $rh = $conn->packet_generator->server_stmt_execute_data_response($field_name); + + // Set extra length to overread by two bytes + $rh[4]->row_field1_len = $row_field1_len; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send_server_stmt_prepare_data_response($field_name); + $conn->read(); + $conn->send( + $conn->packets_to_bytes($rh), + "Malicious Stmt Response for data $field_name [Extract heap through buffer over-read]" + ); + $conn->read(65536); +} + +function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'intval'); +} + +function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'fltval'); +} + +function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dblval'); +} + +function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'datval'); +} + +function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'timval', '0c'); +} + +function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dtival'); +} + +function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'strval', '09'); +} + +function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'bitval'); +} + +function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void +{ + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $field_names = array_keys(my_mysqli_data_fields()); + foreach ($field_names as $field_name) { + $conn->send_server_stmt_prepare_data_response($field_name); + $conn->read(65536); + $conn->send_server_stmt_execute_data_response($field_name); + $conn->read(65536); + } +} + +function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_query_execute_data_response('strval'); + + // Set extra length to overread by two bytes + $rh[4]->row_field2 = 'fefefefefe'; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send($conn->packets_to_bytes($rh), "Malicious Query Response for data strval field [length overflow]"); + $conn->read(65536); +} + +function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void +{ + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $field_names = array_keys(my_mysqli_data_fields()); + foreach ($field_names as $field_name) { + $conn->send_server_query_execute_data_response($field_name); + $conn->read(); + } +} + +function run_fake_server(string $test_function, $port = 33305): void +{ + $address = '127.0.0.1'; + + $socket = @stream_socket_server("tcp://$address:$port", $errno, $errstr); + if (!$socket) { + die("Failed to create socket: $errstr ($errno)\n"); + } + echo "[*] Server started\n"; + + try { + $conn = new my_mysqli_fake_server_conn($socket); + $test_function_name = 'my_mysqli_test_' . $test_function; + call_user_func($test_function_name, $conn); + $conn->close(); + } catch (Exception $e) { + fprintf(STDERR, "[!] Exception: " . $e->getMessage() . "\n"); + } + + fclose($socket); + + echo "[*] Server finished\n"; +} + + +function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process +{ + $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port]; + + $descriptorspec = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + 2 => STDERR, + ); + + $process = proc_open($command, $descriptorspec, $pipes); + + if (is_resource($process)) { + return new my_mysqli_fake_server_process($process, $pipes); + } else { + throw new Exception("Failed to start server process"); + } +} + +if (isset($argv) && $argc > 2 && $argv[1] == 'mysqli_fake_server') { + run_fake_server($argv[2], $argv[3] ?? '33305'); +} diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt new file mode 100644 index 0000000000000..db54a6c0177a1 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt @@ -0,0 +1,38 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - auth message buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +try { + $conn = new mysqli( $servername, $username, $password, "", $port ); + $info = mysqli_info($conn); + var_dump($info); +} catch (Exception $e) { + echo $e->getMessage() . PHP_EOL; +} + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff + +Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d +Unknown error while trying to connect via tcp://127.0.0.1:50001 +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt new file mode 100644 index 0000000000000..77f2232eca687 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt @@ -0,0 +1,47 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - tabular default) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Running query on the fake server...\n"; + +$result = $conn->query("SELECT * from users"); + +if ($result) { + $all_fields = $result->fetch_fields(); + var_dump($result->fetch_all(MYSQLI_ASSOC)); + var_dump(get_object_vars($all_fields[0])["def"]); +} + +$conn->close(); + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Running query on the fake server... +[*] Received: 140000000353454c454354202a2066726f6d207573657273 +[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 01000001011e0000020164016401640164016401640c3f000b000000030350000000fd000001aa05000003fe00002200040000040135017405000005fe00002200 + +Warning: mysqli::query(): Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%d) in %s on line %d +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt new file mode 100644 index 0000000000000..0b4db8ccece95 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt @@ -0,0 +1,43 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - upsert filename buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); +echo "[*] Running query on the fake server...\n"; + +$result = $conn->query("SELECT * from users"); +$info = mysqli_info($conn); + +var_dump($info); + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Running query on the fake server... +[*] Received: 140000000353454c454354202a2066726f6d207573657273 +[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 0900000100000000000000fa65 + +Warning: mysqli::query(): RSET_HEADER packet additional data length is past 249 bytes the packet size in %s on line %d + +Warning: mysqli::query(): Error reading result set's header in %s on line %d +NULL +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt new file mode 100644 index 0000000000000..f141a79bdaa85 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt @@ -0,0 +1,48 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Query the fake server...\n"; +$sql = "SELECT strval, strval FROM data"; + +$result = $conn->query($sql); + +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row['strval']); + } +} +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Query the fake server... +[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Malicious Query Response for data strval field [length overflow]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374fefefefefe05000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after end of packet in %s on line %d +[*] Received: 0100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt new file mode 100644 index 0000000000000..e43518217eb63 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row bit buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT bitval, timval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["bitval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542062697476616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data bitval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000067465737408080808080808080805000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt new file mode 100644 index 0000000000000..76158e940d09d --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row date buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, datval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["datval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data datval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000067465737404de070c0f05000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt new file mode 100644 index 0000000000000..f53d5b83bd432 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row datetime buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, dtival FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["dtival"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data dtival [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000067465737407de070c100d000105000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt new file mode 100644 index 0000000000000..03c9b045d7375 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row double buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, dblval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["dblval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data dblval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000674657374333333333333f33f05000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt new file mode 100644 index 0000000000000..b1ec9aa51eca1 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, fltval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["fltval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data fltval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000006746573743333134005000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt new file mode 100644 index 0000000000000..426d9ea7b3f9b --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, intval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["intval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data intval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000006746573740e00000005000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt new file mode 100644 index 0000000000000..6db6952d42a15 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, strval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["strval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data strval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000974657374047465737405000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. No packet space left for the field in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt new file mode 100644 index 0000000000000..55bad4cc544aa --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row string buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT item FROM items"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["item"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 170000001653454c454354206974656d2046524f4d206974656d73 +[*] Sending - Stmt prepare items: 0c0000010001000000010000000000003000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for items [Extract heap through buffer over-read]: 01000001013000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00002200070000040000fa7465737405000005fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt new file mode 100644 index 0000000000000..06918c375f31a --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row time buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, timval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["timval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data timval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022001000000500000c7465737408000000000015080105000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/protocol_query_row_fetch_data.phpt b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt new file mode 100644 index 0000000000000..524fe5e587c63 --- /dev/null +++ b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt @@ -0,0 +1,74 @@ +--TEST-- +MySQL protocol - statement row data fetch) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +function my_query($conn, $field) +{ + $sql = "SELECT strval, $field FROM data"; + + $result = $conn->query($sql); + + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row[$field]); + } + } +} + +foreach (my_mysqli_data_fields() as $field_name => $field) { + my_query($conn, $field_name); +} + +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECT-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Received: 200000000353454c4543542073747276616c2c20696e7476616c2046524f4d2064617461 +[*] Sending - Query execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe0000220008000005047465737402313405000006fe00002200 +string(2) "14" +[*] Received: 200000000353454c4543542073747276616c2c20666c7476616c2046524f4d2064617461 +[*] Sending - Query execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe0000220009000005047465737403322e3305000006fe00002200 +string(3) "2.3" +[*] Received: 200000000353454c4543542073747276616c2c2064626c76616c2046524f4d2064617461 +[*] Sending - Query execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe0000220009000005047465737403312e3205000006fe00002200 +string(3) "1.2" +[*] Received: 200000000353454c4543542073747276616c2c2064617476616c2046524f4d2064617461 +[*] Sending - Query execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022001000000504746573740a323031342d31322d313505000006fe00002200 +string(10) "2014-12-15" +[*] Received: 200000000353454c4543542073747276616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Query execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022000e00000504746573740831333a30303a303205000006fe00002200 +string(8) "13:00:02" +[*] Received: 200000000353454c4543542073747276616c2c2064746976616c2046524f4d2064617461 +[*] Sending - Query execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe0000220019000005047465737413323031342d31322d31362031333a30303a303105000006fe00002200 +string(19) "2014-12-16 13:00:01" +[*] Received: 200000000353454c4543542073747276616c2c2062697476616c2046524f4d2064617461 +[*] Sending - Query execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe000022000e000005047465737408080808080808080805000006fe00002200 +string(18) "578721382704613384" +[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Query execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374047465737405000006fe00002200 +string(4) "test" +[*] Received: 0100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt new file mode 100644 index 0000000000000..af16a9eb2d05f --- /dev/null +++ b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt @@ -0,0 +1,91 @@ +--TEST-- +MySQL protocol - statement row data fetch) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +function my_query($conn, $field) +{ + $stmt = $conn->prepare("SELECT strval, $field FROM data"); + + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row[$field]); + } + } +} + +foreach (my_mysqli_data_fields() as $field_name => $field) { + my_query($conn, $field_name); +} + +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000004746573740e00000005000006fe00002200 +int(14) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000004746573743333134005000006fe00002200 +float(2.3) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000474657374333333333333f33f05000006fe00002200 +float(1.2) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000047465737404de070c0f05000006fe00002200 +string(10) "2014-12-15" +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00002200100000050000047465737408000000000015080105000006fe00002200 +string(8) "21:08:01" +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000047465737407de070c100d000105000006fe00002200 +string(19) "2014-12-16 13:00:01" +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2062697476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000047465737408080808080808080805000006fe00002200 +%s578721382704613384%s +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000474657374047465737405000006fe00002200 +string(4) "test" +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index 3b38d86273b68..796516b310281 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -50,11 +50,46 @@ struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1]; #define MYSQLND_PS_SKIP_RESULT_W_LEN -1 #define MYSQLND_PS_SKIP_RESULT_STR -2 +static inline void ps_fetch_over_read_error(const zend_uchar ** row) +{ + php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after the end of packet"); + *row = NULL; +} + +static inline bool ps_fetch_is_packet_over_read_with_variable_length(const unsigned int pack_len, + const zend_uchar ** row, const zend_uchar *p, unsigned int length) +{ + if (pack_len == 0) { + return false; + } + size_t length_len = *row - p; + if (length_len > pack_len || length > pack_len - length_len) { + ps_fetch_over_read_error(row); + return true; + } + return false; +} + +static inline bool ps_fetch_is_packet_over_read_with_static_length(const unsigned int pack_len, + const zend_uchar ** row, unsigned int length) +{ + if (pack_len > 0 && length > pack_len) { + ps_fetch_over_read_error(row); + return true; + } + return false; +} + + /* {{{ ps_fetch_from_1_to_8_bytes */ void ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row, unsigned int byte_count) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, byte_count))) { + return; + } + bool is_bit = field->type == MYSQL_TYPE_BIT; DBG_ENTER("ps_fetch_from_1_to_8_bytes"); DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count); @@ -174,6 +209,11 @@ ps_fetch_float(zval * zv, const MYSQLND_FIELD * const field, const unsigned int float fval; double dval; DBG_ENTER("ps_fetch_float"); + + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 4))) { + return; + } + float4get(fval, *row); (*row)+= 4; DBG_INF_FMT("value=%f", fval); @@ -196,6 +236,11 @@ ps_fetch_double(zval * zv, const MYSQLND_FIELD * const field, const unsigned int { double value; DBG_ENTER("ps_fetch_double"); + + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 8))) { + return; + } + float8get(value, *row); ZVAL_DOUBLE(zv, value); (*row)+= 8; @@ -211,9 +256,14 @@ ps_fetch_time(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p { struct st_mysqlnd_time t; zend_ulong length; /* First byte encodes the length */ + const zend_uchar *p = *row; DBG_ENTER("ps_fetch_time"); if ((length = php_mysqlnd_net_field_length(row))) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } + const zend_uchar * to = *row; t.time_type = MYSQLND_TIMESTAMP_TIME; @@ -256,9 +306,14 @@ ps_fetch_date(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p { struct st_mysqlnd_time t = {0}; zend_ulong length; /* First byte encodes the length*/ + const zend_uchar *p = *row; DBG_ENTER("ps_fetch_date"); if ((length = php_mysqlnd_net_field_length(row))) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } + const zend_uchar * to = *row; t.time_type = MYSQLND_TIMESTAMP_DATE; @@ -288,9 +343,14 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i { struct st_mysqlnd_time t; zend_ulong length; /* First byte encodes the length*/ + const zend_uchar *p = *row; DBG_ENTER("ps_fetch_datetime"); if ((length = php_mysqlnd_net_field_length(row))) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } + const zend_uchar * to = *row; t.time_type = MYSQLND_TIMESTAMP_DATETIME; @@ -332,7 +392,11 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i static void ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row) { + const zend_uchar *p = *row; const zend_ulong length = php_mysqlnd_net_field_length(row); + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } DBG_ENTER("ps_fetch_string"); DBG_INF_FMT("len = " ZEND_ULONG_FMT, length); DBG_INF("copying from the row buffer"); @@ -348,7 +412,11 @@ ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int static void ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row) { + const zend_uchar *p = *row; const zend_ulong length = php_mysqlnd_net_field_length(row); + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index fed191c74fa52..19debe98089da 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -447,8 +447,31 @@ php_mysqlnd_greet_read(MYSQLND_CONN_DATA * conn, void * _packet) if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) { BAIL_IF_NO_MORE_DATA; /* The server is 5.5.x and supports authentication plugins */ - packet->auth_protocol = estrdup((char *)p); - p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */ + size_t remaining_size = packet->header.size - (size_t)(p - buf); + if (remaining_size == 0) { + /* Might be better to fail but this will fail anyway */ + packet->auth_protocol = estrdup(""); + } else { + /* Check if NUL present */ + char *null_terminator = memchr(p, '\0', remaining_size); + size_t auth_protocol_len; + if (null_terminator) { + /* If present, do basically estrdup */ + auth_protocol_len = null_terminator - (char *)p; + } else { + /* If not present, copy the rest of the buffer */ + auth_protocol_len = remaining_size; + } + char *auth_protocol = emalloc(auth_protocol_len + 1); + memcpy(auth_protocol, p, auth_protocol_len); + auth_protocol[auth_protocol_len] = '\0'; + packet->auth_protocol = auth_protocol; + + p += auth_protocol_len; + if (null_terminator) { + p++; + } + } } DBG_INF_FMT("proto=%u server=%s thread_id=%u", @@ -721,7 +744,14 @@ php_mysqlnd_auth_response_read(MYSQLND_CONN_DATA * conn, void * _packet) /* There is a message */ if (packet->header.size > (size_t) (p - buf) && (net_len = php_mysqlnd_net_field_length(&p))) { - packet->message_len = MIN(net_len, buf_len - (p - begin)); + /* p can get past packet size when getting field length so it needs to be checked first + * and after that it can be checked that the net_len is not greater than the packet size */ + if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < net_len) { + DBG_ERR_FMT("OK packet message length is past the packet size"); + php_error_docref(NULL, E_WARNING, "OK packet message length is past the packet size"); + DBG_RETURN(FAIL); + } + packet->message_len = net_len; packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE); } else { packet->message = NULL; @@ -1105,6 +1135,17 @@ php_mysqlnd_rset_header_read(MYSQLND_CONN_DATA * conn, void * _packet) BAIL_IF_NO_MORE_DATA; /* Check for additional textual data */ if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) { + /* p can get past packet size when getting field length so it needs to be checked first + * and after that it can be checked that the len is not greater than the packet size */ + if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < len) { + size_t local_file_name_over_read = ((p - buf) - packet->header.size) + len; + DBG_ERR_FMT("RSET_HEADER packet additional data length is past %zu bytes the packet size", + local_file_name_over_read); + php_error_docref(NULL, E_WARNING, + "RSET_HEADER packet additional data length is past %zu bytes the packet size", + local_file_name_over_read); + DBG_RETURN(FAIL); + } packet->info_or_local_file.s = mnd_emalloc(len + 1); memcpy(packet->info_or_local_file.s, p, len); packet->info_or_local_file.s[len] = '\0'; @@ -1255,23 +1296,16 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet) meta->flags |= NUM_FLAG; } - - /* - def could be empty, thus don't allocate on the root. - NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL. - Otherwise the string is length encoded. - */ + /* COM_FIELD_LIST is no longer supported so def should not be present */ if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p)) && len != MYSQLND_NULL_LENGTH) { - BAIL_IF_NO_MORE_DATA; - DBG_INF_FMT("Def found, length " ZEND_ULONG_FMT, len); - meta->def = packet->memory_pool->get_chunk(packet->memory_pool, len + 1); - memcpy(meta->def, p, len); - meta->def[len] = '\0'; - meta->def_length = len; - p += len; + DBG_ERR_FMT("Protocol error. Server sent default for unsupported field list"); + php_error_docref(NULL, E_WARNING, + "Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%u)", + __LINE__); + DBG_RETURN(FAIL); } root_ptr = meta->root = packet->memory_pool->get_chunk(packet->memory_pool, total_len); @@ -1434,8 +1468,10 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi const unsigned int field_count, const MYSQLND_FIELD * const fields_metadata, const bool as_int_or_float, MYSQLND_STATS * const stats) { - unsigned int i; - const zend_uchar * p = row_buffer->ptr; + unsigned int i, j; + size_t rbs = row_buffer->size; + const zend_uchar * rbp = row_buffer->ptr; + const zend_uchar * p = rbp; const zend_uchar * null_ptr; zend_uchar bit; zval *current_field, *end_field, *start_field; @@ -1468,7 +1504,21 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi statistic = STAT_BINARY_TYPE_FETCHED_NULL; } else { enum_mysqlnd_field_types type = fields_metadata[i].type; - mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], 0, &p); + size_t row_position = p - rbp; + if (rbs <= row_position) { + for (j = 0, current_field = start_field; j < i; current_field++, j++) { + zval_ptr_dtor(current_field); + } + php_error_docref(NULL, E_WARNING, "Malformed server packet. No packet space left for the field"); + DBG_RETURN(FAIL); + } + mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], rbs - row_position, &p); + if (p == NULL) { + for (j = 0, current_field = start_field; j < i; current_field++, j++) { + zval_ptr_dtor(current_field); + } + DBG_RETURN(FAIL); + } if (MYSQLND_G(collect_statistics)) { switch (fields_metadata[i].type) { @@ -1525,7 +1575,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fiel unsigned int field_count, const MYSQLND_FIELD * fields_metadata, bool as_int_or_float, MYSQLND_STATS * stats) { - unsigned int i; + unsigned int i, j; zval *current_field, *end_field, *start_field; zend_uchar * p = row_buffer->ptr; const size_t data_size = row_buffer->size; @@ -1546,9 +1596,11 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fiel /* NULL or NOT NULL, this is the question! */ if (len == MYSQLND_NULL_LENGTH) { ZVAL_NULL(current_field); - } else if ((p + len) > packet_end) { - php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing %zu" - " bytes after end of packet", (p + len) - packet_end - 1); + } else if (p > packet_end || len > packet_end - p) { + php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after end of packet"); + for (j = 0, current_field = start_field; j < i; current_field++, j++) { + zval_ptr_dtor(current_field); + } DBG_RETURN(FAIL); } else { struct st_mysqlnd_perm_bind perm_bind = diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index d9b61a5bdcf65..f0176d24b2d83 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -4190,16 +4190,19 @@ static void zend_jit_cleanup_func_info(zend_op_array *op_array) } } -static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, const zend_op *rt_opline) +static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, const zend_op *rt_opline, uint8_t trigger) { zend_ssa ssa; void *checkpoint; zend_func_info *func_info; + uint8_t orig_trigger; if (*dasm_ptr == dasm_end) { return FAILURE; } + orig_trigger = JIT_G(trigger); + JIT_G(trigger) = trigger; checkpoint = zend_arena_checkpoint(CG(arena)); /* Build SSA */ @@ -4232,11 +4235,13 @@ static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, cons zend_jit_cleanup_func_info(op_array); zend_arena_release(&CG(arena), checkpoint); + JIT_G(trigger) = orig_trigger; return SUCCESS; jit_failure: zend_jit_cleanup_func_info(op_array); zend_arena_release(&CG(arena), checkpoint); + JIT_G(trigger) = orig_trigger; return FAILURE; } @@ -4267,7 +4272,7 @@ static int ZEND_FASTCALL zend_runtime_jit(void) opline->handler = jit_extension->orig_handler; /* perform real JIT for this function */ - zend_real_jit_func(op_array, NULL, NULL); + zend_real_jit_func(op_array, NULL, NULL, ZEND_JIT_ON_FIRST_EXEC); } zend_catch { do_bailout = true; } zend_end_try(); @@ -4313,7 +4318,7 @@ void zend_jit_check_funcs(HashTable *function_table, bool is_method) { jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array); opline->handler = jit_extension->orig_handler; if (((double)counter / (double)zend_jit_profile_counter) > JIT_G(prof_threshold)) { - zend_real_jit_func(op_array, NULL, NULL); + zend_real_jit_func(op_array, NULL, NULL, ZEND_JIT_ON_PROF_REQUEST); } } } ZEND_HASH_FOREACH_END(); @@ -4339,7 +4344,7 @@ void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend } /* perform real JIT for this function */ - zend_real_jit_func(op_array, NULL, opline); + zend_real_jit_func(op_array, NULL, opline, ZEND_JIT_ON_HOT_COUNTERS); } zend_catch { do_bailout = 1; } zend_end_try(); @@ -4507,7 +4512,7 @@ ZEND_EXT_API int zend_jit_op_array(zend_op_array *op_array, zend_script *script) } else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { return zend_jit_setup_hot_trace_counters(op_array); } else if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) { - return zend_real_jit_func(op_array, script, NULL); + return zend_real_jit_func(op_array, script, NULL, ZEND_JIT_ON_SCRIPT_LOAD); } else { ZEND_UNREACHABLE(); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 69c7ca3a537c4..c4f09bd15cd72 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1828,6 +1828,12 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_array *op_array, zend_arg_info *arg_info, void **cache_slot) { + if (Z_TYPE_P(arg) == IS_NULL) { + ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type)); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(arg_info->type, IS_NULL))) { + return; + } + } if (UNEXPECTED(!zend_check_user_type_slow( &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { zend_verify_return_error((zend_function*)op_array, arg); diff --git a/ext/opcache/tests/jit/gh16393.phpt b/ext/opcache/tests/jit/gh16393.phpt new file mode 100644 index 0000000000000..c93b06fda8ce5 --- /dev/null +++ b/ext/opcache/tests/jit/gh16393.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-16393 (Assertion failure in ext/opcache/jit/zend_jit.c:2897) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit=1215 +opcache.jit_buffer_size=64M +--FILE-- +bindTo($test, Test::class); +$appendProp2(); +?> +--EXPECTF-- +Warning: Undefined variable $test in %sgh16393.php on line 6 diff --git a/ext/opcache/tests/jit/gh16499.phpt b/ext/opcache/tests/jit/gh16499.phpt new file mode 100644 index 0000000000000..6aec1012ab5ee --- /dev/null +++ b/ext/opcache/tests/jit/gh16499.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-16499 (Undefined to null coercion issues for return) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit_buffer_size=64M +--FILE-- + +--EXPECTF-- +Warning: Undefined variable $i in %sgh16499.php on line 6 + +Warning: Undefined variable $i in %sgh16499.php on line 6 +NULL + +Warning: Undefined variable $i in %sgh16499.php on line 6 + +Warning: Undefined variable $i in %sgh16499.php on line 6 +NULL \ No newline at end of file diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 2ee60d4bbc493..e504b0203a693 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -170,13 +170,13 @@ static zend_always_inline void _zend_accel_function_hash_copy(HashTable *target, function2 = Z_PTR_P(t); CG(in_compilation) = 1; zend_set_compiled_filename(function1->op_array.filename); - CG(zend_lineno) = function1->op_array.opcodes[0].lineno; + CG(zend_lineno) = function1->op_array.line_start; if (function2->type == ZEND_USER_FUNCTION && function2->op_array.last > 0) { zend_error(E_ERROR, "Cannot redeclare %s() (previously declared in %s:%d)", ZSTR_VAL(function1->common.function_name), ZSTR_VAL(function2->op_array.filename), - (int)function2->op_array.opcodes[0].lineno); + (int)function2->op_array.line_start); } else { zend_error(E_ERROR, "Cannot redeclare %s()", ZSTR_VAL(function1->common.function_name)); } diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index f6ed67b805b67..a50a3074117cf 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1457,11 +1457,13 @@ static X509 *php_openssl_x509_from_zval( *free_cert = 1; - if (!try_convert_to_string(val)) { + zend_string *str = zval_try_get_string(val); + if (str == NULL) { return NULL; } - - return php_openssl_x509_from_str(Z_STR_P(val), arg_num, is_from_array, option_name); + X509 *cert = php_openssl_x509_from_str(str, arg_num, is_from_array, option_name); + zend_string_release(str); + return cert; } /* }}} */ @@ -2089,7 +2091,7 @@ PHP_FUNCTION(openssl_x509_parse) /* Can return NULL on error or memory allocation failure */ if (!bn_serial) { php_openssl_store_errors(); - RETURN_FALSE; + goto err; } hex_serial = BN_bn2hex(bn_serial); @@ -2097,7 +2099,7 @@ PHP_FUNCTION(openssl_x509_parse) /* Can return NULL on error or memory allocation failure */ if (!hex_serial) { php_openssl_store_errors(); - RETURN_FALSE; + goto err; } str_serial = i2s_ASN1_INTEGER(NULL, asn1_serial); @@ -2169,19 +2171,15 @@ PHP_FUNCTION(openssl_x509_parse) bio_out = BIO_new(BIO_s_mem()); if (bio_out == NULL) { php_openssl_store_errors(); - RETURN_FALSE; + goto err_subitem; } if (nid == NID_subject_alt_name) { if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) { BIO_get_mem_ptr(bio_out, &bio_buf); add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); } else { - zend_array_destroy(Z_ARR_P(return_value)); BIO_free(bio_out); - if (cert_str) { - X509_free(cert); - } - RETURN_FALSE; + goto err_subitem; } } else if (X509V3_EXT_print(bio_out, extension, 0, 0)) { @@ -2196,6 +2194,16 @@ PHP_FUNCTION(openssl_x509_parse) if (cert_str) { X509_free(cert); } + return; + +err_subitem: + zval_ptr_dtor(&subitem); +err: + zend_array_destroy(Z_ARR_P(return_value)); + if (cert_str) { + X509_free(cert); + } + RETURN_FALSE; } /* }}} */ @@ -3198,6 +3206,11 @@ PHP_FUNCTION(openssl_csr_sign) goto cleanup; } + if (num_days < 0 || num_days > LONG_MAX / 86400) { + php_error_docref(NULL, E_WARNING, "Days must be between 0 and %ld", LONG_MAX / 86400); + goto cleanup; + } + if (PHP_SSL_REQ_PARSE(&req, args) == FAILURE) { goto cleanup; } @@ -3249,7 +3262,7 @@ PHP_FUNCTION(openssl_csr_sign) goto cleanup; } X509_gmtime_adj(X509_getm_notBefore(new_cert), 0); - X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*(long)num_days); + X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*num_days); i = X509_set_pubkey(new_cert, key); if (!i) { php_openssl_store_errors(); diff --git a/ext/openssl/tests/gh16357.phpt b/ext/openssl/tests/gh16357.phpt new file mode 100644 index 0000000000000..32a76167a0e38 --- /dev/null +++ b/ext/openssl/tests/gh16357.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-16357 (openssl may modify member types of certificate arrays) +--EXTENSIONS-- +openssl +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(false) +array(1) { + [0]=> + int(123) +} diff --git a/ext/openssl/tests/gh16433.phpt b/ext/openssl/tests/gh16433.phpt new file mode 100644 index 0000000000000..03554171d72e2 --- /dev/null +++ b/ext/openssl/tests/gh16433.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-16433 (Large values for openssl_csr_sign() $days overflow) +--EXTENSIONS-- +openssl +--FILE-- + +--EXPECTF-- +Warning: openssl_csr_sign(): Days must be between 0 and %d in %s on line %d +bool(false) + +Warning: openssl_csr_sign(): Days must be between 0 and %d in %s on line %d +bool(false) diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index 02ec2466a05fb..d7620307aa85b 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -148,7 +148,7 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo bool use_national_character_set = 0; size_t i; char *q; - size_t quotedlen = 0; + size_t quotedlen = 0, extralen = 0; zend_string *quoted_str; if (H->assume_national_character_set_strings) { @@ -163,7 +163,7 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo /* Detect quoted length, adding extra char for doubled single quotes */ for (i = 0; i < ZSTR_LEN(unquoted); i++) { - if (ZSTR_VAL(unquoted)[i] == '\'') ++quotedlen; + if (ZSTR_VAL(unquoted)[i] == '\'') ++extralen; ++quotedlen; } @@ -171,6 +171,12 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo if (use_national_character_set) { ++quotedlen; /* N prefix */ } + + if (UNEXPECTED(quotedlen > ZSTR_MAX_LEN - extralen)) { + return NULL; + } + + quotedlen += extralen; quoted_str = zend_string_alloc(quotedlen, 0); q = ZSTR_VAL(quoted_str); if (use_national_character_set) { diff --git a/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt new file mode 100644 index 0000000000000..431c61951ee2a --- /dev/null +++ b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt @@ -0,0 +1,24 @@ +--TEST-- +GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing OOB writes) +--EXTENSIONS-- +pdo_dblib +--SKIPIF-- + +--INI-- +memory_limit=-1 +--FILE-- +quote(str_repeat("'", 2147483646))); + +?> +--EXPECT-- +bool(false) diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index 03c9f1b7741c5..a192a0adb10ce 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -664,7 +664,7 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* /* called by the PDO SQL parser to add quotes to values that are copied into SQL */ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { - int qcount = 0; + size_t qcount = 0; char const *co, *l, *r; char *c; size_t quotedlen; @@ -678,6 +678,10 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un /* count the number of ' characters */ for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++); + if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) { + return NULL; + } + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; quoted_str = zend_string_alloc(quotedlen, 0); c = ZSTR_VAL(quoted_str); diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index bd4a2f6162d09..1df4e22571a76 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -689,11 +689,12 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); - /* adjust `used` in case we have length info from the driver */ + /* adjust `used` in case we have proper length info from the driver */ if (orig_fetched_len >= 0 && C->fetched_len >= 0) { SQLLEN fixed_used = orig_fetched_len - C->fetched_len; - ZEND_ASSERT(fixed_used <= used + 1); - used = fixed_used; + if (fixed_used <= used + 1) { + used = fixed_used; + } } /* resize output buffer and reassemble block */ diff --git a/ext/pdo_odbc/tests/gh16450.phpt b/ext/pdo_odbc/tests/gh16450.phpt new file mode 100644 index 0000000000000..e29d7672ee74b --- /dev/null +++ b/ext/pdo_odbc/tests/gh16450.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-16450 (PDO_ODBC can inject garbage into field values) +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE gh16450 (Id INT, MyLongText LONGCHAR)"); +$pdo->exec(sprintf("INSERT INTO gh16450 VALUES (1, '%s')", str_repeat("_", 2048))); +$pdo->exec(sprintf("INSERT INTO gh16450 VALUES (1, '%s')", str_repeat("_", 2049))); + +$stmt = $pdo->query("SELECT MyLongText FROM gh16450"); +var_dump($stmt->fetchColumn(0)); +var_dump($stmt->fetchColumn(0)); +?> +--CLEAN-- +exec("DROP TABLE gh16450"); +?> +--EXPECT-- +string(2048) "________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________" +string(2049) "_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________" diff --git a/ext/pdo_odbc/tests/test.mdb b/ext/pdo_odbc/tests/test.mdb new file mode 100644 index 0000000000000..836d813e49839 Binary files /dev/null and b/ext/pdo_odbc/tests/test.mdb differ diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 646cf42c4a9c9..a509f7e01480c 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -2286,6 +2286,9 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert newentry.tar_type = (entry->is_dir ? TAR_DIR : TAR_FILE); } + /* The header offset is only used for unmodified zips. + * Once modified, phar_zip_changed_apply_int() will update the header_offset. */ + newentry.header_offset = 0; newentry.is_modified = 1; newentry.phar = phar; newentry.old_flags = newentry.flags & ~PHAR_ENT_COMPRESSION_MASK; /* remove compression from old_flags */ diff --git a/ext/phar/tests/gh16406.phpt b/ext/phar/tests/gh16406.phpt new file mode 100644 index 0000000000000..6df5fd3aab6db --- /dev/null +++ b/ext/phar/tests/gh16406.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-16406 (Assertion failure in ext/phar/phar.c:2808) +--EXTENSIONS-- +phar +zlib +--INI-- +phar.readonly=0 +phar.require_hash=0 +--FILE-- +'; +$files['b'] = 'b'; +$files['c'] = 'c'; +include __DIR__.'/files/phar_test.inc'; +$phar = new Phar($fname); +$phar->compressFiles(Phar::GZ); +$phar = $phar->convertToExecutable(Phar::TAR); +$phar = $phar->convertToExecutable(Phar::PHAR, Phar::GZ); +var_dump($phar['b']->openFile()->fread(4096)); +var_dump($phar['c']->openFile()->fread(4096)); +?> +--CLEAN-- + +--EXPECT-- +string(1) "b" +string(1) "c" diff --git a/ext/phar/tests/phar_oo_iswriteable.phpt b/ext/phar/tests/phar_oo_iswriteable.phpt index 836feb4ba8d5d..789d17f10acf1 100644 --- a/ext/phar/tests/phar_oo_iswriteable.phpt +++ b/ext/phar/tests/phar_oo_iswriteable.phpt @@ -5,6 +5,12 @@ phar --INI-- phar.readonly=0 phar.require_hash=0 +--SKIPIF-- + --FILE-- --FILE-- --FILE-- unmangled_name, 0); + efree(reference); +} + +static void reflection_free_parameter_reference(parameter_reference *reference) +{ + _free_function(reference->fptr); + efree(reference); +} + static void reflection_free_objects_storage(zend_object *object) /* {{{ */ { reflection_object *intern = reflection_object_from_obj(object); - parameter_reference *reference; - property_reference *prop_reference; if (intern->ptr) { switch (intern->ref_type) { case REF_TYPE_PARAMETER: - reference = (parameter_reference*)intern->ptr; - _free_function(reference->fptr); - efree(intern->ptr); + reflection_free_parameter_reference(intern->ptr); break; case REF_TYPE_TYPE: { @@ -243,9 +251,7 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ _free_function(intern->ptr); break; case REF_TYPE_PROPERTY: - prop_reference = (property_reference*)intern->ptr; - zend_string_release_ex(prop_reference->unmangled_name, 0); - efree(intern->ptr); + reflection_free_property_reference(intern->ptr); break; case REF_TYPE_ATTRIBUTE: { attribute_reference *attr_ref = intern->ptr; @@ -2546,6 +2552,10 @@ ZEND_METHOD(ReflectionParameter, __construct) } } + if (intern->ptr) { + reflection_free_parameter_reference(intern->ptr); + } + ref = (parameter_reference*) emalloc(sizeof(parameter_reference)); ref->arg_info = &arg_info[position]; ref->offset = (uint32_t)position; @@ -2555,11 +2565,15 @@ ZEND_METHOD(ReflectionParameter, __construct) intern->ptr = ref; intern->ref_type = REF_TYPE_PARAMETER; intern->ce = ce; + zval_ptr_dtor(&intern->obj); if (reference && is_closure) { ZVAL_COPY_VALUE(&intern->obj, reference); + } else { + ZVAL_UNDEF(&intern->obj); } prop_name = reflection_prop_name(object); + zval_ptr_dtor(prop_name); if (has_internal_arg_info(fptr)) { ZVAL_STRING(prop_name, ((zend_internal_arg_info*)arg_info)[position].name); } else { @@ -4015,10 +4029,12 @@ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_ob object = ZEND_THIS; intern = Z_REFLECTION_P(object); + /* Note: class entry name is interned, no need to destroy them */ if (arg_obj) { ZVAL_STR_COPY(reflection_prop_name(object), arg_obj->ce->name); intern->ptr = arg_obj->ce; if (is_object) { + zval_ptr_dtor(&intern->obj); ZVAL_OBJ_COPY(&intern->obj, arg_obj); } } else { @@ -5510,13 +5526,20 @@ ZEND_METHOD(ReflectionProperty, __construct) } } - ZVAL_STR_COPY(reflection_prop_name(object), name); + zval *prop_name = reflection_prop_name(object); + zval_ptr_dtor(prop_name); + ZVAL_STR_COPY(prop_name, name); + /* Note: class name are always interned, no need to destroy them */ if (dynam_prop == 0) { ZVAL_STR_COPY(reflection_prop_class(object), property_info->ce->name); } else { ZVAL_STR_COPY(reflection_prop_class(object), ce->name); } + if (intern->ptr) { + reflection_free_property_reference(intern->ptr); + } + reference = (property_reference*) emalloc(sizeof(property_reference)); reference->prop = dynam_prop ? NULL : property_info; reference->unmangled_name = zend_string_copy(name); @@ -5949,7 +5972,9 @@ ZEND_METHOD(ReflectionExtension, __construct) RETURN_THROWS(); } free_alloca(lcname, use_heap); - ZVAL_STRING(reflection_prop_name(object), module->name); + zval *prop_name = reflection_prop_name(object); + zval_ptr_dtor(prop_name); + ZVAL_STRING(prop_name, module->name); intern->ptr = module; intern->ref_type = REF_TYPE_OTHER; intern->ce = NULL; diff --git a/ext/reflection/tests/ReflectionExtension_double_construct.phpt b/ext/reflection/tests/ReflectionExtension_double_construct.phpt new file mode 100644 index 0000000000000..7bff11c4b1750 --- /dev/null +++ b/ext/reflection/tests/ReflectionExtension_double_construct.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionExtension double construct call +--FILE-- +__construct('standard'); +var_dump($r); + +?> +--EXPECT-- +object(ReflectionExtension)#1 (1) { + ["name"]=> + string(8) "standard" +} +object(ReflectionExtension)#1 (1) { + ["name"]=> + string(8) "standard" +} diff --git a/ext/reflection/tests/ReflectionObject_double_construct.phpt b/ext/reflection/tests/ReflectionObject_double_construct.phpt new file mode 100644 index 0000000000000..536b145243b1e --- /dev/null +++ b/ext/reflection/tests/ReflectionObject_double_construct.phpt @@ -0,0 +1,21 @@ +--TEST-- +ReflectionObject double construct call +--FILE-- +__construct($obj); +var_dump($r); + +?> +--EXPECT-- +object(ReflectionObject)#2 (1) { + ["name"]=> + string(8) "stdClass" +} +object(ReflectionObject)#2 (1) { + ["name"]=> + string(8) "stdClass" +} diff --git a/ext/reflection/tests/ReflectionParameter_double_construct.phpt b/ext/reflection/tests/ReflectionParameter_double_construct.phpt new file mode 100644 index 0000000000000..cc69e6b10e3c5 --- /dev/null +++ b/ext/reflection/tests/ReflectionParameter_double_construct.phpt @@ -0,0 +1,27 @@ +--TEST-- +ReflectionParameter double construct call +--FILE-- +__construct($closure, 'x'); +var_dump($r); +$r->__construct('ord', 'character'); +var_dump($r); + +?> +--EXPECT-- +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(1) "x" +} +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(1) "x" +} +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(9) "character" +} diff --git a/ext/reflection/tests/ReflectionProperty_double_construct.phpt b/ext/reflection/tests/ReflectionProperty_double_construct.phpt new file mode 100644 index 0000000000000..086f2f781a513 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_double_construct.phpt @@ -0,0 +1,24 @@ +--TEST-- +ReflectionProperty double construct call +--FILE-- +__construct(Exception::class, 'message'); +var_dump($r); + +?> +--EXPECT-- +object(ReflectionProperty)#1 (2) { + ["name"]=> + string(7) "message" + ["class"]=> + string(9) "Exception" +} +object(ReflectionProperty)#1 (2) { + ["name"]=> + string(7) "message" + ["class"]=> + string(9) "Exception" +} diff --git a/ext/session/session.c b/ext/session/session.c index 8b95b31e8fe49..dd780f4afd424 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -693,9 +693,18 @@ static PHP_INI_MH(OnUpdateCookieLifetime) /* {{{ */ { SESSION_CHECK_ACTIVE_STATE; SESSION_CHECK_OUTPUT_STATE; - if (atol(ZSTR_VAL(new_value)) < 0) { + +#ifdef ZEND_ENABLE_ZVAL_LONG64 + const zend_long maxcookie = ZEND_LONG_MAX - INT_MAX - 1; +#else + const zend_long maxcookie = ZEND_LONG_MAX / 2 - 1; +#endif + zend_long v = (zend_long)atol(ZSTR_VAL(new_value)); + if (v < 0) { php_error_docref(NULL, E_WARNING, "CookieLifetime cannot be negative"); return FAILURE; + } else if (v > maxcookie) { + return SUCCESS; } return OnUpdateLongGEZero(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); } @@ -1668,10 +1677,6 @@ PHP_FUNCTION(session_set_cookie_params) zend_result result; int found = 0; - if (!PS(use_cookies)) { - return; - } - ZEND_PARSE_PARAMETERS_START(1, 5) Z_PARAM_ARRAY_HT_OR_LONG(options_ht, lifetime_long) Z_PARAM_OPTIONAL @@ -1681,6 +1686,11 @@ PHP_FUNCTION(session_set_cookie_params) Z_PARAM_BOOL_OR_NULL(httponly, httponly_null) ZEND_PARSE_PARAMETERS_END(); + if (!PS(use_cookies)) { + php_error_docref(NULL, E_WARNING, "Session cookies cannot be used when session.use_cookies is disabled"); + RETURN_FALSE; + } + if (PS(session_status) == php_session_active) { php_error_docref(NULL, E_WARNING, "Session cookie parameters cannot be changed when a session is active"); RETURN_FALSE; diff --git a/ext/session/tests/gh16290.phpt b/ext/session/tests/gh16290.phpt new file mode 100644 index 0000000000000..d341eb47471b8 --- /dev/null +++ b/ext/session/tests/gh16290.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-16290 (overflow on session cookie_lifetime ini) +--EXTENSIONS-- +session +--SKIPIF-- + +--FILE-- + +--EXPECT-- +DONE diff --git a/ext/session/tests/gh16385.phpt b/ext/session/tests/gh16385.phpt new file mode 100644 index 0000000000000..4ede457315f03 --- /dev/null +++ b/ext/session/tests/gh16385.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-16385 (Unexpected null returned by session_set_cookie_params) +--EXTENSIONS-- +session +--INI-- +session.use_cookies=0 +--FILE-- + +--EXPECTF-- +Warning: session_set_cookie_params(): Session cookies cannot be used when session.use_cookies is disabled in %s on line %d +bool(false) diff --git a/ext/session/tests/session_get_cookie_params_basic.phpt b/ext/session/tests/session_get_cookie_params_basic.phpt index d34f7ccbf95c3..65b020d30b9ec 100644 --- a/ext/session/tests/session_get_cookie_params_basic.phpt +++ b/ext/session/tests/session_get_cookie_params_basic.phpt @@ -35,7 +35,7 @@ var_dump(session_get_cookie_params()); echo "Done"; ob_end_flush(); ?> ---EXPECT-- +--EXPECTF-- *** Testing session_get_cookie_params() : basic functionality *** array(6) { ["lifetime"]=> @@ -69,7 +69,7 @@ array(6) { bool(true) array(6) { ["lifetime"]=> - int(1234567890) + int(%d) ["path"]=> string(5) "/guff" ["domain"]=> diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 29cf8fbc9086b..39cecf9848dc3 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -2210,8 +2210,8 @@ static xmlNodePtr to_xml_array(encodeTypePtr type, zval *data, int style, xmlNod iter = ce->get_iterator(ce, data, 0); - if (EG(exception)) { - goto iterator_done; + if (!iter) { + goto iterator_failed_to_get; } if (iter->funcs->rewind) { @@ -2251,6 +2251,7 @@ static xmlNodePtr to_xml_array(encodeTypePtr type, zval *data, int style, xmlNod } iterator_done: OBJ_RELEASE(&iter->std); +iterator_failed_to_get: if (EG(exception)) { zval_ptr_dtor(&array_copy); ZVAL_UNDEF(&array_copy); diff --git a/ext/soap/tests/bugs/gh16429.phpt b/ext/soap/tests/bugs/gh16429.phpt new file mode 100644 index 0000000000000..24d517f96b966 --- /dev/null +++ b/ext/soap/tests/bugs/gh16429.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-16429 (Segmentation fault (access null pointer) in SoapClient) +--EXTENSIONS-- +soap +--FILE-- +send(10); +$fusion = $gen; +$client = new SoapClient(__DIR__."/../interop/Round2/GroupB/round2_groupB.wsdl",array("trace"=>1,"exceptions"=>0)); +try { + $client->echo2DStringArray($fusion); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +string(10) "xxxxxxxxxx" +Cannot traverse an already closed generator diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index f1a62c719291a..2430b10977ec7 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1402,7 +1402,8 @@ PHP_FUNCTION(socket_recvfrom) /* overflow check */ /* Shouldthrow ? */ - if ((arg3 + 2) < 3) { + + if (arg3 <= 0 || arg3 > ZEND_LONG_MAX - 1) { RETURN_FALSE; } diff --git a/ext/sockets/tests/socket_recv_overflow.phpt b/ext/sockets/tests/socket_recv_overflow.phpt new file mode 100644 index 0000000000000..9b3f7a0bbb538 --- /dev/null +++ b/ext/sockets/tests/socket_recv_overflow.phpt @@ -0,0 +1,19 @@ +--TEST-- +socket_recvfrom overflow on length argument +--EXTENSIONS-- +sockets +--SKIPIF-- + +--EXPECT-- +bool(false) +bool(false) diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index dbe110f7c40da..2a721d79dbc55 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -555,13 +555,15 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec if (Z_TYPE_P(data) == IS_INDIRECT) { data = Z_INDIRECT_P(data); if (Z_TYPE_P(data) != IS_UNDEF) { - zval_ptr_dtor(data); + zval garbage; + ZVAL_COPY_VALUE(&garbage, data); ZVAL_UNDEF(data); HT_FLAGS(ht) |= HASH_FLAG_HAS_EMPTY_IND; zend_hash_move_forward_ex(ht, spl_array_get_pos_ptr(ht, intern)); if (spl_array_is_object(intern)) { spl_array_skip_protected(intern, ht); } + zval_ptr_dtor(&garbage); } } else { zend_hash_del(ht, key.key); @@ -1052,8 +1054,10 @@ static HashTable *spl_array_it_get_gc(zend_object_iterator *iter, zval **table, static void spl_array_set_array(zval *object, spl_array_object *intern, zval *array, zend_long ar_flags, bool just_array) { /* Handled by ZPP prior to this, or for __unserialize() before passing to here */ ZEND_ASSERT(Z_TYPE_P(array) == IS_ARRAY || Z_TYPE_P(array) == IS_OBJECT); + zval garbage; + ZVAL_UNDEF(&garbage); if (Z_TYPE_P(array) == IS_ARRAY) { - zval_ptr_dtor(&intern->array); + ZVAL_COPY_VALUE(&garbage, &intern->array); if (Z_REFCOUNT_P(array) == 1) { ZVAL_COPY(&intern->array, array); } else { @@ -1071,7 +1075,7 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar } } else { if (Z_OBJ_HT_P(array) == &spl_handler_ArrayObject || Z_OBJ_HT_P(array) == &spl_handler_ArrayIterator) { - zval_ptr_dtor(&intern->array); + ZVAL_COPY_VALUE(&garbage, &intern->array); if (just_array) { spl_array_object *other = Z_SPLARRAY_P(array); ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK; @@ -1089,9 +1093,10 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Overloaded object of type %s is not compatible with %s", ZSTR_VAL(Z_OBJCE_P(array)->name), ZSTR_VAL(intern->std.ce->name)); + ZEND_ASSERT(Z_TYPE(garbage) == IS_UNDEF); return; } - zval_ptr_dtor(&intern->array); + ZVAL_COPY_VALUE(&garbage, &intern->array); ZVAL_COPY(&intern->array, array); } } @@ -1102,6 +1107,8 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar zend_hash_iterator_del(intern->ht_iter); intern->ht_iter = (uint32_t)-1; } + + zval_ptr_dtor(&garbage); } /* }}} */ diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 0440ff59c8ba9..e0fe1292569bb 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -668,7 +668,7 @@ static inline HashTable *spl_filesystem_object_get_debug_info(zend_object *objec if (intern->type == SPL_FS_DIR) { #ifdef HAVE_GLOB pnstr = spl_gen_private_prop_name(spl_ce_DirectoryIterator, "glob", sizeof("glob")-1); - if (php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + if (intern->u.dir.dirp && php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { ZVAL_STR_COPY(&tmp, intern->path); } else { ZVAL_FALSE(&tmp); @@ -2042,22 +2042,27 @@ static void spl_filesystem_file_rewind(zval * this_ptr, spl_filesystem_object *i PHP_METHOD(SplFileObject, __construct) { spl_filesystem_object *intern = spl_filesystem_from_obj(Z_OBJ_P(ZEND_THIS)); + zend_string *file_name = NULL; zend_string *open_mode = ZSTR_CHAR('r'); + zval *stream_context = NULL; bool use_include_path = 0; size_t path_len; zend_error_handling error_handling; - intern->u.file.open_mode = ZSTR_CHAR('r'); + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|Sbr!", &file_name, &open_mode, &use_include_path, &stream_context) == FAILURE) { + RETURN_THROWS(); + } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|Sbr!", - &intern->file_name, &open_mode, - &use_include_path, &intern->u.file.zcontext) == FAILURE) { - intern->u.file.open_mode = NULL; - intern->file_name = NULL; + /* Prevent reinitialization of Object */ + if (UNEXPECTED(intern->u.file.stream)) { + zend_throw_error(NULL, "Cannot call constructor twice"); RETURN_THROWS(); } intern->u.file.open_mode = zend_string_copy(open_mode); + /* file_name and zcontext are copied by spl_filesystem_file_open() */ + intern->file_name = file_name; + intern->u.file.zcontext = stream_context; /* spl_filesystem_file_open() can generate E_WARNINGs which we want to promote to exceptions */ zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); @@ -2096,6 +2101,12 @@ PHP_METHOD(SplTempFileObject, __construct) RETURN_THROWS(); } + /* Prevent reinitialization of Object */ + if (UNEXPECTED(intern->u.file.stream)) { + zend_throw_error(NULL, "Cannot call constructor twice"); + RETURN_THROWS(); + } + if (max_memory < 0) { file_name = zend_string_init("php://memory", sizeof("php://memory")-1, 0); } else if (ZEND_NUM_ARGS()) { diff --git a/ext/spl/spl_dllist.c b/ext/spl/spl_dllist.c index 186b9a34c7efa..612fe2a7ed954 100644 --- a/ext/spl/spl_dllist.c +++ b/ext/spl/spl_dllist.c @@ -44,10 +44,13 @@ PHPAPI zend_class_entry *spl_ce_SplStack; efree(elem); \ } -#define SPL_LLIST_CHECK_DELREF(elem) if ((elem) && !--SPL_LLIST_RC(elem)) { \ +#define SPL_LLIST_CHECK_DELREF_EX(elem, on_free) if ((elem) && !--SPL_LLIST_RC(elem)) { \ efree(elem); \ + on_free \ } +#define SPL_LLIST_CHECK_DELREF(elem) SPL_LLIST_CHECK_DELREF_EX(elem, ;) + #define SPL_LLIST_ADDREF(elem) SPL_LLIST_RC(elem)++ #define SPL_LLIST_CHECK_ADDREF(elem) if (elem) SPL_LLIST_RC(elem)++ @@ -737,8 +740,10 @@ PHP_METHOD(SplDoublyLinkedList, offsetSet) if (element != NULL) { /* the element is replaced, delref the old one as in * SplDoublyLinkedList::pop() */ - zval_ptr_dtor(&element->data); + zval garbage; + ZVAL_COPY_VALUE(&garbage, &element->data); ZVAL_COPY(&element->data, value); + zval_ptr_dtor(&garbage); } else { zval_ptr_dtor(value); zend_argument_error(spl_ce_OutOfRangeException, 1, "is an invalid offset"); @@ -1022,8 +1027,12 @@ PHP_METHOD(SplDoublyLinkedList, serialize) smart_str_appendc(&buf, ':'); next = current->next; + SPL_LLIST_CHECK_ADDREF(next); + php_var_serialize(&buf, ¤t->data, &var_hash); + SPL_LLIST_CHECK_DELREF_EX(next, break;); + current = next; } diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 7c08a189c6fcf..bbee2b9370626 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -484,8 +484,10 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object * return; } else { intern->array.should_rebuild_properties = true; - zval_ptr_dtor(&(intern->array.elements[index])); + zval garbage; + ZVAL_COPY_VALUE(&garbage, &intern->array.elements[index]); ZVAL_NULL(&intern->array.elements[index]); + zval_ptr_dtor(&garbage); } } diff --git a/ext/spl/spl_heap.c b/ext/spl/spl_heap.c index 736f151da5846..df67bcb9daf83 100644 --- a/ext/spl/spl_heap.c +++ b/ext/spl/spl_heap.c @@ -32,6 +32,7 @@ #define PTR_HEAP_BLOCK_SIZE 64 #define SPL_HEAP_CORRUPTED 0x00000001 +#define SPL_HEAP_WRITE_LOCKED 0x00000002 zend_object_handlers spl_handler_SplHeap; zend_object_handlers spl_handler_SplPriorityQueue; @@ -278,12 +279,16 @@ static void spl_ptr_heap_insert(spl_ptr_heap *heap, void *elem, void *cmp_userda heap->max_size *= 2; } + heap->flags |= SPL_HEAP_WRITE_LOCKED; + /* sifting up */ for (i = heap->count; i > 0 && heap->cmp(spl_heap_elem(heap, (i-1)/2), elem, cmp_userdata) < 0; i = (i-1)/2) { spl_heap_elem_copy(heap, spl_heap_elem(heap, i), spl_heap_elem(heap, (i-1)/2)); } heap->count++; + heap->flags &= ~SPL_HEAP_WRITE_LOCKED; + if (EG(exception)) { /* exception thrown during comparison */ heap->flags |= SPL_HEAP_CORRUPTED; @@ -311,6 +316,8 @@ static int spl_ptr_heap_delete_top(spl_ptr_heap *heap, void *elem, void *cmp_use return FAILURE; } + heap->flags |= SPL_HEAP_WRITE_LOCKED; + if (elem) { spl_heap_elem_copy(heap, elem, spl_heap_elem(heap, 0)); } else { @@ -334,6 +341,8 @@ static int spl_ptr_heap_delete_top(spl_ptr_heap *heap, void *elem, void *cmp_use } } + heap->flags &= ~SPL_HEAP_WRITE_LOCKED; + if (EG(exception)) { /* exception thrown during comparison */ heap->flags |= SPL_HEAP_CORRUPTED; @@ -374,10 +383,14 @@ static spl_ptr_heap *spl_ptr_heap_clone(spl_ptr_heap *from) { /* {{{ */ static void spl_ptr_heap_destroy(spl_ptr_heap *heap) { /* {{{ */ int i; + heap->flags |= SPL_HEAP_WRITE_LOCKED; + for (i = 0; i < heap->count; ++i) { heap->dtor(spl_heap_elem(heap, i)); } + heap->flags &= ~SPL_HEAP_WRITE_LOCKED; + efree(heap->elements); efree(heap); } @@ -597,6 +610,21 @@ PHP_METHOD(SplHeap, isEmpty) } /* }}} */ +static zend_result spl_heap_consistency_validations(const spl_heap_object *intern, bool write) +{ + if (intern->heap->flags & SPL_HEAP_CORRUPTED) { + zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + return FAILURE; + } + + if (write && (intern->heap->flags & SPL_HEAP_WRITE_LOCKED)) { + zend_throw_exception(spl_ce_RuntimeException, "Heap cannot be changed when it is already being modified.", 0); + return FAILURE; + } + + return SUCCESS; +} + /* {{{ Push $value on the heap */ PHP_METHOD(SplHeap, insert) { @@ -609,8 +637,7 @@ PHP_METHOD(SplHeap, insert) intern = Z_SPLHEAP_P(ZEND_THIS); - if (intern->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(intern, true) != SUCCESS)) { RETURN_THROWS(); } @@ -632,8 +659,7 @@ PHP_METHOD(SplHeap, extract) intern = Z_SPLHEAP_P(ZEND_THIS); - if (intern->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(intern, true) != SUCCESS)) { RETURN_THROWS(); } @@ -658,8 +684,7 @@ PHP_METHOD(SplPriorityQueue, insert) intern = Z_SPLHEAP_P(ZEND_THIS); - if (intern->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(intern, true) != SUCCESS)) { RETURN_THROWS(); } @@ -699,8 +724,7 @@ PHP_METHOD(SplPriorityQueue, extract) intern = Z_SPLHEAP_P(ZEND_THIS); - if (intern->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(intern, true) != SUCCESS)) { RETURN_THROWS(); } @@ -726,8 +750,7 @@ PHP_METHOD(SplPriorityQueue, top) intern = Z_SPLHEAP_P(ZEND_THIS); - if (intern->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(intern, false) != SUCCESS)) { RETURN_THROWS(); } @@ -837,8 +860,7 @@ PHP_METHOD(SplHeap, top) intern = Z_SPLHEAP_P(ZEND_THIS); - if (intern->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(intern, false) != SUCCESS)) { RETURN_THROWS(); } @@ -902,8 +924,7 @@ static zval *spl_heap_it_get_current_data(zend_object_iterator *iter) /* {{{ */ { spl_heap_object *object = Z_SPLHEAP_P(&iter->data); - if (object->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(object, false) != SUCCESS)) { return NULL; } @@ -920,8 +941,7 @@ static zval *spl_pqueue_it_get_current_data(zend_object_iterator *iter) /* {{{ * zend_user_iterator *user_it = (zend_user_iterator *) iter; spl_heap_object *object = Z_SPLHEAP_P(&iter->data); - if (object->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(object, false) != SUCCESS)) { return NULL; } @@ -949,8 +969,7 @@ static void spl_heap_it_move_forward(zend_object_iterator *iter) /* {{{ */ { spl_heap_object *object = Z_SPLHEAP_P(&iter->data); - if (object->heap->flags & SPL_HEAP_CORRUPTED) { - zend_throw_exception(spl_ce_RuntimeException, "Heap is corrupted, heap properties are no longer ensured.", 0); + if (UNEXPECTED(spl_heap_consistency_validations(object, false) != SUCCESS)) { return; } diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index 976d29283b965..c7081f736d32c 100644 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -538,6 +538,20 @@ static int spl_get_iterator_from_aggregate(zval *retval, zend_class_entry *ce, z return SUCCESS; } +static void spl_RecursiveIteratorIterator_free_iterators(spl_recursive_it_object *object) +{ + if (object->iterators) { + while (object->level >= 0) { + zend_object_iterator *sub_iter = object->iterators[object->level].iterator; + zend_iterator_dtor(sub_iter); + zval_ptr_dtor(&object->iterators[object->level].zobject); + object->level--; + } + efree(object->iterators); + object->iterators = NULL; + } +} + static void spl_recursive_it_it_construct(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *ce_base, zend_class_entry *ce_inner, recursive_it_it_type rit_type) { zval *object = ZEND_THIS; @@ -604,6 +618,7 @@ static void spl_recursive_it_it_construct(INTERNAL_FUNCTION_PARAMETERS, zend_cla } intern = Z_SPLRECURSIVE_IT_P(object); + spl_RecursiveIteratorIterator_free_iterators(intern); intern->iterators = emalloc(sizeof(spl_sub_iterator)); intern->level = 0; intern->mode = mode; @@ -650,6 +665,7 @@ static void spl_recursive_it_it_construct(INTERNAL_FUNCTION_PARAMETERS, zend_cla intern->iterators[0].getchildren = NULL; if (EG(exception)) { + // TODO: use spl_RecursiveIteratorIterator_free_iterators zend_object_iterator *sub_iter; while (intern->level >= 0) { @@ -958,16 +974,7 @@ static void spl_RecursiveIteratorIterator_free_storage(zend_object *_object) { spl_recursive_it_object *object = spl_recursive_it_from_obj(_object); - if (object->iterators) { - while (object->level >= 0) { - zend_object_iterator *sub_iter = object->iterators[object->level].iterator; - zend_iterator_dtor(sub_iter); - zval_ptr_dtor(&object->iterators[object->level].zobject); - object->level--; - } - efree(object->iterators); - object->iterators = NULL; - } + spl_RecursiveIteratorIterator_free_iterators(object); zend_object_std_dtor(&object->std); for (size_t i = 0; i < 6; i++) { diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index 21b19cffa38ad..d3b58f0d762ee 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -746,8 +746,10 @@ PHP_METHOD(SplObjectStorage, setInfo) if ((element = zend_hash_get_current_data_ptr_ex(&intern->storage, &intern->pos)) == NULL) { RETURN_NULL(); } - zval_ptr_dtor(&element->inf); + zval garbage; + ZVAL_COPY_VALUE(&garbage, &element->inf); ZVAL_COPY(&element->inf, inf); + zval_ptr_dtor(&garbage); } /* }}} */ /* {{{ Moves position forward */ @@ -795,11 +797,18 @@ PHP_METHOD(SplObjectStorage, serialize) RETURN_NULL(); } ZVAL_OBJ(&obj, element->obj); + + /* Protect against modification; we need a full copy because the data may be refcounted. */ + zval inf_copy; + ZVAL_COPY(&inf_copy, &element->inf); + php_var_serialize(&buf, &obj, &var_hash); smart_str_appendc(&buf, ','); - php_var_serialize(&buf, &element->inf, &var_hash); + php_var_serialize(&buf, &inf_copy, &var_hash); smart_str_appendc(&buf, ';'); zend_hash_move_forward_ex(&intern->storage, &pos); + + zval_ptr_dtor(&inf_copy); } /* members */ diff --git a/ext/spl/tests/gh14687.phpt b/ext/spl/tests/gh14687.phpt new file mode 100644 index 0000000000000..1e95d6b6517cc --- /dev/null +++ b/ext/spl/tests/gh14687.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-14687 segfault on debugging SplObjectStorage instance after __destruct. +--CREDITS-- +YuanchengJiang +--EXTENSIONS-- +phar +--INI-- +phar.require_hash=0 +phar.readonly=0 +--FILE-- + +--CLEAN-- + +--EXPECT-- +object(SplObjectStorage)#2 (1) { + ["storage":"SplObjectStorage":private]=> + array(1) { + [0]=> + array(2) { + ["obj"]=> + object(Phar)#1 (3) { + ["pathName":"SplFileInfo":private]=> + string(0) "" + ["glob":"DirectoryIterator":private]=> + bool(false) + ["subPathName":"RecursiveDirectoryIterator":private]=> + string(0) "" + } + ["inf"]=> + object(HasDestructor)#3 (0) { + } + } + } +} diff --git a/ext/spl/tests/gh16337.phpt b/ext/spl/tests/gh16337.phpt new file mode 100644 index 0000000000000..94cf9d90cb1a9 --- /dev/null +++ b/ext/spl/tests/gh16337.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-16337 (Use-after-free in SplHeap) +--FILE-- +extract(); + } catch (Throwable $e) { + echo $e->getMessage(), "\n"; + } + try { + $heap->insert(1); + } catch (Throwable $e) { + echo $e->getMessage(), "\n"; + } + echo $heap->top(), "\n"; + return "0"; + } +} + +$heap = new SplMinHeap; +for ($i = 0; $i < 100; $i++) { + $heap->insert((string) $i); +} +$heap->insert(new C); + +?> +--EXPECT-- +Heap cannot be changed when it is already being modified. +Heap cannot be changed when it is already being modified. +0 +Heap cannot be changed when it is already being modified. +Heap cannot be changed when it is already being modified. +0 +Heap cannot be changed when it is already being modified. +Heap cannot be changed when it is already being modified. +0 +Heap cannot be changed when it is already being modified. +Heap cannot be changed when it is already being modified. +0 +Heap cannot be changed when it is already being modified. +Heap cannot be changed when it is already being modified. +0 +Heap cannot be changed when it is already being modified. +Heap cannot be changed when it is already being modified. +0 diff --git a/ext/spl/tests/gh16464.phpt b/ext/spl/tests/gh16464.phpt new file mode 100644 index 0000000000000..7b3b1e80e6fca --- /dev/null +++ b/ext/spl/tests/gh16464.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-16464: Use-after-free in SplDoublyLinkedList::offsetSet() when modifying list in destructor of overwritten object +--FILE-- +pop()); + } +} + +$list = new SplDoublyLinkedList; +$list->add(0, new C); +$list[0] = 42; +var_dump($list); + +?> +--EXPECTF-- +int(42) +object(SplDoublyLinkedList)#%d (2) { + ["flags":"SplDoublyLinkedList":private]=> + int(0) + ["dllist":"SplDoublyLinkedList":private]=> + array(0) { + } +} diff --git a/ext/spl/tests/gh16477-2.phpt b/ext/spl/tests/gh16477-2.phpt new file mode 100644 index 0000000000000..a51b9408c2d44 --- /dev/null +++ b/ext/spl/tests/gh16477-2.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-16477-2: Memory leak when calling SplTempFileObject::__constructor() twice +--FILE-- +__construct(); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +$obj->__debugInfo(); + +?> +DONE +--EXPECT-- +Error: Cannot call constructor twice +DONE diff --git a/ext/spl/tests/gh16477.phpt b/ext/spl/tests/gh16477.phpt new file mode 100644 index 0000000000000..f35c9538e8556 --- /dev/null +++ b/ext/spl/tests/gh16477.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-16477: Segmentation fault when calling __debugInfo() after failed SplFileObject::__constructor +--FILE-- +__construct(); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +$obj->__debugInfo(); + +?> +DONE +--EXPECT-- +ArgumentCountError: SplFileObject::__construct() expects at least 1 argument, 0 given +DONE diff --git a/ext/spl/tests/gh16478.phpt b/ext/spl/tests/gh16478.phpt new file mode 100644 index 0000000000000..5a708b36fe80a --- /dev/null +++ b/ext/spl/tests/gh16478.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-16478: Use-after-free in SplFixedArray::unset() +--FILE-- +setSize(0); + } +} + +$arr = new SplFixedArray(2); +$arr[0] = new C; +unset($arr[0]); +var_dump($arr); + +?> +--EXPECT-- +object(SplFixedArray)#1 (0) { +} diff --git a/ext/spl/tests/gh16479.phpt b/ext/spl/tests/gh16479.phpt new file mode 100644 index 0000000000000..5309a450b9d45 --- /dev/null +++ b/ext/spl/tests/gh16479.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-16479: Use-after-free in SplObjectStorage::setInfo() +--FILE-- +removeAll($store); + } +} + +$o = new stdClass; +$store = new SplObjectStorage; +$store[$o] = new C; +$store->setInfo(1); +var_dump($store); + +?> +--EXPECT-- +object(SplObjectStorage)#2 (1) { + ["storage":"SplObjectStorage":private]=> + array(0) { + } +} diff --git a/ext/spl/tests/gh16588.phpt b/ext/spl/tests/gh16588.phpt new file mode 100644 index 0000000000000..6cf668716ac32 --- /dev/null +++ b/ext/spl/tests/gh16588.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-16588 (UAF in Observer->serialize) +--CREDITS-- +chibinz +--FILE-- +removeAll($store); + return []; + } +} + +$store = new SplObjectStorage; +$store[new C] = new stdClass; +var_dump($store->serialize()); + +?> +--EXPECT-- +string(47) "x:i:1;O:1:"C":0:{},O:8:"stdClass":0:{};m:a:0:{}" diff --git a/ext/spl/tests/gh16589.phpt b/ext/spl/tests/gh16589.phpt new file mode 100644 index 0000000000000..7b1452e37a461 --- /dev/null +++ b/ext/spl/tests/gh16589.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-16589 (UAF in SplDoublyLinked->serialize()) +--CREDITS-- +chibinz +--FILE-- +pop(); + return []; + } +} + +$list = new SplDoublyLinkedList; +$list->add(0, new C); +$list->add(1, 1); +var_dump($list->serialize()); + +?> +--EXPECT-- +string(17) "i:0;:O:1:"C":0:{}" diff --git a/ext/spl/tests/gh16604_1.phpt b/ext/spl/tests/gh16604_1.phpt new file mode 100644 index 0000000000000..3eb2ca9211eaf --- /dev/null +++ b/ext/spl/tests/gh16604_1.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-16604 (Memory leaks in SPL constructors) - recursive iterators +--FILE-- +__construct( $traversable ); + +$obj = new RecursiveTreeIterator( $traversable ); +$obj->__construct( $traversable ); + +?> +--EXPECT-- diff --git a/ext/spl/tests/gh16604_2.phpt b/ext/spl/tests/gh16604_2.phpt new file mode 100644 index 0000000000000..703b37ce87df7 --- /dev/null +++ b/ext/spl/tests/gh16604_2.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-16604 (Memory leaks in SPL constructors) - SplFileObject +--FILE-- +__construct(__DIR__.'/gh16604_2.tmp'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--CLEAN-- + +--EXPECT-- +Cannot call constructor twice diff --git a/ext/spl/tests/gh16646.phpt b/ext/spl/tests/gh16646.phpt new file mode 100644 index 0000000000000..b6cb503d8ed05 --- /dev/null +++ b/ext/spl/tests/gh16646.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-16646: Use-after-free in ArrayObject::unset() with destructor +--FILE-- +b = $arg; + } +} + +class C { + function __destruct() { + global $arr; + echo __METHOD__, "\n"; + $arr->exchangeArray([]); + } +} + +$arr = new ArrayObject(new B(new C)); +unset($arr["b"]); +var_dump($arr); + +?> +--EXPECT-- +C::__destruct +object(ArrayObject)#1 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } +} diff --git a/ext/spl/tests/gh16646_2.phpt b/ext/spl/tests/gh16646_2.phpt new file mode 100644 index 0000000000000..d006583500811 --- /dev/null +++ b/ext/spl/tests/gh16646_2.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-16646: Use-after-free in ArrayObject::exchangeArray() with destructor +--FILE-- +exchangeArray([]); + } +} + +$arr = new ArrayObject(new C); +$arr->exchangeArray([]); +var_dump($arr); + +?> +--EXPECT-- +C::__destruct +object(ArrayObject)#1 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } +} diff --git a/ext/standard/array.c b/ext/standard/array.c index 6c1975b121749..d4e3742bb71fa 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -705,9 +705,9 @@ static void php_natsort(INTERNAL_FUNCTION_PARAMETERS, int fold_case) /* {{{ */ ZEND_PARSE_PARAMETERS_END(); if (fold_case) { - zend_hash_sort(Z_ARRVAL_P(array), php_array_natural_case_compare, 0); + zend_array_sort(Z_ARRVAL_P(array), php_array_natural_case_compare, 0); } else { - zend_hash_sort(Z_ARRVAL_P(array), php_array_natural_compare, 0); + zend_array_sort(Z_ARRVAL_P(array), php_array_natural_compare, 0); } RETURN_TRUE; @@ -743,7 +743,7 @@ PHP_FUNCTION(asort) cmp = php_get_data_compare_func(sort_type, 0); - zend_hash_sort(Z_ARRVAL_P(array), cmp, 0); + zend_array_sort(Z_ARRVAL_P(array), cmp, 0); RETURN_TRUE; } @@ -764,7 +764,7 @@ PHP_FUNCTION(arsort) cmp = php_get_data_compare_func(sort_type, 1); - zend_hash_sort(Z_ARRVAL_P(array), cmp, 0); + zend_array_sort(Z_ARRVAL_P(array), cmp, 0); RETURN_TRUE; } @@ -785,7 +785,7 @@ PHP_FUNCTION(sort) cmp = php_get_data_compare_func(sort_type, 0); - zend_hash_sort(Z_ARRVAL_P(array), cmp, 1); + zend_array_sort(Z_ARRVAL_P(array), cmp, 1); RETURN_TRUE; } @@ -806,7 +806,7 @@ PHP_FUNCTION(rsort) cmp = php_get_data_compare_func(sort_type, 1); - zend_hash_sort(Z_ARRVAL_P(array), cmp, 1); + zend_array_sort(Z_ARRVAL_P(array), cmp, 1); RETURN_TRUE; } @@ -904,7 +904,7 @@ static void php_usort(INTERNAL_FUNCTION_PARAMETERS, bucket_compare_func_t compar /* Copy array, so the in-place modifications will not be visible to the callback function */ arr = zend_array_dup(arr); - zend_hash_sort(arr, compare_func, renumber); + zend_array_sort(arr, compare_func, renumber); zval garbage; ZVAL_COPY_VALUE(&garbage, array); diff --git a/ext/standard/assert.c b/ext/standard/assert.c index 1c5108b94e5b2..f318b1c834067 100644 --- a/ext/standard/assert.c +++ b/ext/standard/assert.c @@ -195,6 +195,11 @@ PHP_FUNCTION(assert) } if (ASSERTG(bail)) { + if (EG(exception)) { + /* The callback might have thrown. Use E_WARNING to print the + * exception so we can avoid bailout and use unwind_exit. */ + zend_exception_error(EG(exception), E_WARNING); + } zend_throw_unwind_exit(); RETURN_THROWS(); } else { diff --git a/ext/standard/filters.c b/ext/standard/filters.c index b390ac7b0a212..9b54b3deab18a 100644 --- a/ext/standard/filters.c +++ b/ext/standard/filters.c @@ -996,6 +996,9 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins } break; case 5: { + if (icnt == 0) { + goto out; + } if (!inst->lbchars && lb_cnt == 1 && *ps == '\n') { /* auto-detect soft line breaks, found network line break */ lb_cnt = lb_ptr = 0; @@ -1009,15 +1012,13 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins /* soft line break */ lb_cnt = lb_ptr = 0; scan_stat = 0; - } else if (icnt > 0) { + } else { if (*ps == (unsigned char)inst->lbchars[lb_cnt]) { lb_cnt++; ps++, icnt--; } else { scan_stat = 6; /* no break for short-cut */ } - } else { - goto out; } } break; diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index dca176ad3f526..2b9c7a06298a4 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -183,6 +183,11 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, return NULL; } + /* Should we send the entire path in the request line, default to no. */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) { + request_fulluri = zend_is_true(tmpzval); + } + use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's'; /* choose default ports */ if (use_ssl && resource->port == 0) @@ -201,6 +206,13 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } } + if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) { + php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper full URI path does not allow CR or LF characters"); + php_url_free(resource); + zend_string_release(transport_string); + return NULL; + } + if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) { double d = zval_get_double(tmpzval); #ifndef PHP_WIN32 @@ -381,12 +393,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, smart_str_appends(&req_buf, "GET "); } - /* Should we send the entire path in the request line, default to no. */ - if (!request_fulluri && context && - (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) { - request_fulluri = zend_is_true(tmpzval); - } - if (request_fulluri) { /* Ask for everything */ smart_str_appends(&req_buf, path); diff --git a/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt b/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt new file mode 100644 index 0000000000000..8fdcce8ff22e0 --- /dev/null +++ b/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt @@ -0,0 +1,12 @@ +--TEST-- +GHSA-r977-prxv-hc43: Single byte overread with convert.quoted-printable-decode filter +--FILE-- + +--EXPECT-- +string(8190) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX" diff --git a/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt new file mode 100644 index 0000000000000..e7dd194dbbe6f --- /dev/null +++ b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt @@ -0,0 +1,28 @@ +--TEST-- +GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context might allow for CRLF injection in URIs) +--INI-- +allow_url_fopen=1 +--CONFLICTS-- +server +--FILE-- + ['proxy' => 'tcp://' . $host, 'request_fulluri' => true]]); +echo file_get_contents("http://$host/$userinput", false, $context); +?> +--EXPECTF-- +Warning: file_get_contents(http://localhost:%d/index.php HTTP/1.1 +Host: localhost:%d + +GET /index2.php HTTP/1.1 +Host: localhost:%d + +GET /index.php): Failed to open stream: HTTP wrapper full URI path does not allow CR or LF characters in %s on line %d diff --git a/ext/sysvmsg/sysvmsg.c b/ext/sysvmsg/sysvmsg.c index d9146f6e78cc3..d6f2b175b01c7 100644 --- a/ext/sysvmsg/sysvmsg.c +++ b/ext/sysvmsg/sysvmsg.c @@ -371,11 +371,19 @@ PHP_FUNCTION(msg_send) php_var_serialize(&msg_var, message, &var_hash); PHP_VAR_SERIALIZE_DESTROY(var_hash); + if (UNEXPECTED(EG(exception))) { + smart_str_free(&msg_var); + RETURN_THROWS(); + } + + + zend_string *str = smart_str_extract(&msg_var); + message_len = ZSTR_LEN(str); /* NB: php_msgbuf is 1 char bigger than a long, so there is no need to * allocate the extra byte. */ - messagebuffer = safe_emalloc(ZSTR_LEN(msg_var.s), 1, sizeof(struct php_msgbuf)); - memcpy(messagebuffer->mtext, ZSTR_VAL(msg_var.s), ZSTR_LEN(msg_var.s) + 1); - message_len = ZSTR_LEN(msg_var.s); + messagebuffer = safe_emalloc(message_len, 1, sizeof(struct php_msgbuf)); + memcpy(messagebuffer->mtext, ZSTR_VAL(str), message_len + 1); + zend_string_release_ex(str, false); smart_str_free(&msg_var); } else { char *p; diff --git a/ext/sysvmsg/tests/gh16592.phpt b/ext/sysvmsg/tests/gh16592.phpt new file mode 100644 index 0000000000000..8490d000f8914 --- /dev/null +++ b/ext/sysvmsg/tests/gh16592.phpt @@ -0,0 +1,19 @@ +--TEST-- +msg_send() segfault when the type does not serialize as expected +--EXTENSIONS-- +sysvmsg +--FILE-- +getMessage(); +} +?> +--EXPECT-- +Test::__serialize() must return an array diff --git a/ext/sysvshm/sysvshm.c b/ext/sysvshm/sysvshm.c index 717c75337bb66..2339a85476c15 100644 --- a/ext/sysvshm/sysvshm.c +++ b/ext/sysvshm/sysvshm.c @@ -257,6 +257,12 @@ PHP_FUNCTION(shm_put_var) php_var_serialize(&shm_var, arg_var, &var_hash); PHP_VAR_SERIALIZE_DESTROY(var_hash); + if (UNEXPECTED(!shm_list_ptr->ptr)) { + smart_str_free(&shm_var); + zend_throw_error(NULL, "Shared memory block has been destroyed by the serialization function"); + RETURN_THROWS(); + } + /* insert serialized variable into shared memory */ ret = php_put_shm_data(shm_list_ptr->ptr, shm_key, shm_var.s? ZSTR_VAL(shm_var.s) : NULL, shm_var.s? ZSTR_LEN(shm_var.s) : 0); diff --git a/ext/sysvshm/tests/gh16591.phpt b/ext/sysvshm/tests/gh16591.phpt new file mode 100644 index 0000000000000..d3ece7cd796aa --- /dev/null +++ b/ext/sysvshm/tests/gh16591.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-16591 (Assertion error in shm_put_var) +--EXTENSIONS-- +sysvshm +--FILE-- + 'b']; + } +} + +$mem = shm_attach(1); +try { + shm_put_var($mem, 1, new C); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Shared memory block has been destroyed by the serialization function diff --git a/ext/xmlreader/php_xmlreader.c b/ext/xmlreader/php_xmlreader.c index 1b4bc6bcef4b1..e3ca41399355d 100644 --- a/ext/xmlreader/php_xmlreader.c +++ b/ext/xmlreader/php_xmlreader.c @@ -1122,7 +1122,13 @@ PHP_METHOD(XMLReader, expand) } if (basenode != NULL) { - NODE_GET_OBJ(node, basenode, xmlNodePtr, domobj); + /* Note: cannot use NODE_GET_OBJ here because of the wrong return type */ + domobj = Z_LIBXML_NODE_P(basenode); + if (UNEXPECTED(domobj->node == NULL || domobj->node->node == NULL)) { + php_error_docref(NULL, E_WARNING, "Couldn't fetch %s", ZSTR_VAL(Z_OBJCE_P(basenode)->name)); + RETURN_FALSE; + } + node = domobj->node->node; docp = node->doc; } diff --git a/ext/xmlreader/tests/gh16292.phpt b/ext/xmlreader/tests/gh16292.phpt new file mode 100644 index 0000000000000..f3cd37e951d5f --- /dev/null +++ b/ext/xmlreader/tests/gh16292.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-16292 (Segmentation fault in ext/xmlreader/php_xmlreader.c:1282) +--EXTENSIONS-- +dom +xmlreader +--FILE-- + +new book'; +$reader = new XMLReader(); +$reader->XML($xmlstring); +while ($reader->read()) { + if ($reader->localName == "book") { + var_dump($reader->expand($character_data)); + } +} + +?> +--EXPECTF-- +Warning: XMLReader::expand(): Couldn't fetch DOMCharacterData in %s on line %d +bool(false) + +Warning: XMLReader::expand(): Couldn't fetch DOMCharacterData in %s on line %d +bool(false) diff --git a/ext/zend_test/fiber.c b/ext/zend_test/fiber.c index 0254799e2bdb5..a4aa8498e4152 100644 --- a/ext/zend_test/fiber.c +++ b/ext/zend_test/fiber.c @@ -91,6 +91,7 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran execute_data = (zend_execute_data *) stack->top; memset(execute_data, 0, sizeof(zend_execute_data)); + execute_data->func = (zend_function *) &zend_pass_function; EG(current_execute_data) = execute_data; EG(jit_trace_num) = 0; @@ -348,4 +349,5 @@ void zend_test_fiber_init(void) zend_test_fiber_handlers = std_object_handlers; zend_test_fiber_handlers.dtor_obj = zend_test_fiber_object_destroy; zend_test_fiber_handlers.free_obj = zend_test_fiber_object_free; + zend_test_fiber_handlers.clone_obj = NULL; } diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index af45f91a5da5b..f22061b169b6d 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -20,6 +20,7 @@ #include "zend_observer.h" #include "zend_smart_str.h" #include "ext/standard/php_var.h" +#include "zend_generators.h" static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data); @@ -163,6 +164,11 @@ static void observer_show_init_backtrace(zend_execute_data *execute_data) zend_execute_data *ex = execute_data; php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name)); } - } else { + } else if (ZEND_USER_CODE(fbc->type)) { php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename)); } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 8989413317d76..100ef25800f9d 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -639,6 +639,17 @@ static ZEND_FUNCTION(zend_test_is_zend_ptr) RETURN_BOOL(is_zend_ptr((void*)addr)); } +static ZEND_FUNCTION(zend_test_log_err_debug) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str); + ZEND_PARSE_PARAMETERS_END(); + + php_log_err_with_severity(ZSTR_VAL(str), LOG_DEBUG); +} + static zend_object *zend_test_class_new(zend_class_entry *class_type) { zend_object *obj = zend_objects_new(class_type); @@ -659,7 +670,7 @@ static zend_function *zend_test_class_method_get(zend_object **object, zend_stri } memset(fptr, 0, sizeof(zend_internal_function)); fptr->type = ZEND_INTERNAL_FUNCTION; - fptr->num_args = 1; + fptr->num_args = 0; fptr->scope = (*object)->ce; fptr->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; fptr->function_name = zend_string_copy(name); @@ -682,7 +693,7 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze } memset(fptr, 0, sizeof(zend_internal_function)); fptr->type = ZEND_INTERNAL_FUNCTION; - fptr->num_args = 1; + fptr->num_args = 0; fptr->scope = ce; fptr->fn_flags = ZEND_ACC_CALL_VIA_HANDLER|ZEND_ACC_STATIC; fptr->function_name = zend_string_copy(name); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 8a605bdcd3280..547e82d60ca20 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -196,6 +196,8 @@ function zend_test_set_fmode(bool $binary): void {} function zend_test_cast_fread($stream): void {} function zend_test_is_zend_ptr(int $addr): bool {} + + function zend_test_log_err_debug(string $str): void {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 9c0735a1d6f89..bd86f090ff109 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 5d861e05edfd57c385167b11b8b1ea977ed130a2 */ + * Stub hash: 1c6384894b21ff02bbc3d6088440de6616878a7d */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -126,6 +126,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_is_zend_ptr, 0, 1, _IS ZEND_ARG_TYPE_INFO(0, addr, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_log_err_debug, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -237,6 +241,7 @@ static ZEND_FUNCTION(zend_test_set_fmode); #endif static ZEND_FUNCTION(zend_test_cast_fread); static ZEND_FUNCTION(zend_test_is_zend_ptr); +static ZEND_FUNCTION(zend_test_log_err_debug); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -302,6 +307,7 @@ static const zend_function_entry ext_functions[] = { #endif ZEND_FE(zend_test_cast_fread, arginfo_zend_test_cast_fread) ZEND_FE(zend_test_is_zend_ptr, arginfo_zend_test_is_zend_ptr) + ZEND_FE(zend_test_log_err_debug, arginfo_zend_test_log_err_debug) ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func) ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func) ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func) diff --git a/ext/zend_test/tests/gh16230.phpt b/ext/zend_test/tests/gh16230.phpt new file mode 100644 index 0000000000000..42487df5b94bb --- /dev/null +++ b/ext/zend_test/tests/gh16230.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-16230: Segfault on debug_backtrace() inside _ZendTestFiber +--EXTENSIONS-- +zend_test +--FILE-- +start(); +?> +--EXPECT-- +array(1) { + [0]=> + array(2) { + ["function"]=> + string(9) "{closure}" + ["args"]=> + array(0) { + } + } +} diff --git a/ext/zend_test/tests/gh16266.phpt b/ext/zend_test/tests/gh16266.phpt new file mode 100644 index 0000000000000..44cb7ef807c1e --- /dev/null +++ b/ext/zend_test/tests/gh16266.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-16266 (_ZendTestClass::test() segfaults on named parameter) +--EXTENSIONS-- +zend_test +--FILE-- +test('a', 'b', c: 'c'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + _ZendTestClass::test('a', 'b', c: 'c'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Unknown named parameter $c +Unknown named parameter $c diff --git a/ext/zend_test/tests/gh16294.phpt b/ext/zend_test/tests/gh16294.phpt new file mode 100644 index 0000000000000..1a85586d1def0 --- /dev/null +++ b/ext/zend_test/tests/gh16294.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-16294: Segfault in test observer on zend_pass_function +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.execute_internal=1 +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/ext/zend_test/tests/gh16388.phpt b/ext/zend_test/tests/gh16388.phpt new file mode 100644 index 0000000000000..521171f756c44 --- /dev/null +++ b/ext/zend_test/tests/gh16388.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-16388 (UB when freeing a cloned _ZendTestFiber) +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Trying to clone an uncloneable object of class _ZendTestFiber in %s:%d +%A diff --git a/ext/zend_test/tests/gh16514.phpt b/ext/zend_test/tests/gh16514.phpt new file mode 100644 index 0000000000000..fcb90f9d963a7 --- /dev/null +++ b/ext/zend_test/tests/gh16514.phpt @@ -0,0 +1,58 @@ +--TEST-- +GH-16514: Nested generator in zend_test observer +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.show_output=1 +zend_test.observer.show_init_backtrace=1 +--FILE-- + +--EXPECTF-- + + + + + + + + + + +#0 %s(%d): Foo->__destruct() +#1 %s(%d): bar() diff --git a/ext/zlib/tests/gh16326.phpt b/ext/zlib/tests/gh16326.phpt new file mode 100644 index 0000000000000..b4997368549f7 --- /dev/null +++ b/ext/zlib/tests/gh16326.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-16326 (Memory management is broken for bad dictionaries) +--EXTENSIONS-- +zlib +--FILE-- + [" ", ""]]); +} catch (ValueError $ex) { + echo $ex->getMessage(), "\n"; +} +try { + deflate_init(ZLIB_ENCODING_DEFLATE, ["dictionary" => ["hello", "wor\0ld"]]); +} catch (ValueError $ex) { + echo $ex->getMessage(), "\n"; +} +try { + deflate_init(ZLIB_ENCODING_DEFLATE, ["dictionary" => [" ", new stdClass]]); +} catch (Error $ex) { + echo $ex->getMessage(), "\n"; +} +?> +--EXPECT-- +deflate_init(): Argument #2 ($options) must not contain empty strings +deflate_init(): Argument #2 ($options) must not contain strings with null bytes +Object of class stdClass could not be converted to string diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 8f1df51c96d8d..e85db94801af0 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -807,35 +807,29 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_ if (zend_hash_num_elements(dictionary) > 0) { char *dictptr; zval *cur; - zend_string **strings = emalloc(sizeof(zend_string *) * zend_hash_num_elements(dictionary)); + zend_string **strings = safe_emalloc(zend_hash_num_elements(dictionary), sizeof(zend_string *), 0); zend_string **end, **ptr = strings - 1; ZEND_HASH_FOREACH_VAL(dictionary, cur) { - size_t i; - *++ptr = zval_get_string(cur); - if (!*ptr || ZSTR_LEN(*ptr) == 0 || EG(exception)) { - if (*ptr) { - efree(*ptr); - } - while (--ptr >= strings) { - efree(ptr); - } + ZEND_ASSERT(*ptr); + if (ZSTR_LEN(*ptr) == 0 || EG(exception)) { + do { + zend_string_release(*ptr); + } while (--ptr >= strings); efree(strings); if (!EG(exception)) { zend_argument_value_error(2, "must not contain empty strings"); } return 0; } - for (i = 0; i < ZSTR_LEN(*ptr); i++) { - if (ZSTR_VAL(*ptr)[i] == 0) { - do { - efree(ptr); - } while (--ptr >= strings); - efree(strings); - zend_argument_value_error(2, "must not contain strings with null bytes"); - return 0; - } + if (zend_str_has_nul_byte(*ptr)) { + do { + zend_string_release(*ptr); + } while (--ptr >= strings); + efree(strings); + zend_argument_value_error(2, "must not contain strings with null bytes"); + return 0; } *dictlen += ZSTR_LEN(*ptr) + 1; diff --git a/main/php_version.h b/main/php_version.h index e2f9e04ada6af..55aa348213229 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 2 -#define PHP_RELEASE_VERSION 25 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.25-dev" -#define PHP_VERSION_ID 80225 +#define PHP_RELEASE_VERSION 26 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.2.26" +#define PHP_VERSION_ID 80226 diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 732ac27c9a207..753196f5f79cd 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -1944,6 +1944,8 @@ static void php_cli_server_client_populate_request_info(const php_cli_server_cli request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL; if (NULL != (val = zend_hash_str_find(&client->request.headers, "content-type", sizeof("content-type")-1))) { request_info->content_type = Z_STRVAL_P(val); + } else { + request_info->content_type = NULL; } } /* }}} */ @@ -2271,6 +2273,7 @@ static int php_cli_server_dispatch_router(php_cli_server *server, php_cli_server } ZVAL_UNDEF(&retval); + CG(skip_shebang) = true; if (SUCCESS == zend_execute_scripts(ZEND_REQUIRE, &retval, 1, &zfd)) { if (Z_TYPE(retval) != IS_UNDEF) { decline = Z_TYPE(retval) == IS_FALSE; diff --git a/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt new file mode 100644 index 0000000000000..2c8aeff12d594 --- /dev/null +++ b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt @@ -0,0 +1,41 @@ +--TEST-- +GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface) +--INI-- +allow_url_fopen=1 +--SKIPIF-- + +--FILE-- + [ + "method" => "POST", + "header" => "Content-Type: application/x-www-form-urlencoded", + "content" => "AAAAA", + ], +]; +$context = stream_context_create($options); + +echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", context: $context); + +$options = [ + "http" => [ + "method" => "POST", + ], +]; +$context = stream_context_create($options); + +echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", context: $context); +?> +--EXPECT-- +string(5) "AAAAA" +string(0) "" diff --git a/sapi/fpm/fpm/zlog.c b/sapi/fpm/fpm/zlog.c index e5e49f8385d21..232f0cb1b2b29 100644 --- a/sapi/fpm/fpm/zlog.c +++ b/sapi/fpm/fpm/zlog.c @@ -150,6 +150,7 @@ static inline void zlog_external( } /* }}} */ +/* Returns the length if the print were complete, this can be larger than buf_size. */ static size_t zlog_buf_prefix( const char *function, int line, int flags, char *buf, size_t buf_size, int use_syslog) /* {{{ */ @@ -186,6 +187,7 @@ static size_t zlog_buf_prefix( } } + /* Important: snprintf returns the number of bytes if the print were complete. */ return len; } /* }}} */ @@ -408,6 +410,7 @@ static inline ssize_t zlog_stream_unbuffered_write( static inline ssize_t zlog_stream_buf_copy_cstr( struct zlog_stream *stream, const char *str, size_t str_len) /* {{{ */ { + ZEND_ASSERT(stream->len <= stream->buf.size); if (stream->buf.size - stream->len <= str_len && !zlog_stream_buf_alloc_ex(stream, str_len + stream->len)) { return -1; @@ -422,6 +425,7 @@ static inline ssize_t zlog_stream_buf_copy_cstr( static inline ssize_t zlog_stream_buf_copy_char(struct zlog_stream *stream, char c) /* {{{ */ { + ZEND_ASSERT(stream->len <= stream->buf.size); if (stream->buf.size - stream->len < 1 && !zlog_stream_buf_alloc_ex(stream, 1)) { return -1; } @@ -678,6 +682,17 @@ ssize_t zlog_stream_prefix_ex(struct zlog_stream *stream, const char *function, len = zlog_buf_prefix( function, line, stream->flags, stream->buf.data, stream->buf.size, stream->use_syslog); + if (!EXPECTED(len + 1 <= stream->buf.size)) { + /* If the buffer was not large enough, try with a larger buffer. + * Note that this may still truncate if the zlog_limit is reached. */ + len = MIN(len + 1, zlog_limit); + if (!zlog_stream_buf_alloc_ex(stream, len)) { + return -1; + } + zlog_buf_prefix( + function, line, stream->flags, + stream->buf.data, stream->buf.size, stream->use_syslog); + } stream->len = stream->prefix_len = len; if (stream->msg_prefix != NULL) { zlog_stream_buf_copy_cstr(stream, stream->msg_prefix, stream->msg_prefix_len); @@ -689,8 +704,8 @@ ssize_t zlog_stream_prefix_ex(struct zlog_stream *stream, const char *function, } else { char sbuf[1024]; ssize_t written; - len = zlog_buf_prefix(function, line, stream->flags, sbuf, 1024, stream->use_syslog); - written = zlog_stream_direct_write(stream, sbuf, len); + len = zlog_buf_prefix(function, line, stream->flags, sbuf, sizeof(sbuf), stream->use_syslog); + written = zlog_stream_direct_write(stream, sbuf, MIN(len, sizeof(sbuf))); if (stream->msg_prefix != NULL) { written += zlog_stream_direct_write( stream, stream->msg_prefix, stream->msg_prefix_len); diff --git a/sapi/fpm/tests/gh16628.phpt b/sapi/fpm/tests/gh16628.phpt new file mode 100644 index 0000000000000..e2df0c8cb84de --- /dev/null +++ b/sapi/fpm/tests/gh16628.phpt @@ -0,0 +1,53 @@ +--TEST-- +GH-16628 (FPM logs are getting corrupted with this log statement) +--EXTENSIONS-- +zend_test +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$tester->request()->expectEmptyBody(); +for ($i = 1; $i < 100; $i++) { + $tester->expectLogNotice("%sPHP message: " . str_repeat("a", $i)); +} +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/phpdbg/phpdbg_lexer.l b/sapi/phpdbg/phpdbg_lexer.l index eb159bdef1c7a..6245262a00598 100644 --- a/sapi/phpdbg/phpdbg_lexer.l +++ b/sapi/phpdbg/phpdbg_lexer.l @@ -82,7 +82,7 @@ ID [^ \r\n\t:#\000]+ GENERIC_ID ([^ \r\n\t:#\000"']|":\\")+|["]([^\n\000"\\]|"\\\\"|"\\"["])+["]|[']([^\n\000'\\]|"\\\\"|"\\"['])+['] ADDR [0][x][a-fA-F0-9]+ OPCODE (ZEND_|zend_)([A-Z_a-z])+ -INPUT ("\\"[#"']|["]("\\\\"|"\\"["]|[^\n\000"])+["]|[']("\\"[']|"\\\\"|[^\n\000'])+[']|[^\n\000#"'])+ +INPUT ("\\"[#"']|["]("\\\\"|"\\"["]|[^\n\000"])*["]|[']("\\"[']|"\\\\"|[^\n\000'])*[']|[^\n\000#"'])+ := yyleng = (size_t) YYCURSOR - (size_t) yytext; diff --git a/sapi/phpdbg/tests/gh16174.phpt b/sapi/phpdbg/tests/gh16174.phpt new file mode 100644 index 0000000000000..f108cd66be9a2 --- /dev/null +++ b/sapi/phpdbg/tests/gh16174.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-16174 (Empty string is an invalid expression for phpdbg-ev) +--PHPDBG-- +ev $e = "" +ev $f = '' +--EXPECT-- +prompt> +prompt> +prompt> +prompt>