web-dev-qa-db-fra.com

Comment créer un sandbox Lua sécurisé?

Lua semble donc idéal pour implémenter des "scripts utilisateur" sécurisés dans mon application.

Cependant, la plupart des exemples d'intégration de lua semblent inclure le chargement de toutes les bibliothèques standard, y compris "io" et "package".

Je peux donc exclure ces bibliothèques de mon interprète, mais même la bibliothèque de base inclut les fonctions "dofile" et "loadfile" qui accèdent au système de fichiers.

Comment puis-je supprimer/bloquer des fonctions dangereuses comme celles-ci, sans se retrouver avec un interprète qui n'a même pas de trucs de base comme la fonction "ipairs"?

74
user150008

Vous pouvez définir l'environnement de fonction dans lequel vous exécutez le code non approuvé via setfenv (). Voici un aperçu:

local env = {ipairs}
setfenv(user_script, env)
pcall(user_script)

Le user_script la fonction ne peut accéder qu'à ce qui se trouve dans son environnement. Ainsi, vous pouvez ensuite ajouter explicitement les fonctions auxquelles vous souhaitez que le code non approuvé ait accès (liste blanche). Dans ce cas, le script utilisateur n'a accès qu'à ipairs mais à rien d'autre (dofile, loadfile, etc.).

Voir Lua Sandboxes pour un exemple et plus d'informations sur le lua sandboxing.

53
Karl Voigtland

Voici une solution pour Lua 5.2 (incluant un exemple d'environnement qui fonctionnerait également en 5.1):

-- save a pointer to globals that would be unreachable in sandbox
local e=_ENV

-- sample sandbox environment
sandbox_env = {
  ipairs = ipairs,
  next = next,
  pairs = pairs,
  pcall = pcall,
  tonumber = tonumber,
  tostring = tostring,
  type = type,
  unpack = unpack,
  coroutine = { create = coroutine.create, resume = coroutine.resume, 
      running = coroutine.running, status = coroutine.status, 
      wrap = coroutine.wrap },
  string = { byte = string.byte, char = string.char, find = string.find, 
      format = string.format, gmatch = string.gmatch, gsub = string.gsub, 
      len = string.len, lower = string.lower, match = string.match, 
      rep = string.rep, reverse = string.reverse, sub = string.sub, 
      upper = string.upper },
  table = { insert = table.insert, maxn = table.maxn, remove = table.remove, 
      sort = table.sort },
  math = { abs = math.abs, acos = math.acos, asin = math.asin, 
      atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos, 
      cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor, 
      fmod = math.fmod, frexp = math.frexp, huge = math.huge, 
      ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max, 
      min = math.min, modf = math.modf, pi = math.pi, pow = math.pow, 
      rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh, 
      sqrt = math.sqrt, tan = math.tan, tanh = math.tanh },
  os = { clock = os.clock, difftime = os.difftime, time = os.time },
}

function run_sandbox(sb_env, sb_func, ...)
  local sb_orig_env=_ENV
  if (not sb_func) then return nil end
  _ENV=sb_env
  local sb_ret={e.pcall(sb_func, ...)}
  _ENV=sb_orig_env
  return e.table.unpack(sb_ret)
end

Ensuite, pour l'utiliser, vous appelleriez votre fonction (my_func) comme suit:

pcall_rc, result_or_err_msg = run_sandbox(sandbox_env, my_func, arg1, arg2)
33
BMitch

Lua live demo contient un sandbox (spécialisé). Le source est disponible gratuitement.

14
lhf

L'un des moyens les plus simples d'éliminer les indésirables est de charger d'abord un script Lua de votre propre conception, qui fait des choses comme:

load = nil
loadfile = nil
dofile = nil

Alternativement, vous pouvez utiliser setfenv pour créer un environnement restreint dans lequel vous pouvez insérer des fonctions sûres spécifiques.

Le sandbox totalement sûr est un peu plus difficile. Si vous chargez du code de n'importe où, sachez que le code précompilé peut planter Lua. Même du code complètement restreint peut entrer dans une boucle infinie et se bloquer indéfiniment si vous n'avez pas de système pour l'arrêter.

4
John Calsbeek

Vous pouvez utiliser le lua_setglobal fonction fournie par l'API Lua pour définir ces valeurs dans l'espace de noms global sur nil, ce qui empêchera efficacement tout script utilisateur d'y accéder.

lua_pushnil(state_pointer);
lua_setglobal(state_pointer, "io");

lua_pushnil(state_pointer);
lua_setglobal(state_pointer, "loadfile");

...etc...
3
Amber

Si vous utilisez Lua 5.1, essayez ceci:

blockedThings = {'os', 'debug', 'loadstring', 'loadfile', 'setfenv', 'getfenv'}
scriptName = "user_script.lua"

function InList(list, val) 
    for i=1, #list do if list[i] == val then 
        return true 
    end 
end

local f, msg = loadfile(scriptName)

local env = {}
local envMT = {}
local blockedStorageOverride = {}
envMT.__index = function(tab, key)
    if InList(blockedThings, key) then return blockedStorageOverride[key] end
    return rawget(tab, key) or getfenv(0)[key]
end
envMT.__newindex = function(tab, key, val)
    if InList(blockedThings, key) then
        blockedStorageOverride[key] = val
    else
        rawset(tab, key, val)
    end
end

if not f then
    print("ERROR: " .. msg)
else
    setfenv(f, env)
    local a, b = pcall(f)
    if not a then print("ERROR: " .. b) end
end
1
DrowsySaturn