l-sandbox.lua /size: 9604 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['l-sandbox'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- We use string instead of function variables, so 'io.open' instead of io.open. That
10-- way we can still intercept repetetive overloads. One complication is that when we use
11-- sandboxed functions in helpers in the sanbox checkers, we can get a recursion loop
12-- so for that reason we need to keep originals around till we enable the sandbox.
13
14-- if sandbox then return end
15
16local global   = _G
17local next     = next
18local unpack   = unpack or table.unpack
19local type     = type
20local tprint   = texio and texio.write_nl or print
21local tostring = tostring
22local format   = string.format -- no formatters yet
23local concat   = table.concat
24local sort     = table.sort
25local gmatch   = string.gmatch
26local gsub     = string.gsub
27local requiem  = require
28
29sandbox            = { }
30local sandboxed    = false
31local overloads    = { }
32local skiploads    = { }
33local initializers = { }
34local finalizers   = { }
35local originals    = { }
36local comments     = { }
37local trace        = false
38local logger       = false
39local blocked      = { }
40
41-- this comes real early, so that we can still alias
42
43local function report(...)
44    tprint("sandbox         ! " .. format(...)) -- poor mans tracer
45end
46
47sandbox.report = report
48
49function sandbox.setreporter(r)
50    report         = r
51    sandbox.report = r
52end
53
54function sandbox.settrace(v)
55    trace = v
56end
57
58function sandbox.setlogger(l)
59    logger = type(l) == "function" and l or false
60end
61
62local function register(func,overload,comment)
63    if type(func) == "function" then
64        if type(overload) == "string" then
65            comment  = overload
66            overload = nil
67        end
68        local function f(...)
69            if sandboxed then
70                local overload = overloads[f]
71                if overload then
72                    if logger then
73                        local result = { overload(func,...) }
74                        logger {
75                            comment   = comments[f] or tostring(f),
76                            arguments = { ... },
77                            result    = result[1] and true or false,
78                        }
79                        return unpack(result)
80                    else
81                        return overload(func,...)
82                    end
83                else
84                    -- ignored, maybe message
85                end
86            else
87                return func(...)
88            end
89        end
90        if comment then
91            comments[f] = comment
92            if trace then
93                report("registering function: %s",comment)
94            end
95        end
96        overloads[f] = overload or false
97        originals[f] = func
98        return f
99    end
100end
101
102local function redefine(func,comment)
103    if type(func) == "function" then
104        skiploads[func] = comment or comments[func] or "unknown"
105        if overloads[func] == false then
106            overloads[func] = nil -- not initialized anyway
107        end
108    end
109end
110
111sandbox.register = register
112sandbox.redefine = redefine
113
114function sandbox.original(func)
115    return originals and originals[func] or func
116end
117
118function sandbox.overload(func,overload,comment)
119    comment = comment or comments[func] or "?"
120    if type(func) ~= "function" then
121        if trace then
122            report("overloading unknown function: %s",comment)
123        end
124    elseif type(overload) ~= "function" then
125        if trace then
126            report("overloading function with bad overload: %s",comment)
127        end
128    elseif overloads[func] == nil then
129        if trace then
130            report("function is not registered: %s",comment)
131        end
132    elseif skiploads[func] then
133        if trace then
134            report("function is not skipped: %s",comment)
135        end
136    else
137        if trace then
138            report("overloading function: %s",comment)
139        end
140        overloads[func] = overload
141    end
142    return func
143end
144
145local function whatever(specification,what,target)
146    if type(specification) ~= "table" then
147        report("%s needs a specification",what)
148    elseif type(specification.category) ~= "string" or type(specification.action) ~= "function" then
149        report("%s needs a category and action",what)
150    elseif not sandboxed then
151        target[#target+1] = specification
152    elseif trace then
153        report("already enabled, discarding %s",what)
154    end
155end
156
157function sandbox.initializer(specification)
158    whatever(specification,"initializer",initializers)
159end
160
161function sandbox.finalizer(specification)
162    whatever(specification,"finalizer",finalizers)
163end
164
165function require(name)
166    local n = gsub(name,"^.*[\\/]","")
167    local n = gsub(n,"[%.].*$","")
168    local b = blocked[n]
169    if b == false then
170        return nil -- e.g. ffi
171    elseif b then
172        if trace then
173            report("using blocked: %s",n)
174        end
175        return b
176    else
177        if trace then
178            report("requiring: %s",name)
179        end
180        return requiem(name)
181    end
182end
183
184function blockrequire(name,lib)
185    if trace then
186        report("preventing reload of: %s",name)
187    end
188    blocked[name] = lib or _G[name] or false
189end
190
191function sandbox.enable()
192    if not sandboxed then
193        debug = {
194            traceback = debug.traceback,
195        }
196        for i=1,#initializers do
197            initializers[i].action()
198        end
199        for i=1,#finalizers do
200            finalizers[i].action()
201        end
202        local nnot = 0
203        local nyes = 0
204        local cnot = { }
205        local cyes = { }
206        local skip = { }
207        for k, v in next, overloads do
208            local c = comments[k]
209            if v then
210                if c then
211                    cyes[#cyes+1] = c
212                else -- if not skiploads[k] then
213                    nyes = nyes + 1
214                end
215            else
216                if c then
217                    cnot[#cnot+1] = c
218                else -- if not skiploads[k] then
219                    nnot = nnot + 1
220                end
221            end
222        end
223        for k, v in next, skiploads do
224            skip[#skip+1] = v
225        end
226        if #cyes > 0 then
227            sort(cyes)
228            report("overloaded known: %s",concat(cyes," | "))
229        end
230        if nyes > 0 then
231            report("overloaded unknown: %s",nyes)
232        end
233        if #cnot > 0 then
234            sort(cnot)
235            report("not overloaded known: %s",concat(cnot," | "))
236        end
237        if nnot > 0 then
238            report("not overloaded unknown: %s",nnot)
239        end
240        if #skip > 0 then
241            sort(skip)
242            report("not overloaded redefined: %s",concat(skip," | "))
243        end
244        --
245        initializers = nil
246        finalizers   = nil
247        originals    = nil
248        sandboxed    = true
249    end
250end
251
252blockrequire("lfs",lfs)
253blockrequire("io",io)
254blockrequire("os",os)
255blockrequire("ffi",ffi)
256
257-- require = register(require,"require")
258
259-- we sandbox some of the built-in functions now:
260
261-- todo: require
262-- todo: load
263
264local function supported(library)
265    local l = _G[library]
266 -- if l then
267 --     for k, v in next, l do
268 --         report("%s.%s",library,k)
269 --     end
270 -- end
271    return l
272end
273
274-- io.tmpfile : we don't know where that one ends up but probably is user temp space
275-- os.tmpname : no need to deal with this: outputs rubish anyway (\s9v0. \s9v0.1 \s9v0.2 etc)
276-- os.tmpdir  : not needed either (luatex.vob000 luatex.vob000 etc)
277
278-- os.setenv  : maybe
279-- require    : maybe (normally taken from tree)
280-- http etc   : maybe (all schemes that go outside)
281
282loadfile = register(loadfile,"loadfile")
283
284if supported("lua") then
285    lua.openfile = register(lua.openfile,"lua.openfile")
286end
287
288if supported("io") then
289    io.open   = register(io.open,  "io.open")
290    io.popen  = register(io.popen, "io.popen") -- needs checking
291    io.lines  = register(io.lines, "io.lines")
292    io.output = register(io.output,"io.output")
293    io.input  = register(io.input, "io.input")
294end
295
296if supported("os") then
297    os.execute = register(os.execute,"os.execute")
298    os.spawn   = register(os.spawn,  "os.spawn")
299    os.exec    = register(os.exec,   "os.exec")
300    os.rename  = register(os.rename, "os.rename")
301    os.remove  = register(os.remove, "os.remove")
302end
303
304if supported("lfs") then
305    lfs.chdir             = register(lfs.chdir,            "lfs.chdir")
306    lfs.mkdir             = register(lfs.mkdir,            "lfs.mkdir")
307    lfs.rmdir             = register(lfs.rmdir,            "lfs.rmdir")
308    lfs.isfile            = register(lfs.isfile,           "lfs.isfile")
309    lfs.isdir             = register(lfs.isdir,            "lfs.isdir")
310    lfs.attributes        = register(lfs.attributes,       "lfs.attributes")
311    lfs.dir               = register(lfs.dir,              "lfs.dir")
312    lfs.lock_dir          = register(lfs.lock_dir,         "lfs.lock_dir")
313    lfs.touch             = register(lfs.touch,            "lfs.touch")
314    lfs.link              = register(lfs.link,             "lfs.link")
315    lfs.setmode           = register(lfs.setmode,          "lfs.setmode")
316    lfs.readlink          = register(lfs.readlink,         "lfs.readlink")
317    lfs.shortname         = register(lfs.shortname,        "lfs.shortname")
318    lfs.symlinkattributes = register(lfs.symlinkattributes,"lfs.symlinkattributes")
319end
320
321