mojira.dev
MC-265772

A command with multiple redirect modifiers can ignore "maxCommandChainLength"

The bug

The command quota given by maxCommandChainLength is decreased by 1 for each modifier stage in command execution. When the command quota reaches 0, the execution in an execution context is canceled.

However, because this condition only applies when the command quota is exactly 0, execution will not be properly canceled when the command quota becomes negative. If your function has commands with two or more redirect modifiers, the function may or may not ignore maxCommandChainLength and keep running unpredictably depending on the command quota before each of the commands is executed. By carefully adjusting the number of command executions, it is even possible to define a function that recurses infinitely as in https://github.com/intsuc/23w44a_infinite_recursion.

How to reproduce

  1. /gamerule maxCommandChainLength 1
  2. /say !

    → ✔ A message is printed; this would be the correct behavior because the command quota is 1 when the executable stage say ! is about to be executed.

  3. /execute as @s run say !

    → ✔ A message is not printed and Command execution stopped due to limit (executed 1 commands) is printed; this would also be the correct behavior because the command quota is 0 when the executable stage say ! is about to be executed. Note that the modifier stage execute as @s -> execute consumes 1 command quota but the modifier stage execute run ... does not because the latter does not have a redirect modifier.

  4. /execute as @s as @s run say !

    → ❌ A message is printed; this would be an incorrect behavior. Because each modifier stage execute as @s -> execute consumes 1 command quota, the command quota is -1 when the executable stage say ! is about to be executed. As mentioned earlier, because -1 is not exactly 0, the executable stage say ! is executed ignoring maxCommandChainLength.

  5. /execute summon armor_stand summon armor_stand run say !

    → ❌ Two armor stands are created and a message is printed; this would be an incorrect behavior. This result indicates that each redirect modifier is executed and decrements the command quota regardless of the current command quota.

Code analysis

net.minecraft.commands.execution.ExecutionContext.java

public void runCommandQueue() {
      // ...
         if (this.commandQuota == 0) { // At this point, commandQuota can be negative.
            LOGGER.info("Command execution stopped due to limit (executed {} commands)", this.commandLimit);
            break;
         }
      // ...
   }

In net.minecraft.commands.execution.tasks.BuildContexts#execute, ExecutionContext#incrementCost, which decrements commandQuota, is called for each modifier stage with a redirect modifier. During these stage executions, commandQuota is not checked for execution cancellation, which could result in a negative commandQuota.

Comments 3

If MC-265805 is fixed but MC-265772 is not, MC-265772 and integer overflow could be exploited to execute a tail-recursive function indefinitely. As of 23w41a, it is not possible to reproduce this due to out of memory. See MC-265805 for more information.

I have created functions that actually recurse infinitely in 23w44a using MC-265772: 23w44a_infinite_recursion

Hi, could you please provide us with more information about this issue:
1. Expected outcome after running commands from repro steps. Right now after running commands from last repro step we noticed that game spawns two armor stands and do not say 1, is this the correct behavior?
2. How '/gamerule maxCommandChainLenght' commands affect the game and what should it allow/disallow.

This is already fixed. And if you don't know what the game rule does, you can just look it up. A report does not have to explain a feature's behavior, only the expected and actual result (which this does).

intsuc

eowyn36

Confirmed

Platform

Normal

Commands

23w41a, 23w42a, 23w43a, 23w44a, 23w45a, 23w46a, 1.20.3 Pre-Release 1

1.20.3 Pre-Release 2

Retrieved