sandkiste_lua 0.4.0

Sandbox for Lua scripts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/// String key for the Lua registry to access the execution limit error object
#define CMACH_EXECLIMIT_REGKEY "cmach_execlimit"

/// State of Lua machine with extensions (e.g. resource usage control)
typedef struct {
    // state of Lua machine
    lua_State *L;
    // allocated memory in bytes
    size_t mem_used;
    // memory limit in bytes (zero = disable limit)
    size_t mem_limit;
    // pointer identifying execution limit error
    void *execlimit_error;
} cmach_lua_t;

/// Allocator that respects memory limits in `cmach_lua_t` passed as `*ud` pointer
static void *cmach_lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
    cmach_lua_t *S = ud;
    void *new;
    if (nsize == 0) {
        // release memory
        S->mem_used -= osize;
        free(ptr);
        return NULL;
    } else {
        // ignore contents of `osize` unless this is a re-allocation
        if (!ptr) osize = 0;
        // allocate or reallocate
        if (S->mem_limit && S->mem_used - osize + nsize > S->mem_limit) return NULL;
        new = realloc(ptr, nsize);
        if (!new) return NULL;
        S->mem_used -= osize;
        S->mem_used += nsize;
        return new;
    }
}

/// Panic handler providing some debug information on strerr before process abort
static int cmach_lua_panic(lua_State *L) {
    const char *msg = lua_tostring(L, -1);
    if (lua_checkstack(L, 1)) {
        msg = luaL_tolstring(L, -1, NULL);
    } else {
        msg = lua_tostring(L, -1);
        if (!msg) msg = "error not a string";
    }
    fprintf(stderr, "Lua panic: %s\n", msg);
    return 0;
}

/// Helper function returning string representation of execution limit error
int cmach_lua_execlimit_tostring(lua_State *L) {
    lua_pushliteral(L, "execution limit reached");
    return 1;
}

/// Helper function to perform some basic setup
static int cmach_lua_setup(lua_State *L) {
    // create table for execution limit error
    lua_createtable(L, 0, 0);
    // create and set metatable for execution limit error
    lua_createtable(L, 0, 1);
    lua_pushcfunction(L, cmach_lua_execlimit_tostring);
    lua_setfield(L, -2, "__tostring");
    lua_setmetatable(L, -2);
    // create pointer to identify execution limit error
    // and store on last but one stack position
    lua_pushlightuserdata(L, (void *)lua_topointer(L, -1));
    lua_insert(L, -2);
    // store table for execution limit error in Lua registry
    // (pops one element from stack)
    lua_setfield(L, LUA_REGISTRYINDEX, CMACH_EXECLIMIT_REGKEY);
    // return
    // * pointer to execution limit error
    return 1;
}

/// Error message handler
int cmach_lua_errmsgh(lua_State *L) {
    lua_Debug ar = { 0, };
    // create table to hold four values
    lua_createtable(L, 4, 0);
    // use error object as first value
    lua_pushvalue(L, 1);
    lua_rawseti(L, -2, 1);
    // use traceback as second value
    luaL_traceback(L, L, NULL, 2);
    lua_rawseti(L, -2, 2);
    // get more information
    if (lua_getstack(L, 2, &ar)) {
        lua_getinfo(L, "Sl", &ar);
        // use chunk name as third value
        lua_pushstring(L, ar.short_src);
        lua_rawseti(L, -2, 3);
        // use line number (or -1 if unavailable) as fourth value
        lua_pushinteger(L, ar.currentline);
        lua_rawseti(L, -2, 4);
    }
    // return created table
    return 1;
}

/// Handler for exhausted execution count
static void cmach_lua_exhausted(lua_State *L, lua_Debug *ar) {
    (void)ar;
    lua_getfield(L, LUA_REGISTRYINDEX, CMACH_EXECLIMIT_REGKEY);
    lua_error(L);
}

/// Set execution count limit
void cmach_lua_execlimit(cmach_lua_t *S, int count) {
    lua_sethook(S->L, cmach_lua_exhausted, LUA_MASKCOUNT, count);
}

