Repository navigation
chore(deps): update dependency phpunit/phpunit to v13.4.4 [security] - #705
Merged
vincentchalamon merged 1 commit intoNov 1, 2026
Merged
Conversation
vincentchalamon
deleted the
renovate/packagist-phpunit-phpunit-vulnerability
branch
November 1, 2026 22:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
13.4.0→13.4.4PHPUnit vulnerable to code execution by other local users via shared temporary directory in PHPT code coverage handling
CVE-2026-119193 / GHSA-9vw2-f4vp-x7gf
More information
Details
Summary
When PHPT tests are run with code coverage, PHPUnit 13.4.0 to 13.4.3 writes the code of each PHPT test to a directory with a fixed name below the operating system's path used for temporary files, for instance
/tmp/phpunit-phpton Linux, and executes it from there. PHPUnit uses this directory even when it was created by another user.On a system where the path used for temporary files is shared between users, another local user can create this directory before the developer runs PHPUnit, replace the code of a PHPT test before it is executed, and thereby execute arbitrary code with the privileges of the developer. The test still passes and the code coverage report is complete, so the attack is not visible.
The impact is full code execution as the user who runs PHPUnit. The likelihood is low: the attacker needs an account on the same system and must create the directory before the developer runs PHPT tests with code coverage. macOS and Windows use per-user temporary directories by default and are not affected in their default configuration. PHPUnit 12.5 and earlier are not affected.
Details
When code coverage is collected for a PHPT test, PHPUnit writes two temporary files: a
.phpfile with the code of the PHPT test (the "job" file), which the child process executes, and a.coveragefile, to which the child process writes the code coverage data it collected.Since PHPUnit 13.4.0, these files are created in a directory with a fixed name:
TestCase::coverageFilesDirectory()returnssys_get_temp_dir() . '/phpunit-phpt'phpunit_phpt_<random>.phpandphpunit_phpt_<random>.coveragein this directoryBefore a PHPT test is run with code coverage, this directory is created using
Filesystem::createDirectory().Filesystem::createDirectory()returnstruewhen the directory already exists, without checking its owner or its permissions. When it does not exist, it is created with mode0777(subject to the umask).Renderer::renderForCoverage()then writes the job file usingfile_put_contents(). The child process loads Composer's autoloader, the bootstrap script, and the code coverage driver, and then executes the job file usingrequire.When another user created the directory, that user owns it. The owner of a directory can list its contents and rename, delete, or replace every file in it, regardless of who owns the file. The directory does not have the sticky bit set, so protections such as Linux's
fs.protected_regularandfs.protected_symlinksdo not apply either. The random part of the file names therefore does not prevent the attack: the attacker waits for the job file to appear, renames it, and puts a file with their own code in its place. The attacker's code can then execute the original job file, so that the test passes and the code coverage report is complete.The
.coveragefile can be replaced in the same way.TestCase::cleanupForCoverage()only allows objects of PHPUnit's class for raw code coverage data to be unserialized from it, so this only allows the falsification of code coverage data.After the test, PHPUnit deletes both files, but not the directory. Once PHPUnit has created the directory for the developer, other users cannot write to it, unless the developer's umask allows this.
PHPUnit 12.5 and earlier are not affected: these versions write the temporary files next to the PHPT test file.
PHPUnit 13.4.4 creates the directory for the temporary files of PHPT tests with an unpredictable name and with permissions that only allow the current user to access it. A directory that already exists is not used.
Proof of concept
The following steps reproduce the issue on a Linux system with two users,
alice(the developer) andmallory(the attacker), that share/tmp. They require PHP 8.4.1 or later with theposixextension and a code coverage driver (PCOV or Xdebug) as well as Composer.As
root, create the two users and make sure that/tmphas its default permissions (1777) and that/tmp/phpunit-phptdoes not exist:As
alice, create a project with a PHPT test and a source file to collect code coverage for:~/project/src/Greeter.php:~/project/tests/greeter.phpt:~/project/phpunit.xml:As
mallory, save the following script as~/attack.phpand run it usingphp ~/attack.php. It creates/tmp/phpunit-phpt, waits for job files to appear in it, and replaces each of them with code that records the identity of the user that executes it and then executes the original job file:As
alice, run the PHPT test with code coverage (when Xdebug is used, prefix the command withXDEBUG_MODE=coverage):PHPUnit reports
OK (1 test, 1 assertion), and the code coverage report shows thatGreeter::greet()is covered. Nothing in the output indicates that the code of the test was replaced.Check which user executed the code that
malloryput in place:The file is owned by
aliceand contains the output ofidforalice, for instance:Impact
This is a local privilege escalation (CWE-379: Creation of Temporary File in Directory with Incorrect Permissions). A local user can execute arbitrary code with the privileges of another user who runs PHPT tests with code coverage. This gives the attacker access to everything the developer can access, for instance source code, credentials, and SSH keys.
Affected are users of PHPUnit 13.4.0 to 13.4.3 when all of the following apply:
sys_get_temp_dir()) is shared between users. This is the case on Linux and other Unix-like systems with a shared/tmp, unless per-user temporary directories are used, for instance through systemd'sPrivateTmp,pam_namespace, or a per-userTMPDIR. On macOS and Windows, the path used for temporary files is specific to the user by defaultSystems on which no other user, including system accounts that run services, can create files in the path used for temporary files are not affected. This is typically the case for a container or an ephemeral CI runner that is used by a single user.
Severity
CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Release Notes
sebastianbergmann/phpunit (phpunit/phpunit)
v13.4.4: PHPUnit 13.4.4Compare Source
Fixed
testSuiteFinishedtwice for a test class when one of its before-first-test methods errors or failstearDown()of a test that erroredLearn how to install or update PHPUnit 13.4 in the documentation.
Keep up to date with PHPUnit:
v13.4.3: PHPUnit 13.4.3Compare Source
Fixed
CodeCoverageorFacade#[Before]method, skips the test, the remaining hook methods of the same kind are still called, so the test is reported as errored instead of skipped when one of them errorsEfor the test class instead of one for each of its tests, only one of its tests is counted as run, and the test that is run next is shown as an errorrequirements is not met
setUp(), and reports the test that is run after it as aborted with the message of the incomplete testsetUp()raises an exception or the global state cannot be backed uptearDown()fails after the test errored, with its last outcome instead of its most severe one, and with more than one end when the preparation of the test errored]]>, bytes that are not valid UTF-8, or characters that are not allowed in XML, for instance the escape character of an ANSIescape sequence
setUp(), with an empty ID before the start of the testLearn how to install or update PHPUnit 13.4 in the documentation.
Keep up to date with PHPUnit:
v13.4.2: PHPUnit 13.4.2Compare Source
Fixed
expectUserDeprecationMessage()andexpectUserDeprecationMessageMatches()did not see the deprecations triggered by an attempt of a retried test and saw those of the previously run test instead#[Retry]does not retry the test,--repeatdoes not skip the remaining repetitions,tearDown()sees the wrong status, and code coverage records the test as successfulwith()were matched by position instead of by name, so an expectation such as->with(b: 7)compared the expected value with the first parameter of the method and the parameter named$bwas not checkedwithParameterSetsInOrder(),withParameterSetsInAnyOrder(), andwithParameterSetsInPartialOrder()were ignored, so a parameter set such as['b' => 7]compared the expected value with the first parameter of the method and the parameternamed
$bwas not checkedLearn how to install or update PHPUnit 13.4 in the documentation.
Keep up to date with PHPUnit:
v13.4.1: PHPUnit 13.4.1Compare Source
Changed
Fixed
TestSuite::runTests()doubles garbage collector time whenzend.exception_ignore_argsis offsetUpBeforeClass()andtearDownAfterClass()were run twice for a test class that is the only test class in a test suite of the XML configuration file that has the name of that test class--ARGS--section of a PHPT test that start with-were interpreted by PHP instead of being passed to the test when the test also has a--STDIN--sectionLearn how to install or update PHPUnit 13.4 in the documentation.
Keep up to date with PHPUnit:
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.