Skip to content

Commit

Permalink
Merge pull request #6645 from pmmp/explode-limit
Browse files Browse the repository at this point in the history
5.25.2
  • Loading branch information
dktapps authored Mar 4, 2025
2 parents e3e0c14 + b1e63e5 commit e8824a3
Show file tree
Hide file tree
Showing 21 changed files with 143 additions and 26 deletions.
2 changes: 1 addition & 1 deletion build/dump-version-info.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
*/
$options = [
"base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{
Expand Down
6 changes: 6 additions & 0 deletions changelogs/5.25.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ Released 26th February 2025.
- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero.
- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.)
- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join.

# 5.25.2
Released 4th March 2025.

## Fixes
- Added limits to various `explode()` calls.
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rules:
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
- pocketmine\phpstan\rules\ExplodeLimitRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule

Expand Down
2 changes: 1 addition & 1 deletion src/PocketMine.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ function server(){
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
if($composerGitHash !== null){
//we can't verify dependency versions if we were installed without using git
$currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
$currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0];
if($currentGitHash !== $composerGitHash){
critical_error("Composer dependencies and/or autoloader are out of sync.");
critical_error("- Current revision is $currentGitHash");
Expand Down
2 changes: 1 addition & 1 deletion src/VersionInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.25.2";
public const IS_DEVELOPMENT_BUILD = true;
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";

/**
Expand Down
3 changes: 2 additions & 1 deletion src/block/tile/Sign.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ class Sign extends Spawnable{

/**
* @return string[]
* @deprecated
*/
public static function fixTextBlob(string $blob) : array{
return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4);
return array_slice(array_pad(explode("\n", $blob, limit: 5), 4, ""), 0, 4);
}

protected SignText $text;
Expand Down
2 changes: 1 addition & 1 deletion src/block/utils/SignText.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function __construct(?array $lines = null, ?Color $baseColor = null, bool
* @throws \InvalidArgumentException if the text is not valid UTF-8
*/
public static function fromBlob(string $blob, ?Color $baseColor = null, bool $glowing = false) : SignText{
return new self(array_slice(array_pad(explode("\n", $blob), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
return new self(array_slice(array_pad(explode("\n", $blob, limit: self::LINE_COUNT + 1), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/command/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use function explode;
use function implode;
use function str_replace;
use const PHP_INT_MAX;

abstract class Command{

Expand Down Expand Up @@ -113,7 +114,7 @@ public function setPermissions(array $permissions) : void{
}

public function setPermission(?string $permission) : void{
$this->setPermissions($permission === null ? [] : explode(";", $permission));
$this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX));
}

public function testPermission(CommandSender $target, ?string $permission = null) : bool{
Expand Down
3 changes: 2 additions & 1 deletion src/command/defaults/HelpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use function min;
use function sort;
use function strtolower;
use const PHP_INT_MAX;
use const SORT_FLAG_CASE;
use const SORT_NATURAL;

Expand Down Expand Up @@ -108,7 +109,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args

$usage = $cmd->getUsage();
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
->prefix(TextFormat::GOLD));

$aliases = $cmd->getAliases();
Expand Down
6 changes: 5 additions & 1 deletion src/command/defaults/ParticleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ private function getParticle(string $name, ?string $data = null) : ?Particle{
break;
case "blockdust":
if($data !== null){
$d = explode("_", $data);
//to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts
//this allows the 4th argument to be processed normally if given without forcing it to also consume
//any unexpected parts
//we probably ought to error in this case, but this will do for now
$d = explode("_", $data, limit: 5);
if(count($d) >= 3){
return new DustParticle(new Color(
((int) $d[0]) & 0xff,
Expand Down
2 changes: 1 addition & 1 deletion src/console/ConsoleCommandSender.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function sendMessage(Translatable|string $message) : void{
$message = $this->getLanguage()->translate($message);
}

foreach(explode("\n", trim($message)) as $line){
foreach(explode("\n", trim($message), limit: PHP_INT_MAX) as $line){
Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line));
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/item/LegacyStringToItemParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ public function getMappings() : array{
*/
public function parse(string $input) : Item{
$key = $this->reprocess($input);
$b = explode(":", $key);
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given a string like 351:4:1
$b = explode(":", $key, limit: 3);

if(!isset($b[1])){
$meta = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/lang/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static function getLanguageList(string $path = "") : array{

foreach($files as $file){
try{
$code = explode(".", $file)[0];
$code = explode(".", $file, limit: 2)[0];
$strings = self::loadLang($path, $code);
if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){
$result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME];
Expand Down
6 changes: 4 additions & 2 deletions src/network/mcpe/JwtUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ final class JwtUtils{
* @throws JwtException
*/
public static function split(string $jwt) : array{
$v = explode(".", $jwt);
//limit of 4 allows us to detect too many parts without having to split the string up into a potentially large
//number of parts
$v = explode(".", $jwt, limit: 4);
if(count($v) !== 3){
throw new JwtException("Expected exactly 3 JWT parts, got " . count($v));
throw new JwtException("Expected exactly 3 JWT parts delimited by a period");
}
return [$v[0], $v[1], $v[2]]; //workaround phpstan bug
}
Expand Down
4 changes: 3 additions & 1 deletion src/permission/BanEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ public static function fromString(string $str) : ?BanEntry{
return null;
}

$parts = explode("|", trim($str));
//we expect at most 5 parts, but accept 6 in case of an extra unexpected delimiter
//we don't want to include unexpected data into the ban reason
$parts = explode("|", trim($str), limit: 6);
$entry = new BanEntry(trim(array_shift($parts)));
if(count($parts) > 0){
$entry->setCreated(self::parseDate(array_shift($parts)));
Expand Down
9 changes: 5 additions & 4 deletions src/utils/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
use const JSON_BIGINT_AS_STRING;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const PHP_INT_MAX;
use const YAML_UTF8_ENCODING;

/**
Expand Down Expand Up @@ -339,7 +340,7 @@ public function __unset($k){
}

public function setNested(string $key, mixed $value) : void{
$vars = explode(".", $key);
$vars = explode(".", $key, limit: PHP_INT_MAX);
$base = array_shift($vars);

if(!isset($this->config[$base])){
Expand All @@ -366,7 +367,7 @@ public function getNested(string $key, mixed $default = null) : mixed{
return $this->nestedCache[$key];
}

$vars = explode(".", $key);
$vars = explode(".", $key, limit: PHP_INT_MAX);
$base = array_shift($vars);
if(isset($this->config[$base])){
$base = $this->config[$base];
Expand All @@ -390,7 +391,7 @@ public function removeNested(string $key) : void{
$this->nestedCache = [];
$this->changed = true;

$vars = explode(".", $key);
$vars = explode(".", $key, limit: PHP_INT_MAX);

$currentNode = &$this->config;
while(count($vars) > 0){
Expand Down Expand Up @@ -495,7 +496,7 @@ private function fillDefaults(array $default, array &$data) : int{
*/
public static function parseList(string $content) : array{
$result = [];
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content)), limit: PHP_INT_MAX) as $v){
$v = trim($v);
if($v === ""){
continue;
Expand Down
6 changes: 4 additions & 2 deletions src/utils/Internet.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
use const CURLOPT_SSL_VERIFYHOST;
use const CURLOPT_SSL_VERIFYPEER;
use const CURLOPT_TIMEOUT_MS;
use const PHP_INT_MAX;
use const SOCK_DGRAM;
use const SOL_UDP;

Expand Down Expand Up @@ -227,9 +228,10 @@ public static function simpleCurl(string $page, float $timeout = 10, array $extr
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$headers = [];
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
//TODO: explore if we can set these limits lower
foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){
$headerGroup = [];
foreach(explode("\r\n", $rawHeaderGroup) as $line){
foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){
$nameValue = explode(":", $line, 2);
if(isset($nameValue[1])){
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ public static function getReferenceCount(object $value, bool $includeCurrent = t
debug_zval_dump($value);
$contents = ob_get_contents();
if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here");
$ret = explode("\n", $contents);
$ret = explode("\n", $contents, limit: 2);
ob_end_clean();

if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
Expand Down
11 changes: 7 additions & 4 deletions src/world/generator/FlatGeneratorOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\item\LegacyStringToItemParser;
use pocketmine\item\LegacyStringToItemParserException;
use pocketmine\world\World;
use function array_map;
use function explode;
use function preg_match;
use function preg_match_all;
use const PHP_INT_MAX;

/**
* @internal
Expand Down Expand Up @@ -70,7 +72,7 @@ public function getExtraOptions() : array{ return $this->extraOptions; }
*/
public static function parseLayers(string $layers) : array{
$result = [];
$split = array_map('\trim', explode(',', $layers));
$split = array_map('\trim', explode(',', $layers, limit: World::Y_MAX - World::Y_MIN));
$y = 0;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($split as $line){
Expand All @@ -96,7 +98,7 @@ public static function parseLayers(string $layers) : array{
* @throws InvalidGeneratorOptionsException
*/
public static function parsePreset(string $presetString) : self{
$preset = explode(";", $presetString);
$preset = explode(";", $presetString, limit: 4);
$blocks = $preset[1] ?? "";
$biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS);
$optionsString = $preset[3] ?? "";
Expand All @@ -109,9 +111,10 @@ public static function parsePreset(string $presetString) : self{
$params = true;
if($matches[3][$i] !== ""){
$params = [];
$p = explode(" ", $matches[3][$i]);
$p = explode(" ", $matches[3][$i], limit: PHP_INT_MAX);
foreach($p as $k){
$k = explode("=", $k);
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given e.g. treecount=20=1
$k = explode("=", $k, limit: 3);
if(isset($k[1])){
$params[$k[0]] = $k[1];
}
Expand Down
92 changes: 92 additions & 0 deletions tests/phpstan/rules/ExplodeLimitRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/

declare(strict_types=1);

namespace pocketmine\phpstan\rules;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function count;

/**
* @phpstan-implements Rule<FuncCall>
*/
final class ExplodeLimitRule implements Rule{
private ReflectionProvider $reflectionProvider;

public function __construct(
ReflectionProvider $reflectionProvider
){
$this->reflectionProvider = $reflectionProvider;
}

public function getNodeType() : string{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope) : array{
if(!$node->name instanceof Name){
return [];
}

if(!$this->reflectionProvider->hasFunction($node->name, $scope)){
return [];
}

$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);

if($functionReflection->getName() !== 'explode'){
return [];
}

$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$node->getArgs(),
$functionReflection->getVariants(),
$functionReflection->getNamedArgumentsVariants(),
);

$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);

if($normalizedFuncCall === null){
return [];
}

$count = count($normalizedFuncCall->getArgs());
if($count !== 3){
return [
RuleErrorBuilder::message('The $limit parameter of explode() must be set to prevent malicious client data wasting resources.')
->identifier("pocketmine.explode.limit")
->build()
];
}

return [];
}
}
2 changes: 1 addition & 1 deletion tools/generate-bedrock-data-from-packets.php
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ function main(array $argv) : int{
}

foreach($packets as $lineNum => $line){
$parts = explode(':', $line);
$parts = explode(':', $line, limit: 3);
if(count($parts) !== 2){
fwrite(STDERR, 'Wrong packet format at line ' . ($lineNum + 1) . ', expected read:base64 or write:base64');
return 1;
Expand Down

0 comments on commit e8824a3

Please sign in to comment.