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