CVE-2024-31446: Insufficient Information (NVD-CWE-noinfo)
value: OpenComputers is a Minecraft mod that adds programmable computers and robots to the game. A user can use OpenComputers to get a Computer thread stuck in the Lua VM, which eventually blocks the Server thread, requiring the server to be forcibly shut down. This can be accomplished using any device in the mod and can be performed by anyone who can execute Lua code on them. This occurs while using the native Lua library. LuaJ appears to not have this issue. This vulnerability is fixed in 1.8.4. The GregTech: New Horizons modpack uses its own modified version of OpenComputers. They have applied the relevant patch in version 1.10.10-GTNH.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-16T20:15Z
Repository: https://github.com/MightyPirates/OpenComputers
Language: Lua
Files changed: src/main/resources/assets/opencomputers/lua/machine.lua
Commit date: 2024-04-16 18:39:11 +0200
Commit message: Fix pcall()/xpcall() bug.

Diff: diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua
index df2cd8cf8..2c1e9a72f 100644
--- a/src/main/resources/assets/opencomputers/lua/machine.lua
+++ b/src/main/resources/assets/opencomputers/lua/machine.lua
@@ -759,10 +759,14 @@ sandbox = {
   end,
   loadfile = nil, -- in boot/*_base.lua
   next = next,
   pairs = pairs,
   pcall = function(...)
+    -- prevent infinite pcall() loops by checking deadline before pcall()
+    local status, err = pcall(checkDeadline)
+    if not status then return false, err end
+
     return pcallTimeoutCheck(pcall(...))
   end,
   print = nil, -- in boot/*_base.lua
   rawequal = rawequal,
   rawget = rawget,
@@ -805,22 +809,31 @@ sandbox = {
   tonumber = tonumber,
   tostring = tostring,
   type = type,
   _VERSION = _VERSION:match("Luaj") and "Luaj" or _VERSION:match("5.4") and "Lua 5.4" or _VERSION:match("5.3") and "Lua 5.3" or "Lua 5.2",
   xpcall = function(f, msgh, ...)
-    local handled = false
+    -- allow xpcall() to call the message handler recursively, per manual 2.3
+    -- Lua itself promises to break the infinite loop; failing that, the timeout
+    -- check will take care of this.
+    local errorCapture
+    errorCapture = function(ff, ...)
+      -- prevent infinite xpcall() loops by checking deadline before xpcall()
+      local status, err = pcall(checkDeadline)
+      if not status then return false, err end
+
+      return xpcall(ff, function(...)
+        if rawequal((...), tooLongWithoutYielding) then
+          return tooLongWithoutYielding
+        else
+          return select(2, errorCapture(msgh, ...))
+        end
+      end, ...)
+    end
+
     checkArg(2, msgh, "function")
-    local result = table.pack(xpcall(f, function(...)
-      if rawequal((...), tooLongWithoutYielding) then
-        return tooLongWithoutYielding
-      elseif handled then
-        return ...
-      else
-        handled = true
-        return msgh(...)
-      end
-    end, ...))
+    local result = table.pack(errorCapture(f, ...))
+    -- if the final returned error is due to timeout, run handler one last time
     if rawequal(result[2], tooLongWithoutYielding) then
       result = table.pack(result[1], select(2, pcallTimeoutCheck(pcall(msgh, tostring(tooLongWithoutYielding)))))
     end
     return table.unpack(result, 1, result.n)
   end,

Vulnerable code:     local handled = false
    local result = table.pack(xpcall(f, function(...)
      if rawequal((...), tooLongWithoutYielding) then
        return tooLongWithoutYielding
      elseif handled then
        return ...
      else
        handled = true
        return msgh(...)
      end
    end, ...))
Fixed code:     -- prevent infinite pcall() loops by checking deadline before pcall()
    local status, err = pcall(checkDeadline)
    if not status then return false, err end

    -- allow xpcall() to call the message handler recursively, per manual 2.3
    -- Lua itself promises to break the infinite loop; failing that, the timeout
    -- check will take care of this.
    local errorCapture
    errorCapture = function(ff, ...)
      -- prevent infinite xpcall() loops by checking deadline before xpcall()
      local status, err = pcall(checkDeadline)
      if not status then return false, err end

      return xpcall(ff, function(...)
        if rawequal((...), tooLongWithoutYielding) then
          return tooLongWithoutYielding
        else
          return select(2, errorCapture(msgh, ...))
        end
      end, ...)
    end

    local result = table.pack(errorCapture(f, ...))
    -- if the final returned error is due to timeout, run handler one last time