Allow the Command Line to access the Client/Server's _G

I’ll let Studio answer the first question.

IMHO, there could be some value in it as a debugging tool.
I don’t think global tables are a good place for scripts to interact, but that’s different.

As a bandaid, you can bridge the gap with Bindables:

local bridge = Instance.new("BindableFunction")
bridge.Name = "Debug"
function bridge.OnInvoke(object, method, ...)
	return _G[object][method](_G[object], ...)
end
bridge.Parent = workspace
unit = workspace.Debug:Invoke("CurrentState", "GetUnits")[1]
workspace.Debug:Invoke("CurrentState", "UnitMove", unit, 1, 1)

Keep in mind that tables are copied as they cross the bridge.

3 Likes

For me personally, I don’t like to use _G because you can’t be sure that functions and variables will be there when you try to ask for them.

If you really want to be able to view your game’s state as it is from memory in Lua, you should take a look at the Lua Debugger: Documentation - Roblox Creator Hub

The feature has drastically improved since its inception. Watching a dictionary/array allows you to expand its contents.

Instead of using _G, you really should use ModuleScripts. ModuleScripts are only executed once per context level, so they’ll return the same state each time they are required unless you’re returning a constructor function.

I know it’s a pain to traverse the DataModel on the client, I even made a thread suggesting that we should be able to load ModuleScripts by name when we’re using the LinkedSource feature. It’s in your best interest to cooperate with Roblox’s API quirks though, because it’ll save you a lot of headaches down the road if you just get used to it. There are more practical solutions to problems like these than opening up _G to be shared by scripts and the command bar.

This problem has more to do with live debugging, and being able to call APIs on the current game state. You could make the state accessible through a module instead of _G, but that doesn’t really change much. It doesn’t solve the identity boundary problem either (as explained here). For the case of interactive debugging, I’d say the use of _G is perfectly fine, since it already isn’t used in any other way, and there are no scripts waiting on it.


Also, I thought of another nice bandaid that only works while the game is running. Since both plugins and the command bar have more privileges, they can read and write script sources. This can be used to run input within a regular script.

function _G.run(s)
	local script = workspace:FindFirstChild("CommandScript")
	if not script then
		script = Instance.new("Script")
		script.Name = "CommandScript"
		script.Parent = workspace
	end
	script.Source = s
	script.Disabled = false
	script.Disabled = true
end

Despite plugins having a different identity from the command bar, they still share the same environment. This means the run function can be set up by a plugin, then used by the command bar.

_G.run[[unit = _G.CurrentState:GetUnits()[1] ]]
_G.run[[_G.CurrentState:UnitMove(unit, 1, 1)]]
2 Likes
local Fenv = getfenv(0)

function Bindable.OnInvoke()
    return function()
        return Fenv
    end
end

And then from the command bar…

local Fenv = Bindable:Invoke()()

_G = Fenv._G

I can’t test this right now but I’d assume it works. BindableEvent can pass functions and Event receive it, so using upvalues and BindableFunctions shoud achieve the same.

We definitely won’t allow scripts running at CommandLineSecurity to access the Lua environment from ScriptSecurity because that would open the door for vulnerabilities. But having a way to run code from the command bar at ScriptSecurity seems reasonable.

If you’re running a game you can achieve this by opening the Developer Console, switching to the Server Log tab, and entering your Lua code.

2 Likes

Yes I was aware, but upvalues are handled via stack position reference, which means calling that function would return the real table.

Just checked, though, and apparently we can’t invoke from BindableFunctions from a higher context at all?

You Invoked the BindableFunction just fine, but the inner function you returned became nil, because there’s no way to transfer it to the command bar environment, because they’re completely separate environments.

Woops, my bad, I assumed it meant nil as in the OnInvoke at first glance. Although it seems a bit weird that this happens; Lua has no actual limitations for these sorts of things so they must have been implemented manually – the function returning and table copying I mean. Wasn’t the whole original point that RemoteEvents were JSON-able, so Bindables were made the same, anyways?

As far as I know, there shouldn’t be any security flaws anyways. If we can use the commandbar at the elevated context then we can do everything a normal script can, and more. So opening up the ability to interact with the default state seems more like an occasional inconvenience in contexts vs a security flaw.

