/* 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 <dsilvers@digital-scurf.org>
 *
 * For licence terms, see COPYING
 *
 */

#include <lua.h>
#include <lauxlib.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#ifdef linux
#include <sys/prctl.h>
#include <malloc.h>
#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_fake_gc(lua_State *L)
{
	(void)L;
	return 0;
}

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");
	/* put a dummy __gc so that the fasttm stuff doesn't bite us later */
	lua_pushcclosure(L, supple_capi_fake_gc, 0);
	lua_setfield(L, -2, "__gc");
	/* 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)
{
#if (LUA_VERSION_NUM > 501)
	luaL_newlib(L, supple_capi_functions);
#else
	luaL_register(L, "supple.capi", supple_capi_functions);
#endif
	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");
	lua_pushliteral(L, WRAPPER_PATH);
	lua_setfield(L, -2, "wrapper_path");
	return 1;
}
