Skip to content

Commit 8d6a0b9

Browse files
authoredMar 5, 2020
Fix potential security issue(s), documentation on minetest.deserialize() (#9369)
Also adds an unittest
1 parent ef09e8a commit 8d6a0b9

File tree

3 files changed

+39
-11
lines changed

3 files changed

+39
-11
lines changed
 

Diff for: ‎builtin/common/serialize.lua

+13-7
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,16 @@ end
177177

178178
-- Deserialization
179179

180-
local env = {
181-
loadstring = loadstring,
182-
}
180+
local function safe_loadstring(...)
181+
local func, err = loadstring(...)
182+
if func then
183+
setfenv(func, {})
184+
return func
185+
end
186+
return nil, err
187+
end
183188

184-
local safe_env = {
185-
loadstring = function() end,
186-
}
189+
local function dummy_func() end
187190

188191
function core.deserialize(str, safe)
189192
if type(str) ~= "string" then
@@ -195,7 +198,10 @@ function core.deserialize(str, safe)
195198
end
196199
local f, err = loadstring(str)
197200
if not f then return nil, err end
198-
setfenv(f, safe and safe_env or env)
201+
202+
-- The environment is recreated every time so deseralized code cannot
203+
-- pollute it with permanent references.
204+
setfenv(f, {loadstring = safe and dummy_func or safe_loadstring})
199205

200206
local good, data = pcall(f)
201207
if good then

Diff for: ‎builtin/common/tests/serialize_spec.lua

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
_G.core = {}
22

3-
_G.setfenv = function() end
3+
_G.setfenv = require 'busted.compatibility'.setfenv
44

55
dofile("builtin/common/serialize.lua")
66

@@ -25,4 +25,20 @@ describe("serialize", function()
2525
local test_out = core.deserialize(core.serialize(test_in))
2626
assert.same(test_in, test_out)
2727
end)
28+
29+
it("strips functions in safe mode", function()
30+
local test_in = {
31+
func = function(a, b)
32+
error("test")
33+
end,
34+
foo = "bar"
35+
}
36+
37+
local str = core.serialize(test_in)
38+
assert.not_nil(str:find("loadstring"))
39+
40+
local test_out = core.deserialize(str, true)
41+
assert.is_nil(test_out.func)
42+
assert.equals(test_out.foo, "bar")
43+
end)
2844
end)

Diff for: ‎doc/lua_api.txt

+9-3
Original file line numberDiff line numberDiff line change
@@ -5275,10 +5275,16 @@ Misc.
52755275
* Convert a table containing tables, strings, numbers, booleans and `nil`s
52765276
into string form readable by `minetest.deserialize`
52775277
* Example: `serialize({foo='bar'})`, returns `'return { ["foo"] = "bar" }'`
5278-
* `minetest.deserialize(string)`: returns a table
5279-
* Convert a string returned by `minetest.deserialize` into a table
5278+
* `minetest.deserialize(string[, safe])`: returns a table
5279+
* Convert a string returned by `minetest.serialize` into a table
52805280
* `string` is loaded in an empty sandbox environment.
5281-
* Will load functions, but they cannot access the global environment.
5281+
* Will load functions if safe is false or omitted. Although these functions
5282+
cannot directly access the global environment, they could bypass this
5283+
restriction with maliciously crafted Lua bytecode if mod security is
5284+
disabled.
5285+
* This function should not be used on untrusted data, regardless of the
5286+
value of `safe`. It is fine to serialize then deserialize user-provided
5287+
data, but directly providing user input to deserialize is always unsafe.
52825288
* Example: `deserialize('return { ["foo"] = "bar" }')`,
52835289
returns `{foo='bar'}`
52845290
* Example: `deserialize('print("foo")')`, returns `nil`

0 commit comments

Comments
 (0)