From 12de46015e389d6b55c29123beb986cc77ebeec9 Mon Sep 17 00:00:00 2001 From: hdmaniak Date: Thu, 26 Mar 2026 21:48:22 +0100 Subject: [PATCH] Fix: Parallel troop recall race condition --- GameEngine/Database.php | 12 +++ GameEngine/Units.php | 163 +++++++++++++++++++++------------------- 2 files changed, 98 insertions(+), 77 deletions(-) diff --git a/GameEngine/Database.php b/GameEngine/Database.php index 28828d5c..bef07072 100755 --- a/GameEngine/Database.php +++ b/GameEngine/Database.php @@ -4592,6 +4592,18 @@ References: User ID/Message ID, Mode mysqli_query($this->dblink, "SELECT RELEASE_LOCK('train_village_$wid')"); } + function getEnforceLock($id) { + $id = (int) $id; + $result = mysqli_query($this->dblink, "SELECT GET_LOCK('enforce_$id', 10) AS locked"); + $row = mysqli_fetch_assoc($result); + return $row['locked'] == 1; + } + + function releaseEnforceLock($id) { + $id = (int) $id; + mysqli_query($this->dblink, "SELECT RELEASE_LOCK('enforce_$id')"); + } + /** * Get the time required to build a specified building * diff --git a/GameEngine/Units.php b/GameEngine/Units.php index 8e58bce7..ab167b3d 100755 --- a/GameEngine/Units.php +++ b/GameEngine/Units.php @@ -417,90 +417,99 @@ class Units { private function sendTroopsBack($post) { global $form, $database, $village, $session, $technology; - $enforce = $database->getEnforceArray( $post['ckey'], 0 ); - $enforceoasis = $database->getOasisEnforceArray( $post['ckey'], 0 ); - if ( ( $enforce['from'] == $village->wid ) || ( $enforce['vref'] == $village->wid ) || ( $enforceoasis['conqured'] == $village->wid ) ) { - $to = $database->getVillage( $enforce['from'] ); - $Gtribe = ($ownerTribe = $database->getUserField( $to['owner'], 'tribe', 0)) == 1 ? "" : $ownerTribe - 1; - - for ( $i = 1; $i < 10; $i ++ ) { - if ( isset( $post[ 't' . $i ] ) ) { - if ( $i != 10 ) { - if ( $post[ 't' . $i ] > $enforce[ 'u' . $Gtribe . $i ] ) { - $form->addError( "error", "You can't send back more units than you have" ); - break; - } - - if ( $post[ 't' . $i ] < 0 ) { - $form->addError( "error", "You can't send back negative units." ); - break; + if (!$database->getEnforceLock($post['ckey'])) return; + try { + // Re-fetch after lock to prevent TOCTOU race condition + $enforce = $database->getEnforceArray( $post['ckey'], 0 ); + $enforceoasis = $database->getOasisEnforceArray( $post['ckey'], 0 ); + if ( ( $enforce['from'] == $village->wid ) || ( $enforce['vref'] == $village->wid ) || ( $enforceoasis['conqured'] == $village->wid ) ) { + $to = $database->getVillage( $enforce['from'] ); + $Gtribe = ($ownerTribe = $database->getUserField( $to['owner'], 'tribe', 0)) == 1 ? "" : $ownerTribe - 1; + + for ( $i = 1; $i < 10; $i ++ ) { + if ( isset( $post[ 't' . $i ] ) ) { + if ( $i != 10 ) { + if ( $post[ 't' . $i ] > $enforce[ 'u' . $Gtribe . $i ] ) { + $form->addError( "error", "You can't send back more units than you have" ); + break; + } + + if ( $post[ 't' . $i ] < 0 ) { + $form->addError( "error", "You can't send back negative units." ); + break; + } } + } else { + $post[ 't' . $i . '' ] = '0'; + } + } + if ( isset( $post['t11'] ) ) { + if ( $post['t11'] > $enforce['hero'] ) { + $form->addError( "error", "You can't send back more units than you have" ); + } + + if ( $post['t11'] < 0 ) { + $form->addError( "error", "You can't send back negative units." ); } } else { - $post[ 't' . $i . '' ] = '0'; + $post['t11'] = '0'; } - } - if ( isset( $post['t11'] ) ) { - if ( $post['t11'] > $enforce['hero'] ) { - $form->addError( "error", "You can't send back more units than you have" ); - } - - if ( $post['t11'] < 0 ) { - $form->addError( "error", "You can't send back negative units." ); - } - } else { - $post['t11'] = '0'; - } - - if ( $form->returnErrors() > 0 ) { - $_SESSION['errorarray'] = $form->getErrors(); - $_SESSION['valuearray'] = $_POST; - header( "Location: a2b.php" ); - exit; - } else { - - //change units - $tribe = $database->getUserField($to['owner'], 'tribe', 0); - $start = ($tribe - 1 ) * 10 + 1; - $end = $tribe * 10 ; - - $units = []; - $amounts = []; - $modes = []; - - $j = 1; - for ( $i = $start; $i <= $end; $i ++ ) { - $units[] = $i; - $amounts[] = $post[ 't' . $j . '' ]; + + if ( $form->returnErrors() > 0 ) { + $_SESSION['errorarray'] = $form->getErrors(); + $_SESSION['valuearray'] = $_POST; + $database->releaseEnforceLock($post['ckey']); + header( "Location: a2b.php" ); + exit; + } else { + + //change units + $tribe = $database->getUserField($to['owner'], 'tribe', 0); + $start = ($tribe - 1 ) * 10 + 1; + $end = $tribe * 10 ; + + $units = []; + $amounts = []; + $modes = []; + + $j = 1; + for ( $i = $start; $i <= $end; $i ++ ) { + $units[] = $i; + $amounts[] = $post[ 't' . $j . '' ]; + $modes[] = 0; + $j ++; + } + + $units[] = 'hero'; + $amounts[] = $post['t11']; $modes[] = 0; - $j ++; + + $database->modifyEnforce($post['ckey'], $units, $amounts, $modes); + $j++; + + $troopsTime = $this->getWalkingTroopsTime($enforce['from'], $enforce['vref'], $to['owner'], $tribe, $post, 1, 't'); + $time = $database->getArtifactsValueInfluence($session->uid, $village->wid, 2, $troopsTime); + + $reference = $database->addAttack($enforce['from'], $post['t1'], $post['t2'], $post['t3'], $post['t4'], $post['t5'], $post['t6'], $post['t7'], $post['t8'], $post['t9'], $post['t10'], $post['t11'], 2, 0, 0, 0, 0); + $database->addMovement(4, $village->wid, $enforce['from'], $reference, time(), ($time + time())); + $technology->checkReinf($post['ckey'], false); + + $database->releaseEnforceLock($post['ckey']); + header("Location: build.php?id=39&refresh=1"); + exit(); + } + }else{ + $form->addError("error", "You cant change someones troops."); + if($form->returnErrors() > 0){ + $_SESSION['errorarray'] = $form->getErrors(); + $_SESSION['valuearray'] = $_POST; + $database->releaseEnforceLock($post['ckey']); + header("Location: a2b.php"); + exit(); } - - $units[] = 'hero'; - $amounts[] = $post['t11']; - $modes[] = 0; - - $database->modifyEnforce($post['ckey'], $units, $amounts, $modes); - $j++; - - $troopsTime = $this->getWalkingTroopsTime($enforce['from'], $enforce['vref'], $to['owner'], $tribe, $post, 1, 't'); - $time = $database->getArtifactsValueInfluence($session->uid, $village->wid, 2, $troopsTime); - - $reference = $database->addAttack($enforce['from'], $post['t1'], $post['t2'], $post['t3'], $post['t4'], $post['t5'], $post['t6'], $post['t7'], $post['t8'], $post['t9'], $post['t10'], $post['t11'], 2, 0, 0, 0, 0); - $database->addMovement(4, $village->wid, $enforce['from'], $reference, time(), ($time + time())); - $technology->checkReinf($post['ckey'], false); - - header("Location: build.php?id=39&refresh=1"); - exit(); - } - }else{ - $form->addError("error", "You cant change someones troops."); - if($form->returnErrors() > 0){ - $_SESSION['errorarray'] = $form->getErrors(); - $_SESSION['valuearray'] = $_POST; - header("Location: a2b.php"); - exit(); } + } finally { + $database->releaseEnforceLock($post['ckey']); } }