/// Release of machine
void cmach_lua_free(cmach_lua_t *S) {
    lua_close(S->L);
    free(S);
}

/// Creation of machine
cmach_lua_t *cmach_lua_new() {
    cmach_lua_t *S;
    lua_State *L;
    int status;
    // try to allocate extended state
    S = calloc(1, sizeof(cmach_lua_t));
    if (!S) return NULL;
    // try to create new `lua_State`
    L = lua_newstate(cmach_lua_alloc, S);
    if (!L) {
        free(S);
        return NULL;
    }
    // set `lua_State` in extended state
    S->L = L;
    // register Lua panic handler (for debugging purposes)
    lua_atpanic(L, cmach_lua_panic);
    // perform some basic setup (`cmach_lua_setup`)
    lua_pushcfunction(L, cmach_lua_setup);
    status = lua_pcall(L, 0, 1, 0);
    if (status != LUA_OK) {
        // escalate non-memory errors
        if (status != LUA_ERRMEM) lua_error(L);
        // return NULL on memory errors
        cmach_lua_free(S);
        return NULL;
    }
    // store return values from `cmach_lua_setup` in extended state
    S->execlimit_error = lua_touserdata(L, -1);
    // pop return values from `cmach_lua_setup`
    lua_pop(L, 1);
    // return created (extended) state
    return S;
}

/// Helper function deleting a global variable
static void cmach_lua_unsetglobal(lua_State *L, char *name) {
    lua_pushnil(L);
    lua_setglobal(L, name);
}

/// Remove certain functions from base library
static void cmach_lua_seal_base_mayfail(lua_State *L) {
    cmach_lua_unsetglobal(L, "dofile");
    cmach_lua_unsetglobal(L, "load");
    cmach_lua_unsetglobal(L, "loadfile");
    cmach_lua_unsetglobal(L, "pcall");
    cmach_lua_unsetglobal(L, "print");
    cmach_lua_unsetglobal(L, "require");
    cmach_lua_unsetglobal(L, "xpcall");
}

/// Helper function
static int cmach_lua_openlibs_sealed_mayfail(lua_State *L) {
    // load base library (certain globals will be deleted later)
    luaL_requiref(L, "_G", luaopen_base, 1);
    // load certain other libraries
    luaL_requiref(L, "table", luaopen_table, 1);
    luaL_requiref(L, "string", luaopen_string, 1);
    luaL_requiref(L, "math", luaopen_math, 1);
    luaL_requiref(L, "utf8", luaopen_utf8, 1);
    // remove certain functions from base library
    cmach_lua_seal_base_mayfail(L);
    // lua_pop not necessary due to return
    return 0;
}

/// Load only those standard library parts needed for sandbox
/// (pushes error on stack if result is not LUA_OK)
int cmach_lua_openlibs_sealed(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_openlibs_sealed_mayfail);
    result = lua_pcall(L, 0, 0, -2);
    if (result == LUA_OK) {
        lua_pop(L, 1);
    } else {
        lua_remove(L, -2);
    }
    return result;
}

/// Helper function
static int cmach_lua_openlibs_mayfail(lua_State *L) {
    luaL_openlibs(L);
    return 0;
}

/// Load complete standard library
/// (pushes error on stack if result is not LUA_OK)
int cmach_lua_openlibs(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_openlibs_mayfail);
    result = lua_pcall(L, 0, 0, -2);
    if (result == LUA_OK) {
        lua_pop(L, 1);
    } else {
        lua_remove(L, -2);
    }
    return result;
}

/// Helper function
static int cmach_lua_seal_mayfail(lua_State *L) {
    cmach_lua_unsetglobal(L, "coroutine");
    cmach_lua_unsetglobal(L, "debug");
    cmach_lua_unsetglobal(L, "io");
    cmach_lua_unsetglobal(L, "os");
    cmach_lua_unsetglobal(L, "package");
    cmach_lua_seal_base_mayfail(L);
    return 0;
}

