Fixed setfenv() across a tail call

Fixed the issue noticed during testing of da06273e, and which resulted
in satest.setfenv1() being disabled. It's not possible to protect
environments by iterating through every stack level, calling getfenv()
at each one, because if any of the stack levels is a tail call, an error
is raised.

Such a tail call was introduced in da06273e, which is why the test broke.

Instead, just protect the actual specified environments, not their
callers. The callers will have to protect themselves.

Change-Id: If39104010ff2663c1bae5105cc8d37e276532100
This commit is contained in:
Tim Starling 2012-04-24 12:33:06 +10:00
parent 606aaf30ac
commit 41b93dd7e1
3 changed files with 40 additions and 15 deletions

View file

@ -136,16 +136,12 @@ function mw.makeProtectedEnvFuncs( protectedEnvironments, protectedFunctions )
error( "'setfenv' cannot set an environment at a level greater than 10", 2 )
end
-- Add one because we are still in Lua and 1 is right here
stackIndex = stackIndex + 1
local i
for i = 2, stackIndex do
local env = old_getfenv( i )
if env == nil or protectedEnvironments[ env ] then
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
end
local env = old_getfenv( stackIndex )
if env == nil or protectedEnvironments[ env ] then
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
end
func = old_setfenv( stackIndex, newEnv )
elseif type( func ) == 'function' then

View file

@ -56,12 +56,13 @@ function test.getTests( engine )
{ 'setfenv1', { error = '%s cannot set the global %s' } },
{ 'setfenv2', { error = '%s cannot set an environment %s' } },
{ 'setfenv3', { error = '%s cannot set the requested environment%s' } },
{ 'setfenv4', { error = '%s cannot set the requested environment%s' } },
{ 'setfenv4', true },
{ 'setfenv5', true },
{ 'setfenv6', { error = '%s cannot be called on a protected function' } },
{ 'setfenv7', { error = '%s can only be called with a function%s' } },
{ 'getfenv1', true },
{ 'getfenv2', { error = '%s cannot get the global environment' } },
{ 'getfenv3', { error = '%Sno function environment for tail call %s' } },
}
end
@ -119,11 +120,27 @@ function test.setfenv3()
end
function test.setfenv4()
local function jailbreak()
(function() setfenv( 3, {} ) end )()
-- Set an unprotected environment at a higher stack level than a protected
-- environment. It's assumed that any higher-level environment will protect
-- itself with its own setfenv wrapper, so this succeeds.
local function level3()
local function level2()
local env = {setfenv = setfenv}
local function level1()
setfenv( 3, {} )
end
setfenv( level1, env )()
end
local protected = {mw = mw}
protected.setfenv, protected.getfenv = mw.makeProtectedEnvFuncs(
{[protected] = true}, {} )
setfenv( level2, protected )()
end
local new_setfenv, new_getfenv = mw.makeProtectedEnvFuncs( { [_G] = true }, {} )
setfenv( jailbreak, {setfenv = new_setfenv} )()
local unprotected = {setfenv = setfenv, getfenv = getfenv, mw = mw}
setfenv( level3, unprotected )()
assert( getfenv( level3 ) ~= unprotected )
return true
end
function test.setfenv5()
@ -161,4 +178,16 @@ function test.getfenv2()
getfenv( 0 )
end
function test.getfenv3()
local function foo()
return getfenv( 2 )
end
local function bar()
return foo()
end
bar()
end
return test

View file

@ -3,13 +3,13 @@ local satest = {}
function satest.getTests()
return {
-- { 'setfenv1', { error = '%s cannot set the requested environment%s' } },
{ 'setfenv1', { error = '%s cannot set the requested environment%s' } },
{ 'getfenv1', true },
}
end
function satest.setfenv1()
setfenv( 2, {} )
setfenv( 3, {} )
end
function satest.getfenv1()