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

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.

I disregarded that point on the basis that leaving it up to the humans to not forget that a perfectly reasonable pattern has an inconspicuous security flaw is a fine way to introduce exploits.

1 Like

I can’t see a situation where it’d be needed to pass said functions, so I really think it being a security issue isn’t actually what the problem is here.

I would also like to point out it’s not possible all together – Roblox has two VMs, 1 for core scripts and 1 for normal scripts, and they have a different base state. So interaction between those two must be explicit.

EDIT: After talking to some rather sketchy friends I was informed that there are 2 states, not 2 vms in this case. However it still means that interaction is pretty much blocked off by default.

1 Like

I was actually messing with this not too long ago. Unfortunately I don’t remember the specifics of what I did to trigger this, but I recall getting some strange results where if I tried to spoof a BindableFunction, it would jump to a completely unrelated function in the BlockPlayerPrompt CoreScript. I did this spoof in the CoreGuiChatConnections SetCore method.

One other interesting thing to note is that when a function passes the boundary between scripts and CoreScripts, it turns into a coroutine thread with its status set to “dead”

Ohhh… that makes sense, maybe that’s actually a security measure. I’ve always wondered why that happens.