Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 3a8c350f3d Bump the github-actions group with 2 updates (#1720) 2026-09-01 16:46:54 -04:00
11 changed files with 121 additions and 95 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v7
- name: Check Spelling
uses: rojopolis/spellcheck-github-actions@0.63.0
uses: rojopolis/spellcheck-github-actions@0.66.0
with:
config_path: .github/spellcheck-settings.yml
task_name: Markdown
+5 -5
View File
@@ -85,13 +85,13 @@ jobs:
uses: actions/checkout@v7
- name: Start Redis standalone image
uses: hoverkraft-tech/compose-action@v3.0.0
uses: hoverkraft-tech/compose-action@v3.1.0
with:
compose-file: .github/docker-compose.yml
services: ${{ env.DOCKER_SERVICE }}
- name: Start Redis unprotected image
uses: hoverkraft-tech/compose-action@v3.0.0
uses: hoverkraft-tech/compose-action@v3.1.0
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -99,7 +99,7 @@ jobs:
- name: Start Redis stack image
id: stack_infra
uses: hoverkraft-tech/compose-action@v3.0.0
uses: hoverkraft-tech/compose-action@v3.1.0
if: ${{ matrix.redis >= '7.2' && matrix.redis < '8.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -107,7 +107,7 @@ jobs:
- name: Start Redis cluster image
id: cluster_infra
uses: hoverkraft-tech/compose-action@v3.0.0
uses: hoverkraft-tech/compose-action@v3.1.0
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -115,7 +115,7 @@ jobs:
- name: Start Redis sentinels image
id: sentinel_infra
uses: hoverkraft-tech/compose-action@v3.0.0
uses: hoverkraft-tech/compose-action@v3.1.0
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
+1 -2
View File
@@ -1,10 +1,9 @@
## Changelog
## Unreleased
## Unlreleads
### Added
### Changed
### Fixed
- Fixed CRLF command injection / smuggling in `AbstractAggregateConnection::write()` (CVE GHSA-w6f5-v2h6-g786, CWE-93)
- Fixed RESP3 double parsing returning positive `INF` for `-inf` payloads (#1716)
## v3.6.0 (2026-08-14)
-3
View File
@@ -158,9 +158,6 @@ abstract class Command implements CommandInterface
/**
* {@inheritDoc}
*
* @deprecated Not binary-safe; see CommandInterface::deserializeCommand().
* Scheduled for removal in the next major.
*/
public static function deserializeCommand(string $serializedCommand): CommandInterface
{
-6
View File
@@ -98,12 +98,6 @@ interface CommandInterface
*
* @param string $serializedCommand
* @return static
*
* @deprecated Not binary-safe: it re-parses on "\r\n" and ignores RESP bulk-length
* prefixes, so any argument containing "\r\n" is corrupted, and it
* instantiates a command class from the parsed input. Never call it on
* untrusted or serialized data (see CVE GHSA-w6f5-v2h6-g786). Scheduled
* for removal in the next major.
*/
public static function deserializeCommand(string $serializedCommand): CommandInterface;
}
-6
View File
@@ -153,12 +153,6 @@ final class RawCommand implements CommandInterface
return $buffer;
}
/**
* {@inheritDoc}
*
* @deprecated Not binary-safe; see CommandInterface::deserializeCommand().
* Scheduled for removal in the next major.
*/
public static function deserializeCommand(string $serializedCommand): CommandInterface
{
if ($serializedCommand[0] !== '*') {
+15 -8
View File
@@ -12,8 +12,8 @@
namespace Predis\Connection;
use Predis\Command\Command;
use Predis\Command\CommandInterface;
use Predis\NotSupportedException;
abstract class AbstractAggregateConnection implements AggregateConnectionInterface
{
@@ -77,12 +77,19 @@ abstract class AbstractAggregateConnection implements AggregateConnectionInterfa
*/
public function write(string $buffer): void
{
// Refuse raw buffers: re-splitting them on "\r\n" ignored RESP length
// prefixes and let CRLF-smuggled commands be routed to a node
// (CVE GHSA-w6f5-v2h6-g786). Pipelines write each command individually.
throw new NotSupportedException(
'Aggregate connections cannot write a raw command buffer; '
. 'route each command through writeRequest() instead.'
);
$rawCommands = [];
$explodedBuffer = explode("\r\n", trim($buffer));
while (!empty($explodedBuffer)) {
$argsLen = (int) explode('*', $explodedBuffer[0])[1];
$cmdLen = ($argsLen * 2) + 1;
$rawCommands[] = array_splice($explodedBuffer, 0, $cmdLen);
}
foreach ($rawCommands as $command) {
$command = implode("\r\n", $command) . "\r\n";
$commandObj = Command::deserializeCommand($command);
$this->getConnectionByCommand($commandObj)->write($command);
}
}
}
@@ -474,31 +474,46 @@ class PredisClusterTest extends PredisTestCase
}
/**
* Regression guard for CVE GHSA-w6f5-v2h6-g786 (CWE-93): an aggregate connection
* must refuse a raw, already-serialized command buffer instead of re-splitting it
* on "\r\n". The old parser ignored RESP bulk-length prefixes, so CRLF sequences
* smuggled into a value or key were parsed as extra commands and routed to a node.
*
* @group disconnected
*/
public function testWriteRejectsRawCommandBuffer(): void
public function testWrite(): void
{
// A single GET whose key carries a smuggled FLUSHDB payload; the old code
// would have re-parsed and routed the FLUSHDB, this must route nothing.
$command = new GET();
$command->setArguments(["slug:PAD\r\n*1\r\n\$7\r\nFLUSHDB"]);
$command1 = new GET();
$command1->setArguments(['arg1']);
$command2 = new GET();
$command2->setArguments(['arg2']);
$command3 = new GET();
$command3->setArguments(['arg3']);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:7001');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:7002');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:7003');
$connection1
->expects($this->exactly(3))
->method('write')
->withConsecutive(
[$command1->serializeCommand()],
[$command2->serializeCommand()],
[$command3->serializeCommand()]
);
$connection2
->expects($this->never())
->method('write');
$connection3
->expects($this->never())
->method('write');
$cluster = new PredisCluster(new Parameters());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Aggregate connections cannot write a raw command buffer');
$cluster->write($command->serializeCommand());
$cluster->write($command1->serializeCommand() . $command2->serializeCommand() . $command3->serializeCommand());
}
}
@@ -1634,33 +1634,46 @@ class RedisClusterTest extends PredisTestCase
}
/**
* Regression guard for CVE GHSA-w6f5-v2h6-g786 (CWE-93): an aggregate connection
* must refuse a raw, already-serialized command buffer instead of re-splitting it
* on "\r\n". The old parser ignored RESP bulk-length prefixes, so CRLF sequences
* smuggled into a value or key were parsed as extra commands and routed to a node.
*
* @group disconnected
*/
public function testWriteRejectsRawCommandBuffer(): void
public function testWrite(): void
{
// A single GET whose key carries a smuggled FLUSHDB payload; the old code
// would have re-parsed and routed the FLUSHDB, this must route nothing.
$command = new Command\Redis\GET();
$command->setArguments(["slug:PAD\r\n*1\r\n\$7\r\nFLUSHDB"]);
$command1 = new Command\Redis\GET();
$command1->setArguments(['arg1']);
$command2 = new Command\Redis\GET();
$command2->setArguments(['arg2']);
$command3 = new Command\Redis\GET();
$command3->setArguments(['arg3']);
$factory = $this->getMockBuilder(FactoryInterface::class)->getMock();
$connection1 = $this->getMockConnection('tcp://127.0.0.1:7001');
$connection2 = $this->getMockConnection('tcp://127.0.0.1:7002');
$connection3 = $this->getMockConnection('tcp://127.0.0.1:7003');
$connection1
->expects($this->never())
->method('write');
->expects($this->once())
->method('write')
->with($command3->serializeCommand());
$connection2
->expects($this->once())
->method('write')
->with($command2->serializeCommand());
$connection3
->expects($this->once())
->method('write')
->with($command1->serializeCommand());
$cluster = new RedisCluster($factory, new Parameters());
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Aggregate connections cannot write a raw command buffer');
$cluster->write($command->serializeCommand());
$cluster->write($command1->serializeCommand() . $command2->serializeCommand() . $command3->serializeCommand());
}
}
@@ -1459,40 +1459,41 @@ repl_backlog_histlen:12978
}
/**
* Regression guard for CVE GHSA-w6f5-v2h6-g786 (CWE-93): an aggregate connection
* must refuse a raw, already-serialized command buffer instead of re-splitting it
* on "\r\n". The old parser ignored RESP bulk-length prefixes, so CRLF sequences
* smuggled into a value or key were parsed as extra commands and routed to a node.
*
* @group disconnected
*/
public function testWriteRejectsRawCommandBuffer(): void
public function testWrite(): void
{
// A single command whose key carries a smuggled FLUSHDB payload; the old code
// would have re-parsed and routed the FLUSHDB, this must route nothing.
$command = new Command\Redis\Json\JSONGET();
$command->setArguments(["slug:PAD\r\n*1\r\n\$7\r\nFLUSHDB"]);
$command1 = new Command\Redis\Json\JSONGET();
$command1->setArguments(['arg1']);
$command2 = new Command\Redis\Json\JSONGET();
$command2->setArguments(['arg2']);
$command3 = new Command\Redis\Json\JSONGET();
$command3->setArguments(['arg3']);
$master = $this->getMockConnection('tcp://127.0.0.1:6379?role=master');
$slave1 = $this->getMockConnection('tcp://127.0.0.1:6380?role=slave');
$master
->expects($this->never())
->method('write');
$slave1
->expects($this->never())
->method('write');
$master
->expects($this->exactly(3))
->method('write')
->withConsecutive(
[$command1->serializeCommand()],
[$command2->serializeCommand()],
[$command3->serializeCommand()]
);
$replication = new MasterSlaveReplication();
$replication->add($master);
$replication->add($slave1);
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Aggregate connections cannot write a raw command buffer');
$replication->write($command->serializeCommand());
$replication->write($command1->serializeCommand() . $command2->serializeCommand() . $command3->serializeCommand());
}
/**
@@ -1975,19 +1975,18 @@ class SentinelReplicationTest extends PredisTestCase
}
/**
* Regression guard for CVE GHSA-w6f5-v2h6-g786 (CWE-93): an aggregate connection
* must refuse a raw, already-serialized command buffer instead of re-splitting it
* on "\r\n". The old parser ignored RESP bulk-length prefixes, so CRLF sequences
* smuggled into a value or key were parsed as extra commands and routed to a node.
*
* @group disconnected
*/
public function testWriteRejectsRawCommandBuffer(): void
public function testWrite(): void
{
// A single command whose argument carries a smuggled FLUSHDB payload; the old
// code would have re-parsed and routed the FLUSHDB, this must route nothing.
$command = new Command\Redis\Search\FTSEARCH();
$command->setArguments(["idx:PAD\r\n*1\r\n\$7\r\nFLUSHDB", '*']);
$command1 = new Command\Redis\Search\FTSEARCH();
$command1->setArguments(['arg1', '*']);
$command2 = new Command\Redis\Search\FTSEARCH();
$command2->setArguments(['arg2', '*']);
$command3 = new Command\Redis\Search\FTSEARCH();
$command3->setArguments(['arg3', '*']);
$sentinel = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
$master = $this->getMockConnection('tcp://127.0.0.1:6379?role=master');
@@ -1996,22 +1995,29 @@ class SentinelReplicationTest extends PredisTestCase
$factory = new Connection\Factory();
$master
->expects($this->never())
->method('write');
->expects($this->exactly(3))
->method('isConnected')
->willReturn(true);
$slave
->expects($this->never())
->method('write');
$master
->expects($this->exactly(3))
->method('write')
->withConsecutive(
[$command1->serializeCommand()],
[$command2->serializeCommand()],
[$command3->serializeCommand()]
);
$replication = new SentinelReplication('svc', [$sentinel], $factory, $strategy);
$replication->add($master);
$replication->add($slave);
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Aggregate connections cannot write a raw command buffer');
$replication->write($command->serializeCommand());
$replication->write($command1->serializeCommand() . $command2->serializeCommand() . $command3->serializeCommand());
}
public function connectionsProvider(): array