noliCAIKS
|
  |
| Joined: 08 Mar 2010 |
| Total Posts: 917 |
|
|
| 11 Jan 2013 10:20 AM |
A while ago, someone claimed it was impossible for normal users to make read-only tables in ROBLOX. Today, I prove them wrong.
-- create a closure do -- store global functions locally to prevent hacking local error = error local getmetatable = getmetatable local newproxy = newproxy -- show error message on attempt to modify a read-only table local function ModificationError() error("can't modify this table") end function CreateReadonlyTable(contentTable) local proxy = newproxy(true) local metatable = getmetatable(proxy) metatable.__index = contentTable metatable.__metatable = "The metatable is locked" metatable.__newindex = ModificationError return proxy end end
Boolean = CreateReadonlyTable({ False = 0, True = 1 })
There you go! |
|
|
| Report Abuse |
|
|
|
| 11 Jan 2013 10:25 AM |
Wat do now nubs jejejejejejejje |
|
|
| Report Abuse |
|
|
|
| 11 Jan 2013 12:34 PM |
That's not a table. That's a read-only userdata. |
|
|
| Report Abuse |
|
|
noliCAIKS
|
  |
| Joined: 08 Mar 2010 |
| Total Posts: 917 |
|
|
| 11 Jan 2013 01:47 PM |
The "coroutine", "math", "string" and "table" libraries are read-only userdata's as well in ROBLOX. I don't think it counts. |
|
|
| Report Abuse |
|
|
Seranok
|
  |
| Joined: 12 Dec 2009 |
| Total Posts: 11083 |
|
|
| 11 Jan 2013 03:04 PM |
print(type(Vector3)) --> "table" Vector3.new = nil --> error |
|
|
| Report Abuse |
|
|
Luc599345
|
  |
| Joined: 25 Jul 2008 |
| Total Posts: 1169 |
|
|
| 11 Jan 2013 03:15 PM |
| You can't change functions in tables that are the same in every scripts' environments. If you COULD edit it, you could just overwrite the Vector3.new function and whenever another script would call it, it'll run your function instead. This exploit was found by popinman, which later reported it to the admins. Which is why, now, you cannot overwrite those functions. |
|
|
| Report Abuse |
|
|
noliCAIKS
|
  |
| Joined: 08 Mar 2010 |
| Total Posts: 917 |
|
|
| 11 Jan 2013 03:17 PM |
Oh, and what's more? print(getmetatable(Vector3)) --> "nil" print(rawget(Vector3, "new")) --> function: ******** print(pcall(rawset, Vector3, "new", "hax")) --> false Attempt to modify a readonly table |
|
|
| Report Abuse |
|
|
|
| 11 Jan 2013 07:07 PM |
The person who claimed that was probably me, and I was right. What you made is a read-only userdata, as Sorcus pointed out.
Read-only tables do not exist in normal Lua, they only exist in ROBLOX. An example of them is the Vector3 table or the Instance table. They're read-only, but their type is still 'table'. It is impossible to reproduce that behavior.
Obviously, you can create an userdata that behaves somewhat as a table and is read-only, but it's still an userdata and not a table. |
|
|
| Report Abuse |
|
|
Luc599345
|
  |
| Joined: 25 Jul 2008 |
| Total Posts: 1169 |
|
|
| 11 Jan 2013 07:32 PM |
"as Sorcus pointed out."
I can't tell if SCARFACIAL is just a successful wanna-be or Julien is joking o.O |
|
|
| Report Abuse |
|
|
|
| 11 Jan 2013 07:39 PM |
@Luc599345
I'm just joking, obviously. He doesn't actually look like Sorcus, and it's obvious by his posts that he's not Sorcus. |
|
|
| Report Abuse |
|
|
1smurf1
|
  |