/// Remove certain functions from standard library
int cmach_lua_seal(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_seal_mayfail);
    result = lua_pcall(L, 0, 0, -2);
    if (result == LUA_OK) {
        lua_pop(L, 1);
    } else {
        lua_remove(L, -2);
    }
    return result;
}

/// Helper function
static int cmach_lua_touserstr_mayfail(lua_State *L) {
    const char *str;
    size_t len;
    char *buf;
    str = luaL_tolstring(L, 1, &len);
#if LUA_VERSION_NUM < 504
    buf = lua_newuserdata(L, len);
#else
    buf = lua_newuserdatauv(L, len, 0);
#endif
    memcpy(buf, str, len);
    return 1;
}

/// Converts value on top of stack to a string and stores userdata with string
/// on top of stack (pops value and pushes result or error on stack)
int cmach_lua_touserstr(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_touserstr_mayfail);
    lua_pushvalue(L, -3);
    result = lua_pcall(L, 1, 1, -3);
    lua_remove(L, -2); // remove error message handler
    lua_remove(L, -2); // remove original value
    return result;
}

/// Helper function
static int cmach_lua_usertostr_mayfail(lua_State *L) {
    lua_pushlstring(L, lua_touserdata(L, 1), lua_tointeger(L, 2));
    return 1;
}

/// Pushes string onto stack (leaves string or error on stack)
int cmach_lua_pushlstring(lua_State *L, const char *str, lua_Integer len) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_usertostr_mayfail);
    lua_pushlightuserdata(L, (void *)str);
    lua_pushinteger(L, len);
    result = lua_pcall(L, 2, 1, -4);
    lua_remove(L, -2); // remove error message handler
    return result;
}

static int cmach_lua_ref_mayfail(lua_State *L) {
    lua_pushinteger(L, luaL_ref(L, LUA_REGISTRYINDEX));
    return 1;
}

/// Converts value on top of stack to reference
/// (pops value and pushes integer reference or error on stack)
int cmach_lua_ref(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_ref_mayfail);
    lua_pushvalue(L, -3);
    result = lua_pcall(L, 1, 1, -3);
    lua_remove(L, -2); // remove error message handler
    lua_remove(L, -2); // remove original value
    return result;
}

/// Checks if value on top of stack is execution limit error
/// (requires pointer to cmach_lua_t instead of lua_State)
int cmach_lua_is_execlimit(cmach_lua_t *S) {
    if (lua_topointer(S->L, -1) == S->execlimit_error) {
        return 1;
    } else {
        return 0;
    }
}

/// Function pointer for closure
/// (function gets and returns number of arguments on Lua stack,
/// or returns -1 for error)
typedef int (*cmach_callback_fptr)(void *context, int nargs);

/// Function pointer for release of closure
typedef void (*cmach_callback_release_fptr)(void *context);

/// Helper function to be converted into Lua closure
int cmach_call_callback(lua_State *L) {
    cmach_callback_fptr callback = lua_touserdata(L, lua_upvalueindex(2));
    int result = callback(lua_touserdata(L, lua_upvalueindex(1)), lua_gettop(L));
    if (result < 0) lua_error(L);
    return result;
}

/// Lua __gc metamethod to release closure
static int cmach_lua_closure_gc(lua_State *L) {
    void *context;
    cmach_callback_release_fptr release;
    lua_rawgeti(L, 1, 1);
    context = lua_touserdata(L, -1);
    lua_rawgeti(L, 1, 2);
    release = lua_touserdata(L, -1);
    release(context);
    return 0;
}

