Fix parallel building exploit via race condition (#141)

Co-authored-by: hdmaniak <bombo15.forum@gmail.com>
This commit is contained in:
hdmaniak2
2026-03-24 07:48:54 +01:00
committed by GitHub
parent a7bdac8392
commit e83985e7f7
2 changed files with 136 additions and 99 deletions
+124 -99
View File
@@ -309,6 +309,9 @@ class Building {
public function loadBuilding() {
global $database,$village,$session;
$this->basic = 0;
$this->inner = 0;
$this->plus = 0;
$this->buildArray = $database->getJobs($village->wid);
$this->allocated = count($this->buildArray);
if($this->allocated > 0) {
@@ -344,131 +347,153 @@ class Building {
private function upgradeBuilding($id) {
global $database, $village, $session, $logging, ${'bid'.$village->resarray['f'.$id.'t']};
if($this->allocated < $this->maxConcurrent) {
$uprequire = $this->resourceRequired($id,$village->resarray['f'.$id.'t']);
$time = time() + $uprequire['time'];
$bindicate = $this->canBuild($id,$village->resarray['f'.$id.'t']);
// don't allow building above max levels and don't allow building if it's in demolition
if (in_array($bindicate, [1, 2, 3, 10, 11])) {
header("Location: dorf2.php");
exit;
}
$loop = ($bindicate == 9 ? 1 : 0);
$loopsame = 0;
if($loop == 1) {
foreach($this->buildArray as $build) {
if($build['field'] == $id) {
$loopsame++;
$uprequire = $this->resourceRequired($id, $village->resarray['f'.$id.'t'], ($loopsame > 0 ? 2 : 1));
}
if(!$database->getBuildLock($village->wid)) return;
try {
$this->loadBuilding();
if($this->allocated < $this->maxConcurrent) {
$uprequire = $this->resourceRequired($id,$village->resarray['f'.$id.'t']);
$time = time() + $uprequire['time'];
$bindicate = $this->canBuild($id,$village->resarray['f'.$id.'t']);
// don't allow building above max levels and don't allow building if it's in demolition
if (in_array($bindicate, [1, 2, 3, 10, 11])) {
$database->releaseBuildLock($village->wid);
header("Location: dorf2.php");
exit;
}
if($session->tribe == 1 || ALLOW_ALL_TRIBE) {
if($id >= 19) {
foreach($this->buildArray as $build) {
if($build['field'] >= 19) {
$time = $build['timestamp'] + $uprequire['time'];
$loop = ($bindicate == 9 ? 1 : 0);
$loopsame = 0;
if($loop == 1) {
foreach($this->buildArray as $build) {
if($build['field'] == $id) {
$loopsame++;
$uprequire = $this->resourceRequired($id, $village->resarray['f'.$id.'t'], ($loopsame > 0 ? 2 : 1));
}
}
if($session->tribe == 1 || ALLOW_ALL_TRIBE) {
if($id >= 19) {
foreach($this->buildArray as $build) {
if($build['field'] >= 19) {
$time = $build['timestamp'] + $uprequire['time'];
}
}
}
else {
foreach($this->buildArray as $build) {
if($build['field'] <= 18) {
$time = $build['timestamp'] + $uprequire['time'];
}
}
}
}
else {
foreach($this->buildArray as $build) {
if($build['field'] <= 18) {
$time = $build['timestamp'] + $uprequire['time'];
}
}
$time = $this->buildArray[0]['timestamp'] + $uprequire['time'];
}
}
else {
$time = $this->buildArray[0]['timestamp'] + $uprequire['time'];
$level = $database->getResourceLevel($village->wid);
if($database->addBuilding($village->wid, $id, $village->resarray['f'.$id.'t'], $loop, $time + ($loop == 1 ? ceil(60 / SPEED) : 0), 0, $level['f'.$id] + 1 + count($database->getBuildingByField($village->wid, $id)))) {
$database->modifyResource($village->wid, $uprequire['wood'], $uprequire['clay'], $uprequire['iron'], $uprequire['crop'], 0);
$logging->addBuildLog($village->wid, self::procResType($village->resarray['f'.$id.'t']), ($village->resarray['f'.$id] + ($loopsame > 0 ? 2 : 1)), 0);
$this->redirect($id);
}
}
$level = $database->getResourceLevel($village->wid);
if($database->addBuilding($village->wid, $id, $village->resarray['f'.$id.'t'], $loop, $time + ($loop == 1 ? ceil(60 / SPEED) : 0), 0, $level['f'.$id] + 1 + count($database->getBuildingByField($village->wid, $id)))) {
$database->modifyResource($village->wid, $uprequire['wood'], $uprequire['clay'], $uprequire['iron'], $uprequire['crop'], 0);
$logging->addBuildLog($village->wid, self::procResType($village->resarray['f'.$id.'t']), ($village->resarray['f'.$id] + ($loopsame > 0 ? 2 : 1)), 0);
$this->redirect($id);
}
} finally {
$database->releaseBuildLock($village->wid);
}
}
private function downgradeBuilding($id) {
global $database, $village, $session, $logging;
if($this->allocated < $this->maxConcurrent) {
$name = "bid".$village->resarray['f'.$id.'t'];
global $$name;
$dataarray = $$name;
$time = time() + round($dataarray[$village->resarray['f'.$id]-1]['time'] / 4);
$loop = 0;
if($this->inner == 1 || $this->basic == 1) {
if(($session->plus || $village->resarray['f'.$id.'t']==40)&& $this->plus == 0) {
$loop = 1;
if(!$database->getBuildLock($village->wid)) return;
try {
$this->loadBuilding();
if($this->allocated < $this->maxConcurrent) {
$name = "bid".$village->resarray['f'.$id.'t'];
global $$name;
$dataarray = $$name;
$time = time() + round($dataarray[$village->resarray['f'.$id]-1]['time'] / 4);
$loop = 0;
if($this->inner == 1 || $this->basic == 1) {
if(($session->plus || $village->resarray['f'.$id.'t']==40)&& $this->plus == 0) {
$loop = 1;
}
}
}
if($loop == 1) {
if($session->tribe == 1 || ALLOW_ALL_TRIBE) {
if($id >= 19) {
foreach($this->buildArray as $build) {
if($build['field'] >= 19) {
$time = $build['timestamp'] + round($dataarray[$village->resarray['f'.$id]-1]['time'] / 4);
if($loop == 1) {
if($session->tribe == 1 || ALLOW_ALL_TRIBE) {
if($id >= 19) {
foreach($this->buildArray as $build) {
if($build['field'] >= 19) {
$time = $build['timestamp'] + round($dataarray[$village->resarray['f'.$id]-1]['time'] / 4);
}
}
}
}
else {
$time = $this->buildArray[0]['timestamp'] + round($dataarray[$village->resarray['f'.$id]-1]['time'] / 4);
}
}
else {
$time = $this->buildArray[0]['timestamp'] + round($dataarray[$village->resarray['f'.$id]-1]['time'] / 4);
$level = $database->getResourceLevel($village->wid);
if($database->addBuilding($village->wid, $id, $village->resarray['f'.$id.'t'], $loop, $time, 0, 0, $level['f'.$id] + 1 + count($database->getBuildingByField($village->wid, $id)))){
$logging->addBuildLog($village->wid, self::procResType($village->resarray['f'.$id.'t']), ($village->resarray['f'.$id] - 1), 2);
header("Location: dorf2.php");
exit();
}
}
$level = $database->getResourceLevel($village->wid);
if($database->addBuilding($village->wid, $id, $village->resarray['f'.$id.'t'], $loop, $time, 0, 0, $level['f'.$id] + 1 + count($database->getBuildingByField($village->wid, $id)))){
$logging->addBuildLog($village->wid, self::procResType($village->resarray['f'.$id.'t']), ($village->resarray['f'.$id] - 1), 2);
header("Location: dorf2.php");
exit();
}
} finally {
$database->releaseBuildLock($village->wid);
}
}
private function constructBuilding($id, $tid) {
global $database, $village, $session, $logging;
if($this->allocated < $this->maxConcurrent) {
if($tid == 16) $id = 39;
elseif($tid == 31 || $tid == 32 || $tid == 33) $id = 40;
$uprequire = $this->resourceRequired($id, $tid);
$time = time() + $uprequire['time'];
$bindicate = $this->canBuild($id, $tid);
$loop = ($bindicate == 9 ? 1 : 0);
if($loop == 1) {
foreach( $this->buildArray as $build) {
if($build['field'] >= 19 || ($session->tribe <> 1 && !ALLOW_ALL_TRIBE)) {
$time = $build['timestamp'] + ceil(60 / SPEED) + $uprequire['time'];
}
}
}
if($this->meetRequirement($tid)) {
$level = $database->getResourceLevel($village->wid);
if($database->addBuilding($village->wid, $id, $tid, $loop, $time, 0, $level['f' . $id] + 1 + count($database->getBuildingByField($village->wid, $id)))){
$logging->addBuildLog($village->wid, self::procResType($tid), ($village->resarray['f' . $id] + 1), 1);
$database->modifyResource($village->wid, $uprequire['wood'], $uprequire['clay'], $uprequire['iron'], $uprequire['crop'], 0);
header("Location: dorf2.php");
exit;
}
}else{
header("location: dorf2.php");
exit;
}
}else{
header("Location: dorf2.php");
exit;
if(!$database->getBuildLock($village->wid)) return;
try {
$this->loadBuilding();
if($this->allocated < $this->maxConcurrent) {
if($tid == 16) $id = 39;
elseif($tid == 31 || $tid == 32 || $tid == 33) $id = 40;
$uprequire = $this->resourceRequired($id, $tid);
$time = time() + $uprequire['time'];
$bindicate = $this->canBuild($id, $tid);
$loop = ($bindicate == 9 ? 1 : 0);
if($loop == 1) {
foreach( $this->buildArray as $build) {
if($build['field'] >= 19 || ($session->tribe <> 1 && !ALLOW_ALL_TRIBE)) {
$time = $build['timestamp'] + ceil(60 / SPEED) + $uprequire['time'];
}
}
}
if($this->meetRequirement($tid)) {
$level = $database->getResourceLevel($village->wid);
if($database->addBuilding($village->wid, $id, $tid, $loop, $time, 0, $level['f' . $id] + 1 + count($database->getBuildingByField($village->wid, $id)))){
$logging->addBuildLog($village->wid, self::procResType($tid), ($village->resarray['f' . $id] + 1), 1);
$database->modifyResource($village->wid, $uprequire['wood'], $uprequire['clay'], $uprequire['iron'], $uprequire['crop'], 0);
header("Location: dorf2.php");
exit;
}
}else{
header("location: dorf2.php");
exit;
}
}else{
header("Location: dorf2.php");
exit;
}
} finally {
$database->releaseBuildLock($village->wid);
}
}
+12
View File
@@ -4556,6 +4556,18 @@ References: User ID/Message ID, Mode
return mysqli_query($this->dblink,$q);
}
function getBuildLock($wid) {
$wid = (int) $wid;
$result = mysqli_query($this->dblink, "SELECT GET_LOCK('build_village_$wid', 10) AS locked");
$row = mysqli_fetch_assoc($result);
return $row['locked'] == 1;
}
function releaseBuildLock($wid) {
$wid = (int) $wid;
mysqli_query($this->dblink, "SELECT RELEASE_LOCK('build_village_$wid')");
}
/**
* Get the time required to build a specified building
*