moai / moai-dev

This is the development repo of Moai SDK.
http://getmoai.com
940 stars 314 forks source link

Suggestion: a secure require() for moai cloud ( with code attached ) #96

Closed pygy closed 10 years ago

pygy commented 12 years ago

Require is very convenient.

The following code implements a secure require() functionality for theMoai cloud. Actually, you expose the real require function in the sandbox, but only provide a single, secure loader. require iterates through the package.loaders table for a loader suitable for the string it was passed. You can replace the standard loaders with your own.

The 'dot notation' is supported. The root directory, 'lib' and 'Library' are added to the standard path by default, but the user can add more if he wants to.

require'mod.submodule' will search the path for mod/submodule.lua and mod/submodule/init.lua. C libraries are not supported.

A similar loader could allow you to host modules for your users with more granularity than the current solution where you add everything in the global table by default.

This is adapted from code I wrote for a LÖVE library.

--------------------------------------------------------------------------------
-- Copyright P-Y Gérardy, 
-- Released under the Romantic WTF Public License, see below.
--------------------------------------------------------------------------------
-- Loader.lua
-- Use Loader.addPath( ... ) to add one or more directories to the path 
-- (no terminal slash). 

local moaiLibPath = ""

local 
function search (modname) 
   -- adapted (actually, almost copied verbatim) from PiL 2nd ed.
   modname = string.gsub( modname, "%.", "/" ) 
   for c in string.gmatch( moaiLibPath, "[^;]+" ) do
      local path = string.gsub(c, "?", modname) 
      local content = moai.code.find_file ( path )
      if content then
         return path, content 
      end
   end
   return nil   -- not found 
end

uploadedThroughZipFile = {
   [["some/file"]] = true,
   [["some/other/file"]] = true,
}

local 
function moaiLoader (modname)
   local path, content = search( modname )
   if path and uploadedThrougZipFile[ path ] then            
      return secure_loadstring( content ) 
      -- see http://article.gmane.org/gmane.comp.lang.lua.general/87526
      -- and http://article.gmane.org/gmane.comp.lang.lua.general/87528 (whoops)
   else                                   
      return false
   end
end

package.loaders={moaiLoader} -- wipe the standard loaders

local l={} -- doesn't have to be public
function l.addPath (...)
   for _, path in pairs{...} do
      moaiLibPath = moaiLibPath .. path .. "/?/init.lua;" .. path .. "/?.lua;"
   end
end

l.addPath("","lib","Library")

return l

--[[----------------------------------------------------------------------------
             The Romantic WTF public license.
             --------------------------------
             a.k.a. version "<3" or simply v3

     Dear Moai team,

     this short piece of code

                                      \ 
                                       '.,__
                                    \  /
                                     '/,__
                                     /
                                    /
                                   /
                has been          / released
           - - - - - - - -       - - - - - - - - 
         under  the  Romantic   WTF Public License.
        - - - - - - - - - - -',' - - - - - - - - - - 
        I hereby grant you an irrevocable license to
         - - - - - - - - - - - - - - - - - - - - -
           do what the gentle caress you want to
                - - - - - - - - - - - - - - -  
                    with   this   little
                       - - - - - - - - 
                        / snippet.
                       /  - - - -
                      /    Love,
                #    /       -
                ##  /  ##    ,
                #######     
                #####
                ###
                #

     -- Pierre-Yves.

]]------------------------------------------------------------------------------
pygy commented 12 years ago

I just realized that you were providing loadstring, which makes it possible to completely implement require from the application.

It makes me wonder why you restrict moai.code.loadsource(filename) to files uploaded through the zip file , though...

patrickmeehan commented 12 years ago

Rob - wanted to make sure you didn't miss this one.

larubbio commented 12 years ago

I'll take a look at this since I would like to allow people to choose what they want to include instead of having every thing available by default in the sandbox (although there are performance gains from just loading everything once at the start). Additionally I'd like to allow users the ability to reference and load code from other services they own so they can create libraries that they share across services.

pygy commented 12 years ago

From a security and performance stand point, I think it would make sense to provide two isolated filesystem roots per service, with different functions for doing io.

This would allow to cache the code partition on each application server, and would prevent a range of arbitrary code execution vulnerabilities.

Is loadstring a most wanted feature? Would it still be if you implemented the above suggestion?

larubbio commented 12 years ago

One thing we want to provide is a shared data space across services owned by a user. It's easy enough to swap the above per-service data space you mention with one that is instead service wide. We could also provide three if users want it.

Also for the R/W spaces is re-implementing the file.* operations on top of mongo desirable or is sticking with the mongo gridfs api sufficient?

pygy commented 12 years ago

You're right about io.* (and not file.* as I wrote earlier) it makes little sense to provide them when Mongo can store structured data. Also, incremental writes would be very inefficient.

pygy commented 12 years ago

I completely overlooked the issue of environments (require loads libraries in _G). Here's require, with that concern in mind. It is based on PiL2 and the Lua source. I'm in the process of writing module.

sandbox.package={loaded={}}

local _loaded,                _loaders 
    = sandbox.package.loaded, package.loaders

local _sentinel = {}

function sandbox.require (name) 
   if _loaded[name] == _sentinel then
      error("loop or previous error loading module "..name)
   end
   if not _loaded[name] then         -- module not loaded yet?
      _loaded[name] = _sentinel
      local trace, loader = ""
      for _,l in ipairs(_loaders) do 
      -- limited to package.preload and local source files on local fs.
         res = l(name)
         id type(res) == "function" then loader=res; break end
         if type(res) == string then trace = trace..res end
      end
      if loader == nil then 
         error("unable to load module " .. name .. trace) 
      end 
      local res = loader(name) 
      if res ~= nil 
      then _loaded[name] = res 
      else _loaded[name] = true
      end
   end
   return _loaded[name] 
end
larubbio commented 12 years ago

Wasn't module removed in lua 5.2? I"ll review this and try to get it in shortly. Another feature I'd like to expose it to allow users to do things like:

require 'moai://'

So instead of having to bundle the same code in two different services they can create and load libraries.

pygy commented 12 years ago

Yes, it was removed, but a lot of 5.1 libraries use it. You already use 5.2 in the cloud?

For the preloaded libs, you could populate the package.preload table. The first loader (searcher in 5.2 nomenclature) looks there for packages. You get the best of both world: the libs are preloaded, but only run when required, and it doesn't pollute the global environment.

makotok commented 10 years ago

MOAI Cloud ended. Close.