/// Helper function (expects three userdata as arguments)
static int cmach_lua_pushclosure_mayfail(lua_State *L) {
    // create table that will invoke release function on garbage collection
    lua_createtable(L, 2, 0);
    // store context in that table
    lua_pushvalue(L, 1);
    lua_rawseti(L, -2, 1);
    // store function pointer for release in that table
    lua_pushvalue(L, 3);
    lua_rawseti(L, -2, 2);
    // remove function pointer from stack, leaving following on stack:
    // * context
    // * callback function pointer
    // * table that will invoke release function on GC
    lua_remove(L, -2);
    // set metatable for table which will invoke release function
    lua_createtable(L, 0, 1);
    lua_pushcfunction(L, cmach_lua_closure_gc);
    lua_setfield(L, -2, "__gc");
    lua_setmetatable(L, -2);
    // create and return Lua closure
    lua_pushcclosure(L, cmach_call_callback, 3);
    return 1;
}

/// Pushes callback onto stack (leaves closure or error on stack)
int cmach_lua_pushclosure(
    lua_State *L,
    void *context,
    cmach_callback_fptr callback,
    cmach_callback_release_fptr release
) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_pushclosure_mayfail);
    lua_pushlightuserdata(L, context);
    lua_pushlightuserdata(L, callback);
    lua_pushlightuserdata(L, release);
    result = lua_pcall(L, 3, 1, -5);
    lua_remove(L, -2); // remove error message handler
    return result;
}

/// Helper function
static int cmach_lua_newtable_mayfail(lua_State *L) {
    lua_newtable(L);
    return 1;
}

/// Create new table (pushes table or error on stack)
int cmach_lua_newtable(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_newtable_mayfail);
    result = lua_pcall(L, 0, 1, -2);
    lua_remove(L, -2); // remove error message handler
    return result;
}

/// Helper function
static int cmach_lua_len_mayfail(lua_State *L) {
    lua_pushinteger(L, luaL_len(L, 1));
    return 1;
}

/// Determine length of value
int cmach_lua_len(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_len_mayfail);
    lua_pushvalue(L, -3);
    result = lua_pcall(L, 1, 1, -3);
    lua_remove(L, -2); // remove error message handler
    lua_remove(L, -2); // remove original value
    return result;
}

/// Helper function
static int cmach_lua_gettable_mayfail(lua_State *L) {
    lua_gettable(L, 1);
    return 1;
}

/// Get table entry
int cmach_lua_gettable(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_gettable_mayfail);
    lua_pushvalue(L, -4);
    lua_pushvalue(L, -4);
    result = lua_pcall(L, 2, 1, -4);
    lua_remove(L, -2); // remove error message handler
    lua_remove(L, -2); // remove original value #2
    lua_remove(L, -2); // remove original value #1
    return result;
}

/// Helper function
static int cmach_lua_settable_mayfail(lua_State *L) {
    lua_settable(L, 1);
    return 1;
}

/// Set table entry
int cmach_lua_settable(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_settable_mayfail);
    lua_pushvalue(L, -5);
    lua_pushvalue(L, -5);
    lua_pushvalue(L, -5);
    result = lua_pcall(L, 3, 0, -5);
    lua_pop(L, 4); // remove error message handler plus 3 original values
    return result;
}

/// Helper function
static int cmach_lua_setglobal_mayfail(lua_State *L) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
    lua_insert(L, 1);
    lua_rawset(L, 1);
    return 0;
}

/// Set key in global table
int cmach_lua_setglobal(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_setglobal_mayfail);
    lua_pushvalue(L, -4);
    lua_pushvalue(L, -4);
    result = lua_pcall(L, 2, 0, -4);
    lua_pop(L, 3); // remove error message handler plus 2 original values
    return result;
}

/// Helper function
static int cmach_lua_getglobal_mayfail(lua_State *L) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
    lua_insert(L, 1);
    lua_rawget(L, 1);
    return 1;
}

/// Get value from global table
int cmach_lua_getglobal(lua_State *L) {
    int result;
    lua_pushcfunction(L, cmach_lua_errmsgh);
    lua_pushcfunction(L, cmach_lua_getglobal_mayfail);
    lua_pushvalue(L, -3);
    result = lua_pcall(L, 1, 1, -3);
    lua_replace(L, -3); // replace original value with result
    lua_pop(L, 1); // remove error message handler
    return result;
}