1if not modules then modules = { } end modules ['util-sbx'] = {
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
10
11
12
13
14
15if not sandbox then require("l-sandbox") end
16
17local next, type = next, type
18
19local replace = utilities.templates.replace
20local collapsepath = file.collapsepath
21local expandname = dir.expandname
22local sortedhash = table.sortedhash
23local lpegmatch = lpeg.match
24local platform = os.type
25local P, S, C = lpeg.P, lpeg.S, lpeg.C
26local gsub = string.gsub
27local lower = string.lower
28local find = string.find
29local concat = string.concat
30local unquoted = string.unquoted
31local optionalquoted = string.optionalquoted
32local basename = file.basename
33local nameonly = file.nameonly
34
35local sandbox = sandbox
36local validroots = { }
37local validrunners = { }
38local validbinaries = true
39local validlibraries = true
40local validators = { }
41local finalized = nil
42local trace = false
43
44local p_validroot = nil
45local p_split = lpeg.firstofsplit(" ")
46
47local report = logs.reporter("sandbox")
48
49trackers.register("sandbox",function(v) trace = v end)
50
51sandbox.setreporter(report)
52
53sandbox.finalizer {
54 category = "files",
55 action = function()
56 finalized = true
57 end
58}
59
60local function registerroot(root,what)
61 if finalized then
62 report("roots are already finalized")
63 else
64 if type(root) == "table" then
65 root, what = root[1], root[2]
66 end
67 if type(root) == "string" and root ~= "" then
68 root = collapsepath(expandname(root))
69 if what == "r" or what == "ro" or what == "readable" then
70 what = "read"
71 elseif what == "w" or what == "wo" or what == "writable" then
72 what = "write"
73 end
74
75 validroots[root] = what == "write" or false
76 end
77 end
78end
79
80sandbox.finalizer {
81 category = "files",
82 action = function()
83 if p_validroot then
84 report("roots are already initialized")
85 else
86 sandbox.registerroot(".","write")
87
88 for name in sortedhash(validroots) do
89 if p_validroot then
90 p_validroot = P(name) + p_validroot
91 else
92 p_validroot = P(name)
93 end
94 end
95 p_validroot = p_validroot / validroots
96 end
97 end
98}
99
100local function registerbinary(name)
101 if finalized then
102 report("binaries are already finalized")
103 elseif type(name) == "string" and name ~= "" then
104 if not validbinaries then
105 return
106 end
107 if validbinaries == true then
108 validbinaries = { [name] = true }
109 else
110 validbinaries[name] = true
111 end
112 elseif name == true then
113 validbinaries = { }
114 end
115end
116
117local function registerlibrary(name)
118 if finalized then
119 report("libraries are already finalized")
120 elseif type(name) == "string" and name ~= "" then
121 if not validlibraries then
122 return
123 end
124 if validlibraries == true then
125 validlibraries = { [nameonly(name)] = true }
126 else
127 validlibraries[nameonly(name)] = true
128 end
129 elseif name == true then
130 validlibraries = { }
131 end
132end
133
134
135
136local p_write = S("wa") p_write = (1 - p_write)^0 * p_write
137local p_path = S("\\/~$%:") p_path = (1 - p_path )^0 * p_path
138
139local function normalized(name)
140 if platform == "windows" then
141 name = gsub(name,"/","\\")
142 end
143 return name
144end
145
146function sandbox.possiblepath(name)
147 return lpegmatch(p_path,name) and true or false
148end
149
150local filenamelogger = false
151
152function sandbox.setfilenamelogger(l)
153 filenamelogger = type(l) == "function" and l or false
154end
155
156local function validfilename(name,what)
157 if p_validroot and type(name) == "string" and lpegmatch(p_path,name) then
158 local asked = collapsepath(expandname(name))
159
160
161
162 local okay = lpegmatch(p_validroot,asked)
163 if okay == true then
164
165 if filenamelogger then
166 filenamelogger(name,"w",asked,true)
167 end
168 return name
169 elseif okay == false then
170
171 if not what then
172
173 if filenamelogger then
174 filenamelogger(name,"r",asked,true)
175 end
176 return name
177 elseif lpegmatch(p_write,what) then
178 if filenamelogger then
179 filenamelogger(name,"w",asked,false)
180 end
181 return
182 else
183 if filenamelogger then
184 filenamelogger(name,"r",asked,true)
185 end
186 return name
187 end
188 elseif filenamelogger then
189 filenamelogger(name,"*",name,false)
190 end
191 else
192 return name
193 end
194end
195
196local function readable(name,finalized)
197
198
199
200 return validfilename(name,"r")
201end
202
203local function normalizedreadable(name,finalized)
204
205
206
207 local valid = validfilename(name,"r")
208 if valid then
209 return normalized(valid)
210 end
211end
212
213local function writeable(name,finalized)
214
215
216
217 return validfilename(name,"w")
218end
219
220local function normalizedwriteable(name,finalized)
221
222
223
224 local valid = validfilename(name,"w")
225 if valid then
226 return normalized(valid)
227 end
228end
229
230validators.readable = readable
231validators.writeable = normalizedwriteable
232validators.normalizedreadable = normalizedreadable
233validators.normalizedwriteable = writeable
234validators.filename = readable
235
236table.setmetatableindex(validators,function(t,k)
237 if k then
238 t[k] = readable
239 end
240 return readable
241end)
242
243
244
245
246
247function validators.string(s,finalized)
248
249 if finalized and suspicious(s) then
250 return ""
251 else
252 return s
253 end
254end
255
256function validators.cache(s)
257 if finalized then
258 return basename(s)
259 else
260 return s
261 end
262end
263
264function validators.url(s)
265 if finalized and find("^file:") then
266 return ""
267 else
268 return s
269 end
270end
271
272
273
274local function filehandlerone(action,one,...)
275 local checkedone = validfilename(one)
276 if checkedone then
277 return action(one,...)
278 else
279
280 end
281end
282
283local function filehandlertwo(action,one,two,...)
284 local checkedone = validfilename(one)
285 if checkedone then
286 local checkedtwo = validfilename(two)
287 if checkedtwo then
288 return action(one,two,...)
289 else
290
291 end
292 else
293
294 end
295end
296
297local function iohandler(action,one,...)
298 if type(one) == "string" then
299 local checkedone = validfilename(one)
300 if checkedone then
301 return action(one,...)
302 end
303 elseif one then
304 return action(one,...)
305 else
306 return action()
307 end
308end
309
310
311
312
313
314
315
316
317
318local osexecute = sandbox.original(os.execute)
319local iopopen = sandbox.original(io.popen)
320local reported = { }
321
322local function validcommand(name,program,template,checkers,defaults,variables,reporter,strict)
323 if validbinaries ~= false and (validbinaries == true or validbinaries[program]) then
324 local binpath = nil
325 if variables then
326 for variable, value in next, variables do
327 local chktype = checkers[variable]
328 if chktype == "verbose" then
329
330 else
331 local checker = validators[chktype]
332 if checker and type(value) == "string" then
333 value = checker(unquoted(value),strict)
334 if value then
335 variables[variable] = optionalquoted(value)
336 else
337 report("variable %a with value %a fails the check",variable,value)
338 return
339 end
340 else
341 report("variable %a has no checker",variable)
342 return
343 end
344 end
345 end
346 for variable, default in next, defaults do
347 local value = variables[variable]
348 if not value or value == "" then
349 local chktype = checkers[variable]
350 if chktype == "verbose" then
351
352 elseif type(default) == "string" then
353 local checker = validators[chktype]
354 if checker then
355 default = checker(unquoted(default),strict)
356 if default then
357 variables[variable] = optionalquoted(default)
358 else
359 report("variable %a with default %a fails the check",variable,default)
360 return
361 end
362 end
363 end
364 end
365 end
366 binpath = variables.binarypath
367 end
368 if type(binpath) == "string" and binpath ~= "" then
369
370
371 program = binpath .. "/" .. program
372 end
373 local command = program .. " " .. replace(template,variables)
374 if reporter then
375 reporter("executing runner %a: %s",name,command)
376 elseif trace then
377 report("executing runner %a: %s",name,command)
378 end
379 return command
380 elseif not reported[name] then
381 report("executing program %a of runner %a is not permitted",program,name)
382 reported[name] = true
383 end
384end
385
386local runners = {
387
388
389
390 resultof = function(...)
391 local command = validcommand(...)
392 if command then
393 if trace then
394 report("resultof: %s",command)
395 end
396 local handle = iopopen(command,"rb")
397 if handle then
398 local result = handle:read("*all") or ""
399 handle:close()
400 return result
401 end
402 end
403 end,
404 execute = function(...)
405 local command = validcommand(...)
406 if command then
407 if trace then
408 report("execute: %s",command)
409 end
410 local okay = osexecute(command)
411 return okay
412 end
413 end,
414 pipeto = function(...)
415 local command = validcommand(...)
416 if command then
417 if trace then
418 report("pipeto: %s",command)
419 end
420 return iopopen(command,"w")
421 end
422 end,
423}
424
425function sandbox.registerrunner(specification)
426 if type(specification) == "string" then
427 local wrapped = validrunners[specification]
428 inspect(table.sortedkeys(validrunners))
429 if wrapped then
430 return wrapped
431 else
432 report("unknown predefined runner %a",specification)
433 return
434 end
435 end
436 if type(specification) ~= "table" then
437 report("specification should be a table (or string)")
438 return
439 end
440 local name = specification.name
441 if type(name) ~= "string" then
442 report("invalid name, string expected",name)
443 return
444 end
445 if validrunners[name] then
446 report("invalid name, runner %a already defined",name)
447 return
448 end
449 local program = specification.program
450 if type(program) == "string" then
451
452 elseif type(program) == "table" then
453 program = program[platform] or program.default or program.unix
454 end
455 if type(program) ~= "string" or program == "" then
456 report("invalid runner %a specified for platform %a",name,platform)
457 return
458 end
459 local template = specification.template
460 if not template then
461 report("missing template for runner %a",name)
462 return
463 end
464 local method = specification.method or "execute"
465 local checkers = specification.checkers or { }
466 local defaults = specification.defaults or { }
467 local internal = specification.internal
468 local runner = runners[method]
469 if runner then
470 local finalized = finalized
471 local internalized = false
472 local wrapped = function(variables)
473 if internal and not internalized then
474
475
476 internal = internal(specification)
477 if type(internal) ~= "function" then
478 internal = false
479 end
480 internalized = true
481 end
482 return (internal or runner)(name,program,template,checkers,defaults,variables,specification.reporter,finalized)
483 end
484 validrunners[name] = wrapped
485 return wrapped
486 else
487 validrunners[name] = nil
488 report("invalid method for runner %a",name)
489 end
490end
491
492function sandbox.getrunner(name)
493 return name and validrunners[name]
494end
495
496local function suspicious(str)
497 return (find(str,"[/\\]") or find(command,"..",1,true)) and true or false
498end
499
500local function binaryrunner(action,command,...)
501 if validbinaries == false then
502
503 report("no binaries permitted, ignoring command: %s",command)
504 return
505 end
506 if type(command) ~= "string" then
507
508 report("command should be a string")
509 return
510 end
511 local program = lpegmatch(p_split,command)
512 if not program or program == "" then
513 report("unable to filter binary from command: %s",command)
514 return
515 end
516 if validbinaries == true then
517
518 elseif not validbinaries[program] then
519 report("binary not permitted, ignoring command: %s",command)
520 return
521 elseif suspicious(command) then
522 report("/ \\ or .. found, ignoring command (use sandbox.registerrunner): %s",command)
523 return
524 end
525 return action(command,...)
526end
527
528local function dummyrunner(action,command,...)
529 if type(command) == "table" then
530 command = concat(command," ",command[0] and 0 or 1)
531 end
532 report("ignoring command: %s",command)
533end
534
535sandbox.filehandlerone = filehandlerone
536sandbox.filehandlertwo = filehandlertwo
537sandbox.iohandler = iohandler
538
539do
540
541 local library = optional.library
542 local foreign = optional.foreign
543 local reported = { }
544 local libraryload = library.load
545 local foreignload = foreign.load
546
547 function library.load(name,...)
548 if validlibraries == false then
549
550 elseif validlibraries == true then
551
552 return libraryload(name,...)
553 elseif validlibraries[nameonly(name)] then
554
555 return libraryload(name,...)
556 else
557
558 end
559 if not reported[name] then
560 report("using library %a is not permitted",name)
561 reported[name] = true
562 end
563 return nil
564 end
565
566 function foreign.load(name,...)
567 if validlibraries == false then
568
569 elseif validlibraries == true then
570
571 return foreignload(name,...)
572 elseif validlibraries[nameonly(name)] then
573
574 return foreignload(name,...)
575 else
576
577 end
578 if not reported[name] then
579 report("using foreign %a is not permitted",name)
580 reported[name] = true
581 end
582 return nil
583 end
584
585
586
587 function sandbox.disablelibraries()
588 validlibraries = false
589 library.load = function() end
590 foreign.load = function() end
591 end
592
593 function sandbox.disablerunners()
594 validbinaries = false
595 end
596
597end
598
599
600
601local overload = sandbox.overload
602local register = sandbox.register
603
604 overload(loadfile, filehandlerone,"loadfile")
605
606if io then
607 overload(io.open, filehandlerone,"io.open")
608 overload(io.popen, binaryrunner, "io.popen")
609 overload(io.input, iohandler, "io.input")
610 overload(io.output, iohandler, "io.output")
611 overload(io.lines, filehandlerone,"io.lines")
612end
613
614if os then
615 overload(os.execute, binaryrunner, "os.execute")
616 overload(os.spawn, dummyrunner, "os.spawn")
617 overload(os.exec, dummyrunner, "os.exec")
618 overload(os.resultof, binaryrunner, "os.resultof")
619 overload(os.pipeto, binaryrunner, "os.pipeto")
620 overload(os.rename, filehandlertwo,"os.rename")
621 overload(os.remove, filehandlerone,"os.remove")
622end
623
624if lfs then
625 overload(lfs.chdir, filehandlerone,"lfs.chdir")
626 overload(lfs.mkdir, filehandlerone,"lfs.mkdir")
627 overload(lfs.rmdir, filehandlerone,"lfs.rmdir")
628 overload(lfs.isfile, filehandlerone,"lfs.isfile")
629 overload(lfs.isdir, filehandlerone,"lfs.isdir")
630 overload(lfs.attributes, filehandlerone,"lfs.attributes")
631 overload(lfs.dir, filehandlerone,"lfs.dir")
632 overload(lfs.lock_dir, filehandlerone,"lfs.lock_dir")
633 overload(lfs.touch, filehandlerone,"lfs.touch")
634 overload(lfs.link, filehandlertwo,"lfs.link")
635 overload(lfs.setmode, filehandlerone,"lfs.setmode")
636 overload(lfs.readlink, filehandlerone,"lfs.readlink")
637 overload(lfs.shortname, filehandlerone,"lfs.shortname")
638 overload(lfs.symlinkattributes,filehandlerone,"lfs.symlinkattributes")
639end
640
641
642
643if zip then
644 zip.open = register(zip.open, filehandlerone,"zip.open")
645end
646
647sandbox.registerroot = registerroot
648sandbox.registerbinary = registerbinary
649sandbox.registerlibrary = registerlibrary
650sandbox.validfilename = validfilename
651
652
653
654
655
656
657
658
659 |