/* supple/lib/supple/capi.c * * Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine - Supple * * Useful C methods for Supple including the userdata partitioning. * * Copyright 2012 Daniel Silverstone * * For licence terms, see COPYING * */ #include #include #include #include #include #include #include #include #include #include #ifdef linux #include #include #endif /* The API exposed, increment on backward-compatible changes */ #define CAPI_API 1 /* The ABI exposed, increment on backward-incompatible changes */ #define CAPI_ABI 1 static int supple_capi_explain(lua_State *L) { int one_type; if (lua_gettop(L) != 2) { return luaL_error(L, "Expected 2 args got %d", lua_gettop(L)); } one_type = lua_type(L, 1); if ((one_type != LUA_TTABLE) && (one_type != LUA_TFUNCTION) && (one_type != LUA_TUSERDATA)) { return luaL_error(L, "Expected one of table, function or userdata. " \ "Got %s instead", lua_typename(L, one_type)); } if (lua_type(L, 2) != LUA_TSTRING) { return luaL_error(L, "Expected string, got %s instead", lua_typename(L, lua_type(L, 2))); } lua_newtable(L); lua_pushvalue(L, 2); lua_setfield(L, -2, "tag"); lua_pushstring(L, lua_typename(L, one_type)); lua_setfield(L, -2, "type"); if (one_type != LUA_TFUNCTION) { /* Get the metatable */ if (lua_getmetatable(L, 1) != 0) { int idx = 1; /* Prepare a methods table */ lua_newtable(L); lua_pushnil(L); /* For iterating */ while (lua_next(L, -3) != 0) { /* Iterate the metatable */ lua_pop(L, 1); /* Don't need the value */ if (lua_type(L, -1) != LUA_TSTRING) { /* Not a string key */ } else if (strcmp(lua_tostring(L, -1), "__mode") == 0) { /* mode key is ignored */ } else { /* dup */ lua_pushvalue(L, -1); /* And copy into methods */ lua_rawseti(L, -3, idx++); } /* Key is left ready for lua_next() */ } /* Stuff the methods table into the return */ lua_setfield(L, -3, "methods"); /* And pop the metatable */ lua_pop(L, 1); } } /* And return the new table */ return 1; } static int supple_capi_proxy_tostring(lua_State *L) { /* arg 1 is the object, upvalue 1 is the original type */ const char *orig_type = lua_tostring(L, lua_upvalueindex(1)); lua_pushfstring(L, "%s: %p", orig_type, lua_touserdata(L, 1)); return 1; } static int supple_capi_proxy_type(lua_State *L) { /* arg 1 is the object, upvalue 1 is the original type */ lua_pushvalue(L, lua_upvalueindex(1)); return 1; } static int supple_capi_new_proxy(lua_State *L) { void *proxy_ptr; /* Input is a type string */ if (lua_type(L, 1) != LUA_TSTRING) { return luaL_error(L, "Expected type string, got %s", lua_typename(L, lua_type(L, 1))); } proxy_ptr = lua_newuserdata(L, 1); if (proxy_ptr == NULL) { return 0; } lua_newtable(L); /* typestr, proxyobj, metatable */ /* First job is the tostring metamethod, we always add that */ lua_pushvalue(L, 1); lua_pushcclosure(L, supple_capi_proxy_tostring, 1); lua_setfield(L, -2, "__tostring"); /* next the __type metamethod for the lua level type call */ lua_pushvalue(L, 1); lua_pushcclosure(L, supple_capi_proxy_type, 1); lua_setfield(L, -2, "__type"); /* And now prevent anything poking in us later */ lua_pushboolean(L, 1); lua_setfield(L, -2, "__metatable"); /* Finally set the metatable and return proxy, metatable */ lua_pushvalue(L, -1); lua_setmetatable(L, -3); return 2; } /* Replacement for lbaselib.c's type method, honouring __type */ static int supple_capi_type(lua_State *L) { lua_settop(L, 1); if (lua_getmetatable(L, 1) != 0) { /* There is a metatable, try and find __type */ lua_getfield(L, 2, "__type"); if (lua_isfunction(L, 3)) { lua_pushvalue(L, 1); lua_call(L, 1, 1); return 1; } } lua_pushstring(L, lua_typename(L, lua_type(L, 1))); return 1; } /* Reimplementation of base type in case it's not available */ static int supple_capi_rawtype(lua_State *L) { lua_settop(L, 1); lua_pushstring(L, lua_typename(L, lua_type(L, 1))); return 1; } /* Lockdown function, takes nothing, returns a string token and errno which * explains the result. We're using SUPPLE_MKDTEMP as a template for * mkdtemp(). The wrapper ensures it is unset unless testing. */ #define TEMPDIR_NAME ((getenv("SUPPLE_MKDTEMP") != NULL) ? \ (getenv("SUPPLE_MKDTEMP")) : ("/var/tmp/suppleXXXXXX")) static int supple_capi_lockdown(lua_State *L) { bool rootly = (geteuid() == 0); char *tempdir; struct rlimit lim; lua_settop(L, 0); /* Discard any arguments */ /* Prepare a copy of the tempdir template ready */ tempdir = lua_newuserdata(L, strlen(TEMPDIR_NAME)); strcpy(tempdir, TEMPDIR_NAME); /* And make a temp directory to use */ if (mkdtemp(tempdir) != tempdir) { /* Failed to make the temp dir, give up */ int saved_errno = errno; lua_pushliteral(L, "mkdir"); lua_pushnumber(L, saved_errno); return 2; } /* Temporary directory made, if we're rootly, chown it */ if (rootly) { if (chown(tempdir, 0, 0) != 0) { int saved_errno = errno; rmdir(tempdir); lua_pushliteral(L, "chown"); lua_pushnumber(L, saved_errno); return 2; } } /* Change into that directory */ if (chdir(tempdir) != 0) { int saved_errno = errno; rmdir(tempdir); lua_pushliteral(L, "chdir"); lua_pushnumber(L, saved_errno); return 2; } /* Remove the directory */ if (rmdir(tempdir) != 0) { int saved_errno = errno; lua_pushliteral(L, "rmdir"); lua_pushnumber(L, saved_errno); return 2; } /* chroot() to the ephemeral dir */ if (rootly && (chroot(".") != 0)) { int saved_errno = errno; lua_pushliteral(L, "chroot"); lua_pushnumber(L, saved_errno); return 2; } /* Ensure our PWD is "/" */ if (rootly && chdir("/") != 0) { int saved_errno = errno; lua_pushliteral(L, "chdir"); lua_pushnumber(L, saved_errno); return 2; } /* Cap our limits down as far as we can */ #define CAP_AT(lval,cap) lval = (lval < cap) ? lval : cap #define CAP_LIMIT(resource, soft, hard) \ do { \ if (rootly && getrlimit(resource, &lim) != 0) { \ int saved_errno = errno; \ lua_pushliteral(L, "getrlimit(" #resource ")"); \ lua_pushnumber(L, saved_errno); \ return 2; \ } \ CAP_AT(lim.rlim_cur,soft); \ CAP_AT(lim.rlim_max,hard); \ if (rootly && setrlimit(resource, &lim) != 0) { \ int saved_errno = errno; \ lua_pushliteral(L, "setrlimit(" #resource ")"); \ lua_pushnumber(L, saved_errno); \ return 2; \ } \ } while (0) #ifdef linux /* Have a go at ensuring we won't call brk() ready for seccomp() */ if (rootly && prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) { /* We might be able to enter seccomp mode 1, so carry on */ /* Disable returning memory to the OS */ if (mallopt(M_TRIM_THRESHOLD, -1) == 0) { int saved_errno = errno; lua_pushliteral(L, "mallopt(M_TRIM_THRESHOLD)"); lua_pushnumber(L, saved_errno); return 2; } /* Disable allocating memory by mmap() */ if (mallopt(M_MMAP_MAX, 0) == 0) { int saved_errno = errno; lua_pushliteral(L, "mallopt(M_MMAP_MAX)"); lua_pushnumber(L, saved_errno); return 2; } /* Now allocate/free a big chunk of RAM */ free(malloc(20 * 1024 * 1024)); } #endif /* Regardless of OS, we'll now try and set off some limits */ /* Limit the virtual memory arena to 50 megs. * While this might seem a lot, a bare lua5.1 interpreter plus Supple * and deps seems to come to around 28MB VMSZ (although only 2MB res) * because of shared libraries and the ilk. 50MB allows for up to * 22MB of allocations beyond that, which seems fair. */ CAP_LIMIT(RLIMIT_AS, (50 * 1024 * 1024), (50 * 1024 * 1024)); /* And limit CPU to 15 seconds, 30 if not otherwise handled */ CAP_LIMIT(RLIMIT_CPU, 15, 30); /* Cap output file size at 0 bytes, so even if we managed to open * a file, writes would fail. */ CAP_LIMIT(RLIMIT_FSIZE, 0, 0); /* Do not allow core files to be created */ CAP_LIMIT(RLIMIT_CORE, 0, 0); /* And the attackers would have to close their only FD to the outside * world in order to open anything anyway. */ CAP_LIMIT(RLIMIT_NOFILE, 1, 1); /* Drop privs */ if (rootly && setuid(getuid()) != 0) { int saved_errno = errno; lua_pushliteral(L, "setuid"); lua_pushnumber(L, saved_errno); return 2; } #ifdef linux /* Next, we're going to attempt a seccomp setup */ if (rootly) { /* We're fairly well locked down, however with Linux we * can go one step further and enter 'seccomp mode 1' which * limits our syscalls to read/write/_exit/sigreturn. * * This means that even if the attacker breaks out harder * than we can imagine, their *ONLY* avenue of attack is * back along the already open FD to the host app. */ if (prctl(PR_SET_SECCOMP, 1, 0, 0, 0) != 0) { int saved_errno = errno; lua_pushliteral(L, "prctl(PR_SET_SECCOMP, 1)"); lua_pushnumber(L, saved_errno); return 2; } } /* If we reach here and we're rootly, we need to inform the lib * Not to do the open() test or we get killed */ lua_pushstring(L, (rootly ? "OK" : "oknonroot")); #else lua_pushstring(L, (rootly ? "ok" : "oknonroot")); #endif lua_pushnumber(L, 0); return 2; } static int supple_capi_raw_getmm(lua_State *L) { lua_getmetatable(L, 1); lua_pushvalue(L, 2); lua_gettable(L, -2); return 1; } static int supple_capi_next(lua_State *L) { int type; lua_settop(L, 2); type = lua_type(L, 1); if (type == LUA_TTABLE) { /* It is a table, so do the equiv of luaB_next */ if (lua_next(L, 1)) /* return the k/v pair we got */ return 2; /* didn't get a k/v pair, so push a nil key and return */ lua_pushnil(L); return 1; } else { /* Do we have a metatable? */ if (lua_getmetatable(L, 1)) { /* We do, do we have a __next entry? */ lua_getfield(L, -1, "__next"); if (!lua_isnil(L, -1)) { /* we do, so let's call that instead */ lua_insert(L, 1); lua_settop(L, 3); lua_call(L, 2, LUA_MULTRET); return lua_gettop(L); } } } /* Fell out of the ladders so return an error */ return luaL_error(L, "Expected table (or iterable) as argument 1"); } static int supple_capi_ipairs_iterator(lua_State *L) { /* Obj Int -> (NewInt Value|nothing) */ int i = luaL_checkint(L, 2) + 1; lua_pushinteger(L, i); if (lua_type(L, 1) == LUA_TTABLE) { lua_rawgeti(L, 1, i); } else { lua_pushinteger(L, i); lua_gettable(L, 1); /* Use this so __index is used */ } return lua_isnil(L, -1) ? 0 : 2; } static int supple_capi_ipairs(lua_State *L) { /* Replacement ipairs which uses our iterator so that fetches * cross the proxy boundary transparently if needed. */ /* We register this with the iterator as the first upvalue */ lua_pushvalue(L, lua_upvalueindex(1)); /* iterator */ lua_pushvalue(L, 1); /* state (table?) */ lua_pushinteger(L, 0); /* initial value */ return 3; } static int supple_capi_pairs(lua_State *L) { /* Replacement pairs which uses our next function. It is * provided as our upvalue */ lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, 1); lua_pushnil(L); return 3; } static const struct luaL_Reg supple_capi_functions[] = { { "explain", supple_capi_explain }, { "new_proxy", supple_capi_new_proxy }, { "type", supple_capi_type }, { "rawtype", supple_capi_rawtype }, { "lockdown", supple_capi_lockdown }, { "raw_getmm", supple_capi_raw_getmm }, { "next", supple_capi_next }, { NULL, NULL } }; int luaopen_supple_capi(lua_State *L) { luaL_register(L, "supple.capi", supple_capi_functions); lua_pushnumber(L, CAPI_API); lua_setfield(L, -2, "_API"); lua_pushnumber(L, CAPI_ABI); lua_setfield(L, -2, "_ABI"); lua_pushcclosure(L, supple_capi_ipairs_iterator, 0); lua_pushcclosure(L, supple_capi_ipairs, 1); lua_setfield(L, -2, "ipairs"); lua_pushcclosure(L, supple_capi_next, 0); lua_pushcclosure(L, supple_capi_pairs, 1); lua_setfield(L, -2, "pairs"); return 1; }