a sandboxed Lua backend for FastCGI
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

430 lines
9.6 KiB

  1. #include <stdlib.h>
  2. #include <inttypes.h>
  3. #include <stdint.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <ctype.h>
  7. #include <fcntl.h>
  8. #include <errno.h>
  9. #include <sys/stat.h>
  10. #include <sys/mman.h>
  11. #include <sys/time.h>
  12. #include <sys/resource.h>
  13. #include <fcgiapp.h>
  14. #include <lua5.1/lua.h>
  15. #include <lua5.1/lauxlib.h>
  16. #include <lua5.1/lualib.h>
  17. #include "lua.h"
  18. #include "lfuncs.h"
  19. #ifdef DEBUG
  20. void LF_printstack(lua_State *l)
  21. {
  22. int ss = lua_gettop(l);
  23. printf("Lua Stack:\n");
  24. for(int i=1; i <= ss; i++){
  25. printf("\t%d) %s\n", i, lua_typename(l, lua_type(l, i)));
  26. }
  27. }
  28. #endif
  29. // Gets current thread usage
  30. static int LF_threadusage(struct timeval *tv)
  31. {
  32. struct rusage usage;
  33. if(getrusage(RUSAGE_SELF, &usage) == -1){
  34. return 1;
  35. }
  36. // Add user to sys to get actual usage
  37. tv->tv_usec = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
  38. tv->tv_sec = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec;
  39. if(tv->tv_usec > 1000000){
  40. tv->tv_usec -= 1000000;
  41. tv->tv_sec++;
  42. }
  43. return 0;
  44. }
  45. // limits cpu usage
  46. static void LF_limit_hook(lua_State *l, lua_Debug *d)
  47. {
  48. lua_pushstring(l, "CPU_LIMIT");
  49. lua_rawget(l, LUA_REGISTRYINDEX);
  50. struct timeval *limit = lua_touserdata(l, -1);
  51. lua_pop(l, 1);
  52. struct timeval tv;
  53. if(LF_threadusage(&tv)){ luaL_error(l, "CPU usage sample error"); }
  54. if(timercmp(&tv, limit, >)){ luaL_error(l, "CPU limit exceeded"); }
  55. }
  56. // removes global variables
  57. static void LF_nilglobal(lua_State *l, char *var)
  58. {
  59. lua_pushnil(l);
  60. lua_setglobal(l, var);
  61. }
  62. // Limited memory allocator
  63. static void *LF_limit_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
  64. {
  65. size_t *limit = ud;
  66. *limit += osize;
  67. if(nsize == 0){
  68. free(ptr);
  69. return NULL;
  70. } else {
  71. if(*limit < nsize){ return NULL; }
  72. *limit -= nsize;
  73. return realloc(ptr, nsize);
  74. }
  75. }
  76. // Allocates and initializes a new set of limits for a lua state
  77. LF_limits *LF_newlimits()
  78. {
  79. LF_limits *limits = malloc(sizeof(LF_limits));
  80. if(limits == NULL){ return NULL; }
  81. memset(limits, 0, sizeof(LF_limits));
  82. return limits;
  83. }
  84. void LF_setlimits(LF_limits *limits, size_t memory, size_t output, uint32_t cpu_sec, uint32_t cpu_usec)
  85. {
  86. limits->memory = memory;
  87. limits->output = output;
  88. limits->cpu.tv_usec = cpu_usec;
  89. limits->cpu.tv_sec = cpu_sec;
  90. }
  91. void LF_enablelimits(lua_State *l, LF_limits *limits)
  92. {
  93. if(limits->cpu.tv_usec > 0 || limits->cpu.tv_sec > 0){
  94. struct timeval curr;
  95. if(LF_threadusage(&curr)){
  96. printf("CPU usage sample error\n"); // FIX ME
  97. } else {
  98. timeradd(&limits->cpu, &curr, &limits->cpu);
  99. lua_sethook(l, &LF_limit_hook, LUA_MASKCOUNT, 1000);
  100. }
  101. lua_pushstring(l, "CPU_LIMIT");
  102. lua_pushlightuserdata(l, &limits->cpu);
  103. lua_rawset(l, LUA_REGISTRYINDEX);
  104. }
  105. if(limits->output){
  106. lua_pushstring(l, "RESPONSE_LIMIT");
  107. lua_pushlightuserdata(l, &limits->output);
  108. lua_rawset(l, LUA_REGISTRYINDEX);
  109. }
  110. if(limits->memory){
  111. lua_pushstring(l, "MEMORY_LIMIT");
  112. lua_pushlightuserdata(l, &limits->memory);
  113. lua_rawset(l, LUA_REGISTRYINDEX);
  114. lua_setallocf(l, &LF_limit_alloc, &limits->memory);
  115. }
  116. }
  117. // Initialize a new lua state using specific parameters
  118. lua_State *LF_newstate(int sandbox, char *content_type)
  119. {
  120. lua_State *l = luaL_newstate();
  121. if(l == NULL){ return NULL; }
  122. // Load base
  123. lua_pushcfunction(l, luaopen_base);
  124. lua_pushliteral(l, "");
  125. lua_call(l, 1, 0);
  126. if(sandbox){
  127. // Load table
  128. lua_pushcfunction(l, luaopen_table);
  129. lua_pushliteral(l, LUA_TABLIBNAME);
  130. lua_call(l, 1, 0);
  131. // Load string
  132. lua_pushcfunction(l, luaopen_string);
  133. lua_pushliteral(l, LUA_STRLIBNAME);
  134. lua_call(l, 1, 0);
  135. // Load math
  136. lua_pushcfunction(l, luaopen_math);
  137. lua_pushliteral(l, LUA_MATHLIBNAME);
  138. lua_call(l, 1, 0);
  139. // Nil out unsafe functions/objects
  140. LF_nilglobal(l, "load");
  141. LF_nilglobal(l, "xpcall");
  142. LF_nilglobal(l, "pcall");
  143. LF_nilglobal(l, "module");
  144. LF_nilglobal(l, "require");
  145. // Override unsafe functions
  146. lua_register(l, "loadstring", &LF_loadstring);
  147. lua_register(l, "loadfile", &LF_loadfile);
  148. lua_register(l, "dofile", &LF_dofile);
  149. }
  150. // Register the print function
  151. lua_register(l, "print", &LF_print);
  152. // Register the write function
  153. lua_register(l, "write", &LF_write);
  154. // Setup the "HEADER" value
  155. lua_newtable(l);
  156. lua_pushstring(l, "Content-Type");
  157. lua_pushstring(l, content_type);
  158. lua_rawset(l, 1);
  159. lua_setglobal(l, "HEADER");
  160. return l;
  161. }
  162. // Set GET variables
  163. static void LF_parsequerystring(lua_State *l, char *query_string, char *table)
  164. {
  165. lua_newtable(l);
  166. int stack = lua_gettop(l);
  167. char *sptr, *optr, *nptr;
  168. for(nptr = optr = sptr = &query_string[0]; 1; optr++){
  169. switch(*optr){
  170. case '+':
  171. *nptr++ = ' ';
  172. break;
  173. case '=':
  174. // Push a key, if it's valid and there's not already one
  175. if(lua_gettop(l) == stack){
  176. if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
  177. sptr = nptr;
  178. } else {
  179. *nptr++ = '=';
  180. }
  181. break;
  182. case '&':
  183. // Push key or value if valid
  184. if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
  185. // Push value, if there is already a key
  186. if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); }
  187. // Set key/value if they exist
  188. if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); }
  189. sptr = nptr;
  190. break;
  191. case '%': {
  192. // Decode hex percent encoded sets, if valid
  193. char c1 = *(optr+1), c2 = *(optr+2);
  194. if(isxdigit(c1) && isxdigit(c2)){
  195. char digit = 16 * (c1 >= 'A' ? (c1 & 0xdf) - '7' : (c1 - '0'));
  196. digit += (c2 >= 'A' ? (c2 & 0xdf) - '7' : (c2 - '0'));
  197. *nptr++ = digit;
  198. optr += 2;
  199. } else {
  200. *nptr++ = '%';
  201. }
  202. } break;
  203. case '\0':
  204. // Push key or value if valid
  205. if((nptr-sptr) > 0){ lua_pushlstring(l, sptr, (nptr - sptr)); }
  206. // Push value, if needed
  207. if(lua_gettop(l) == (stack+1)){ lua_pushstring(l, ""); }
  208. // Set key/value if valid
  209. if(lua_gettop(l) == (stack+2)){ lua_rawset(l, stack); }
  210. // Finally, set the table
  211. lua_setglobal(l, table);
  212. return;
  213. break;
  214. default:
  215. *nptr++ = *optr;
  216. break;
  217. }
  218. }
  219. }
  220. // Parses fastcgi request
  221. void LF_parserequest(lua_State *l, FCGX_Request *request, LF_state *state)
  222. {
  223. uintmax_t content_length = 0;
  224. char *content_type = NULL;
  225. state->committed = 0;
  226. state->response = request->out;
  227. lua_pushstring(l, "STATE");
  228. lua_pushlightuserdata(l, state);
  229. lua_rawset(l, LUA_REGISTRYINDEX);
  230. lua_newtable(l);
  231. for(char **p = request->envp; *p; ++p){
  232. char *vptr = strchr(*p, '=');
  233. int keylen = (vptr - *p);
  234. lua_pushlstring(l, *p, keylen); // Push Key
  235. lua_pushstring(l, (vptr+1)); // Push Value
  236. lua_rawset(l, 1); // Set key/value into table
  237. switch(keylen){
  238. case 11:
  239. if(memcmp(*p, "SCRIPT_NAME", 11) == 0){
  240. lua_pushstring(l, "SCRIPT_NAME");
  241. lua_pushlightuserdata(l, (vptr+1));
  242. lua_rawset(l, LUA_REGISTRYINDEX);
  243. }
  244. break;
  245. case 12:
  246. if(memcmp(*p, "QUERY_STRING", 12) == 0){
  247. LF_parsequerystring(l, (vptr+1), "GET");
  248. } if(memcmp(*p, "CONTENT_TYPE", 12) == 0){
  249. content_type = (vptr+1);
  250. }
  251. break;
  252. case 13:
  253. if(memcmp(*p, "DOCUMENT_ROOT", 13) == 0){
  254. lua_pushstring(l, "DOCUMENT_ROOT");
  255. lua_pushlightuserdata(l, (vptr+1));
  256. lua_rawset(l, LUA_REGISTRYINDEX);
  257. }
  258. break;
  259. case 14:
  260. if(memcmp(*p, "CONTENT_LENGTH", 14) == 0){
  261. content_length = strtoumax((vptr+1), NULL, 10);
  262. }
  263. break;
  264. case 15:
  265. if(memcmp(*p, "SCRIPT_FILENAME", 15) == 0){
  266. lua_pushstring(l, "SCRIPT_FILENAME");
  267. lua_pushlightuserdata(l, (vptr+1));
  268. lua_rawset(l, LUA_REGISTRYINDEX);
  269. }
  270. break;
  271. }
  272. }
  273. lua_setglobal(l, "REQUEST");
  274. if(content_length > 0 && content_type != NULL && memcmp(content_type, "application/x-www-form-urlencoded", 33) == 0){
  275. char *content = lua_newuserdata(l, content_length+1);
  276. int r = FCGX_GetStr(
  277. content, (content_length > INT_MAX ? INT_MAX : content_length),
  278. request->in
  279. );
  280. *(content + r) = 0; // Add NUL byte at end for proper string
  281. LF_parsequerystring(l, content, "POST");
  282. lua_pop(l, 1);
  283. }
  284. }
  285. // Load script by name and path
  286. int LF_fileload(lua_State *l, const char *name, char *scriptpath)
  287. {
  288. char *script = NULL;
  289. int fd = -1, r = 0;
  290. struct stat sb;
  291. if(scriptpath == NULL){ return LF_ERRNOPATH; }
  292. if(name == NULL){ return LF_ERRNONAME; }
  293. // Generate a string with an '=' followed by the script name
  294. // this ensures lua will generation a reasonable error
  295. size_t namelen = strlen(name);
  296. char scriptname[namelen+2];
  297. scriptname[0] = '=';
  298. memcpy(&scriptname[1], name, namelen+1);
  299. if((fd = open(scriptpath, O_RDONLY)) == -1){ goto errorL; }
  300. if(fstat(fd, &sb) == -1){ goto errorL; }
  301. if((script = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0)) == NULL){
  302. goto errorL;
  303. }
  304. if(madvise(script, sb.st_size, MADV_SEQUENTIAL) == -1){ goto errorL; }
  305. if(sb.st_size > 3 && memcmp(script, LUA_SIGNATURE, 4) == 0){
  306. r = LF_ERRBYTECODE;
  307. } else {
  308. switch(luaL_loadbuffer(l, script, sb.st_size, scriptname)){
  309. case LUA_ERRSYNTAX: r = LF_ERRSYNTAX; break;
  310. case LUA_ERRMEM: r = LF_ERRMEMORY; break;
  311. }
  312. }
  313. if(script != NULL){ munmap(script, sb.st_size); }
  314. if(fd != -1){ close(fd); }
  315. return r;
  316. errorL:
  317. if(script != NULL){ munmap(script, sb.st_size); }
  318. if(fd != -1){ close(fd); }
  319. switch(errno){
  320. case EACCES: return r = LF_ERRACCESS;
  321. case ENOENT: return r = LF_ERRNOTFOUND;
  322. case ENOMEM: return r = LF_ERRMEMORY;
  323. default: return r = LF_ERRANY;
  324. }
  325. return r;
  326. }
  327. // Loads script specified in registryindex into lua state
  328. int LF_loadscript(lua_State *l)
  329. {
  330. lua_pushstring(l, "SCRIPT_FILENAME");
  331. lua_rawget(l, LUA_REGISTRYINDEX);
  332. char *scriptpath = lua_touserdata(l, 1);
  333. lua_pop(l, 1);
  334. lua_pushstring(l, "SCRIPT_NAME");
  335. lua_rawget(l, LUA_REGISTRYINDEX);
  336. char *name = lua_touserdata(l, 1);
  337. lua_pop(l, 1);
  338. return LF_fileload(l, name, scriptpath);
  339. }
  340. // Closes a state
  341. void LF_closestate(lua_State *l)
  342. {
  343. lua_close(l);
  344. }