| Joined: 07 Aug 2010 |
| Total Posts: 50 |
|
|
| 11 Jan 2013 11:28 PM |
read only table?
ReadOnly = function() return setmetatable({}, { __newindex = function() print"This is a read-only table!" end, __metatable = "This metatable is locked!"}) end
readTab = ReadOnly()
table = { remove = table.remove, sort = table.sort, unpack = table.unpack, concat = table.concat } function table:insert(arg1, arg2) if not(arg2) then self[#self + 1] = arg1 else if type(arg1) ~= "number" then error("number expected, got string", 2) end for i = #self, arg1, -1 do self[i + 1] = self[i] end self[arg1] = arg2 end end rawset = function(self, i, v) self[i] = v end |
|
|
| Report Abuse |
|
|
|
| 11 Jan 2013 11:43 PM |
@1smurf1
That still isn't a read-only table. You can change its values with rawset. Yes, I know, you replaced the rawset variable, but it still isn't read-only, because, if you had access to rawset, you could change it. |
|
|
| Report Abuse |
|
|
1smurf1
|
  |
| Joined: 07 Aug 2010 |
| Total Posts: 50 |
|
|
| 11 Jan 2013 11:52 PM |
| oh and you could change it with table.insert too - ignoring the fact that the code is meant to be placed at the top of a script. . . |
|
|
| Report Abuse |
|
|
|
| 12 Jan 2013 12:14 AM |
@1smurf1
Read-only tables CAN be changed with table.insert. Try it with the Vector3 or the Instance tables. |
|
|
| Report Abuse |
|
|
1smurf1
|
  |
| Joined: 07 Aug 2010 |
| Total Posts: 50 |
|
|
| 12 Jan 2013 12:32 AM |
did i not just say that? i'm saying that the case you made - "it still isn't read-only, because, if you had access to rawset, you could change it" - doesn't apply since you don't have access to rawset/table.insert if you put the code where its *supposed* to be (at the top of the script) |
|
|
| Report Abuse |
|
|
|
| 12 Jan 2013 01:31 AM |
| I actually can still have access to rawset if another script put a reference to it in _G, since I can access that reference. Overwriting variables is cheating and does not apply. |
|
|
| Report Abuse |
|
|
| |
|
SN0X
|
  |
| Joined: 24 Oct 2011 |
| Total Posts: 7277 |
|
| |
|
|
| 12 Jan 2013 09:21 AM |
Olololol.
local OldType = type local Proxies = {} local GetInTable = function(tab,val) for i,v in pairs(tab) do if v == val then return i end end end local type = function(Val) if GetInTable(Proxies,Val) then return "table" else return OldType(Val) end end local Tab = { Hi = "Yar", Derp = "Ummmm", } local NewIndex = function() error("lolno") end local Metatable = "Locked." local Prox = newproxy(true) local Meta = getmetatable(Prox) Meta.__index = Tab Meta.__newindex = NewIndex Meta.__metatable = Metatable table.insert(Proxies,Prox) Tab = Prox print(type(Tab)) print(Tab.Hi) Tab.Derp = "dfdfdf" print(Tab.Derp)
|
|
|
| Report Abuse |
|
|
Seranok
|
  |
| Joined: 12 Dec 2009 |
| Total Posts: 11083 |
|
|
| 12 Jan 2013 10:35 AM |
| Lua's built-in type function should return "table" when called with the table |
|
|
| Report Abuse |
|
|
noliCAIKS
|
  |
| Joined: 08 Mar 2010 |
| Total Posts: 917 |
|
|
| 12 Jan 2013 11:25 AM |
Just noticed an actual issue caused by this...
local proxy = newproxy(true) local meta = getmetatable(proxy) meta.__index = {var1 = 1234} local object = setmetatable({}, proxy) -- bad argument #2 to 'setmetatable' (nil or table expected) |
|
|
| Report Abuse |
|
|
booing
|
  |
| Joined: 04 May 2009 |
| Total Posts: 6594 |
|
|
| 12 Jan 2013 12:21 PM |
| setmetatable(_G, { __index = {}, __newindex = error }) |
|
|
| Report Abuse |
|
|
1smurf1
|
  |
| Joined: 07 Aug 2010 |
| Total Posts: 50 |
|
|
| 12 Jan 2013 12:56 PM |
Since when were there rules in scripting. . . I overwrote table, I overwrote rawest, ill just overwrite _G (like stated above)
Is the idea of a read only table that hard for you to wrap your head around? |
|
|
| Report Abuse |
|
|
Seranok
|
  |
| Joined: 12 Dec 2009 |
| Total Posts: 11083 |
|
|
| 12 Jan 2013 02:09 PM |
| But it's still technically not a table... |
|
|
| Report Abuse |
|
|
|
| 12 Jan 2013 06:31 PM |
"I overwrote table, I overwrote rawest, ill just overwrite _G (like stated above)"
Well, the table will still not be read-only no matter what you do.
I'm going to take a script and use that script to insert the rawset function into the Vector3 table, which is read-only. I'll insert it under a numerical key, since that's all the table.insert function allows you to do.
Then, in the script, I'll get the rawset function from the Vector3 table in the correct index (an index which you cannot guess, since it could be any numerical value) and use it to edit your table.
You could iterate through the Vector3 table, find the index and remove it using table.insert, though. But you'd need to do it with Instance, BrickColor, Region3, Vector3int16 and all the other read-only tables.
Oh, and don't forget I can also put it in shared instead of _G. As for overwriting their metatables, what if the other script overwrites them first?
Basically, as long as the two scripts can communicate in any way with each others, I can give it back all the functions you overwrite.
Even if you actually made it impossible to use a read-only table or shared or _G by removing all of them, I could still add the function back in a read-only table every frame. So you'd need to run the loop that removes all the functions from these tables every single frame. Oh, and you'd need to do it with the Spawn function or in a coroutine (or in an event), because you don't want to interrupt the script.
So by that point, you overwrote the rawset function, shared and _G. You also have around 10 coroutines or threads running permanently, iterating through all the read-only tables every frame and removing all the numerical values from them. Great.
But I can still communicate through the game hierarchy. I can create a BindableFunction that points to the rawset function. And I can put it anywhere in the game.
So, to prevent me from doing that, you'd need to scan through the entire game hierarchy every single frame in a coroutine or a thread and remove all BindableFunctions. And BindableEvents too, because I could also do it with BindableEvents.
Now, to recapitulate, you have: - Overwritten the rawset function - Overwritten _G and shared - A thread or coroutine iterating every frame through all the read-only tables and removing numerical values from them - A thread or coroutine iterating every frame through the whole game hierarchy to remove BindableFunctions and BindableEvents
Not yet convinced? Ok. I have also thought about inserting the bindable object in the CoreGui using InsertService::Insert, in which case you'd need to brute-force the FindFirstChild function in a thread every frame to find it, and in which case you couldn't even remove it anyway, but the script wouldn't be able to access it and invoke it.
There's also the idea of making the script do something when an event on a service (like the Changed event, in which case the script would have to change the name property), but that doesn't make it possible to pass tables.
There are also some other things I could do that would work, but it'd be ridiculous to even mention them. Let's say I now use the environment of a function in one of the ROBLOX libraries (RbxLibrary) to make the two scripts communicate. Or no, let's say I actually get the environment of a such function and call the rawset function directly from there. Unless you actually decide to change the environment of every single library function ROBLOX provides, I don't think you can do much.
Ok, let's stop with all this theoretical madness, because, anyway, you can prevent me from accessing the game hierarchy, the read-only tables and the ROBLOX libraries.
Let's pretend you just set every single default variable to nil. Well, congratulations, the script is now completely useless and non-functional because all it can do is play with syntax. But yes, it cannot edit the table. That's cool, but the script cannot access anything and all it can do is create tables, strings and numbers and manipulate them in whatever way using loops or operations. That's just useless, though.
So yes, you made a table that cannot be edited by indexing it using the syntax, and you made sure it could not be used in any other way by removing every single function whatsoever from the environment. Even then, there are some other ways to get the default environment back, but you'll probably find some way to prevent those too.
And yet, your table is still not a read-only table. It cannot be edited by that particular script because you removed everything from its environment, but it's still not a read-only table, because if we had access to those default functions, we could edit the table. Editing everything other than the table doesn't make the table different, it just makes everything else different. Therefore, the table is still as it was before you removed everything from the environment, and, thus, it cannot have become read-only.
Even then, to make this system useful in whatever way, you'd need to be able to make the script unable to detect whether you edited anything. You'd need to be able to make it unable to know whether the table is really read-only or whether it's a fake read-only table. To do that, you'd... huh... basically need to re-implement everything in the environment that can be used in any way to edit the table, while making sure it still functions in the same way. But, if you overwrite functions, then the script can detect it, since it's possible, with string.dump, to know if a function is a C function or a Lua function. So you'd need to overwrite the string.dump function too. In fact, your code would just get overly complex, and you'd end up needing over 5000 lines of code only to make sure the script cannot detect whether a table is a real read-only table or not.
In the end, you might as well just write your own Lua interpreter in Lua with read-only tables and every single other feature supported by Lua. It'd be a more proper solution, and I assure you that it would be more useful.
Oh, and, remember, even by that point, the table wouldn't be really read-only, because, if we had access to the default functions, we could still edit it. |
|
|
| Report Abuse |
|
|