[Date Prev][Date Next][Thread Prev][Thread Next]
- Subject: Re: setfenv
- From: RLake@...
- Date: Mon, 13 Oct 2003 18:13:46 -0500
> So, can you answer the question of how one can implement
> the following:
> while cond do
> (re)set initial global environment
I think what you want to do is to sandbox luafnc, right?
Or do you really want to resandbox it each time through
> The aim is that luafnc() cannot modify the the global
> environment. I (maybe incorrectly) assumed that the
> purpose of setfenv() was provide this functionality
> without needing to perform a deep copy of the
> GLOBALSINDEX table.
It may be that setfenv() was intended to help solve this
problem, but it does not do so "out of the box". You can
use it to attach a previously created global environment
to a script loaded with loadfile or equivalent, but this
environment should not include the standard versions of
setfenv nor getfenv.
If you need to ensure that the environment is clean on
each invocation of luafnc(), you will probably have to
set up an environment with a __newindex metamethod which
proxies global assignments, or you will need to recreate
the global environment from scratch each time through the
loop. Using a lazy copy from a precreated original may help.
However, there is an issue with standard library tables.
Some random thoughts follow... it is a bit long; sorry.
I have posted various lazy copy implementations to this list
before: see <http://lua-users.org/lists/lua-l/2003-06/msg00469.html>
for example, so I'm not repeating it here.
Wim Couwenberg and I had a long correspondence about sandboxing
and globals, and I have been seriously deficient in not posting
the results to this list.
One of the results of that conversation is that it is probably
not possible to really protect an environment without making
a copy of all the library tables. Consequently, if we are going
to take sandboxing seriously, we need to think about library
loading protocols: the only protocol that is workable, I think,
is for the library to export a single function which fills a
The problem is that if you give an untrusted function a copy
of, say, math, then it is free to redefine math.sin as anything
it wants to. Assuming it has nothing "dangerous" in its environment,
the effect of redefining math.sin is "benign", except that it will
obviously affect the correction execution of any other script
using math.sin. To take the example further, a malign script
might attempt to redefine math.random so as to cause another
script to generate a specific sequence of random numbers in order
to defeat a cryptographic algorithm. Thus, even if the math library
were modified to use a cryptographically adequate implementation
of math.random, the fact that the math global table is unprotected
could create a security hole.
Creating a new library table is not terribly expensive; in the case
of the math library, which has a large number of members, the cost
could be further reduced by using a lazy copy from an effectively
read-only original. Whether this is worth it or not would depend
on a large number of OS/machine dependent factors, but it appears
that there is some cut-off table size above which the lazy copy
is beneficial. But I digress, as usual.
What is clear is that it is faster to create a new instance of a
library table using the original "openlib" function than to do
a shallow copy either in Lua or in C. So my recommendation is
that the protocol for library loading be, for both Lua and C
libraries ("modules", if you prefer) that the module returns a
single function which takes a table as a parameter, fills that
table, and returns it. The table should not have a metatable,
in case the module function wishes to use lazy copying.
The global environment itself has very few keys aside from the
base library. Even the base library could install itself into
a table (in fact, it does, although it expects the table to
be the global table); however, the fact that certain base functions
refer to other base functions through global name lookup is awkward.
It is relatively straightforward to remove these references, but
the VM itself refers to two globals: next() and __pow(). It is
obviously undesirable to allow these to be modified by an untrusted
script; in the current version of Lua, the only solution is to
prevent sandboxed scripts from having write access to the global
environment. The simplest way to do this is to not give them
any access to the global environment. A number of base library
functions are not safe; these include, unfortunately, setfenv
In the absence of a change to the library loading protocol, the
simplest solution to the sandbox problem is to create safe
originals of each library table, including the base library
(only those functions which are actually safe), then create
a customised sandbox including lazy copies of these library
tables. This obviously needs to be applied to each library
module loaded into the Lua environment; of course, if a sandboxed
script is permitted to load its own modules, it is not necessary
to protect these tables.
However, allowing a sandboxed module access to the filesystem
strikes me as unwise, and I would prefer to provide the sandbox
with a function which it can use to request a module by
official module-name; the module loader would then provide the
module iff it the name were present in a safe module list.
There is also an issue with the io library as it is currently
implemented; all file objects share a metatable which contains
the functions which implement the actual I/O; since this metatable
is not locked, it cannot be safely shared with a sandboxed
script. This is unfortunate, because it would actually be useful
to provide sandboxed scripts with already-opened input and output
file objects; several modifications are necessary to the io
library to make this possible. As a side-note, it has always
puzzled me why API designers have universally implemented
standard file dialog boxes as returning filenames rather than
open file objects; in many environments, it would be safe to
allow an untrusted script to interact with the end-user to
request a file, but it is not desirable to provide an untrusted
script with the ability to open a file by filename; this can
easily be solved by providing standard file dialogs which return
opened file objects. In particular, the standard "save as" or
"write" file dialog could preclude over-writing an existing
file, while the "read" file dialog could provide a read-only
These comments on standard file dialogs should not be interpreted
to be limited to graphical interfaces. For example, a command line
utility which ran scripts might provide a function which returned
an opened read file handle for a file named on the command line; or
an opened write file handle for a file named on the command line
following the -o switch. Or it might implement a CLUI file picker.
I do have some more written about Lua globals and sandboxing, which
I will try to post later this week...