Awesome, this is a good enough shim for me for the time being, I didn’t know that plugins could shove stuff into _G so it’s pretty painless with that in mind. I think it actually takes a bit more work than you show there to hook up the “unit” variable between the two calls, but I can do that with some setfenv magic.

Bindables predate remotes, and values are sanitized on purpose. I’m not sure about the exact dates between bindables and separate identity environments, but bindables could easily have been created with the intention of using them to bridge identities.

Also, JSON is involved with neither bindables nor remotes.

I don’t think global tables are a good place for scripts to interact, but that’s different.

So… you think that the acceptable solution for this problem is an “evaluate expression here” for the debugger?

I don’t see us getting that any time soon. When I have non-trivial Model objects it gets really painful to debug anything because the internal state of my Model objects isn’t remotely readable simply through inspecting their table members. And that’s assuming that the debugger even works at all… which it often still doesn’t for my code more than two years after being added (it really doesn’t seem to like me using lots of BindableEvents, often getting confused when the call stack resulting in an error or breakpoint passes through one).

_G is the goto of Roblox. It is often mistaken to be bad for all possible cases, and misapplied to contexts in which it is perfectly reasonable to use.

What I refer to as JSON-able is that often times you have the limitation of not sending any tables that aren’t JSON-able through, such as:
image

The ideal behavior would be that the table is passed as a pointer, but instead, it is recreated.
I didn’t mean they were actually using JSON behind the scenes, but it just acts as if it were.

I really don’t see a reason to sanitize the values (server->server or client->sameclient). Assuming they aren’t using the higher identity to interact directly with the current one, it isn’t an issue for the current one to try to do so.
An example of the higher context interacting with the lower, causing issues, was the env breakout issue that happened a while ago, where users could get the environment of the global state and edit it from the Roblox Lua, and in turn do things from changing globals to overwriting methods.

If you have any possible ideas of something that could go wrong I’d like to know.

So bindables should sometimes return direct references and sometimes return sanitized values, depending on a virtually invisible feature (identities)?

Value sanitation is necessary for security between different identities. Sanitation between same identities is necessary for consistency.

1 Like

I think that the consistency of the behavior between Remotes and Bindables is for the best. If you want the pass-by-reference behavior, it’s easy to get with something like my signal module (Doesn’t handle event re-entrancy, that is firing the event again inside one of the calls to the handler, but if you have re-entrant events in your code it’s probably broken in other ways too).

Overall pass-by-value as the default for events seems logical to me, it’s likely to save people from bugs in a lot of cases. I would say that a good 1/4 of all bugs that I run into in my Roblox code are lingering reference issues where I accidentally assign something by reference when I meant to copy or :Clone() it and end up with two Model objects sharing some piece of internal state that they should each have their own copy of.

@Anaminus I don’t see how it’d be a security concern from the pov of the values being passed always containing safe content to begin with. I don’t believe passing a function from context 4 to 2 will cause an issue, because 1. Roblox would have no reason to, and therefore would not do that, and 2. even if for theoretical case in which it does, the context is too low to make api calls from within it.
Context is inherited by caller.

@stravant I can see Remote and Bindable consistency being a thing to take into account, and I too have custom signaling code (albeit basic for the intent), but there is an amount of precaution that has already been taken to make sure that this can’t be used maliciously. I’m not asking for change, since it hasn’t really bothered me in any case, just wrap things in storage functions and pass those instead.

What I mean is, there are the cases to be made for security and consistency, and I can see the latter, but I haven’t been presented with any examples for the former.

Okay. Bindables can pass functions willy-nilly with no filtering. An ID2 receives a function from an ID4. This ID2 uses getfenv/setfenv to mess with the function’s environment (it’s possible to probe the environment to see what is being accessed). ID2 figures out that ID4 is calling a global function, so it replaces that with a function which calls a sensitive API with whatever desired arguments. ID4 will eventually call that function and successfully call the sensitive API with those arguments specified by ID2. Uhohspaghettios.

I recall that CoreScripts make use of bindables in several SetCores (the Chat module too, I think), so I wouldn’t be surprised if this could actually happen right now, if only bindables weren’t filtered.

1 Like

Yes but I mentioned that

in regards to them passing functions via Bindables to our context level, so it’s not a possibility unless they go out of their way to make it so.