Update redbean to show Lua stack traces (#237)

- Update redbean to include stack trace in Lua errors
- Extend Lua in redbean to include stack trace in all logged errors
- Update default error page in redbean with error details (when allowed)
- Prepend `@` to Lua paths in redbean to recognize them as paths in error messages
- Replace GetClientAddr with GetRemoteAddr to avoid backtrace leak in proxy scenarios
- Fix typo in GetRemoteAddr documentation
This commit is contained in:
Paul Kulchenko 2021-08-11 23:27:39 -07:00 committed by GitHub
parent 9454788223
commit a2e443edd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 34 deletions

View file

@ -584,7 +584,7 @@ FUNCTIONS
Returns client ip4 address and port, e.g. 0x01020304,31337 would Returns client ip4 address and port, e.g. 0x01020304,31337 would
represent 1.2.3.4:31337. This is the same as GetClientAddr except represent 1.2.3.4:31337. This is the same as GetClientAddr except
it will use the ip:port from the X-Forwarded-For header, only if it will use the ip:port from the X-Forwarded-For header, only if
it IsPrivateIp or IsPrivateIp. IsPrivateIp or IsLoopbackIp return true.
GetClientAddr() → ip:uint32,port:uint16 GetClientAddr() → ip:uint32,port:uint16
Returns client socket ip4 address and port, e.g. 0x01020304,31337 Returns client socket ip4 address and port, e.g. 0x01020304,31337

View file

@ -1062,6 +1062,35 @@ static void Daemonize(void) {
ChangeUser(); ChangeUser();
} }
static int LuaCallWithTrace(lua_State *L, int nargs, int nres) {
int nresults, status;
// create a coroutine to retrieve traceback on failure
lua_State *co = lua_newthread(L);
// pop the coroutine, so that the function is at the top
lua_pop(L, 1);
// move the function (and arguments) to the top of the coro stack
lua_xmove(L, co, nargs+1);
// resume the coroutine thus executing the function
status = lua_resume(co, L, nargs, &nresults);
if (status != LUA_OK && status != LUA_YIELD) {
// move the error message
lua_xmove(co, L, 1);
// replace the error with the traceback on failure
luaL_traceback(L, co, lua_tostring(L, -1), 0);
lua_remove(L, -2); // remove the error message
} else {
// move results to the main stack
lua_xmove(co, L, nresults);
// grow the stack in case returned fewer results
// than the caller expects, as lua_resume
// doesn't adjust the stack for needed results
for (; nresults < nres; nresults++)
lua_pushnil(L);
status = LUA_OK; // treat LUA_YIELD the same as LUA_OK
}
return status;
}
static bool LuaOnClientConnection(void) { static bool LuaOnClientConnection(void) {
bool dropit; bool dropit;
uint32_t ip, serverip; uint32_t ip, serverip;
@ -1073,7 +1102,7 @@ static bool LuaOnClientConnection(void) {
lua_pushnumber(L, port); lua_pushnumber(L, port);
lua_pushnumber(L, serverip); lua_pushnumber(L, serverip);
lua_pushnumber(L, serverport); lua_pushnumber(L, serverport);
if (lua_pcall(L, 4, 1, 0) == LUA_OK) { if (LuaCallWithTrace(L, 4, 1) == LUA_OK) {
dropit = lua_toboolean(L, -1); dropit = lua_toboolean(L, -1);
} else { } else {
WARNF("%s: %s", "OnClientConnection", lua_tostring(L, -1)); WARNF("%s: %s", "OnClientConnection", lua_tostring(L, -1));
@ -1094,7 +1123,7 @@ static void LuaOnProcessCreate(int pid) {
lua_pushnumber(L, port); lua_pushnumber(L, port);
lua_pushnumber(L, serverip); lua_pushnumber(L, serverip);
lua_pushnumber(L, serverport); lua_pushnumber(L, serverport);
if (lua_pcall(L, 5, 0, 0) != LUA_OK) { if (LuaCallWithTrace(L, 5, 0) != LUA_OK) {
WARNF("%s: %s", "OnProcessCreate", lua_tostring(L, -1)); WARNF("%s: %s", "OnProcessCreate", lua_tostring(L, -1));
lua_pop(L, 1); lua_pop(L, 1);
} }
@ -1103,7 +1132,7 @@ static void LuaOnProcessCreate(int pid) {
static void LuaOnProcessDestroy(int pid) { static void LuaOnProcessDestroy(int pid) {
lua_getglobal(L, "OnProcessDestroy"); lua_getglobal(L, "OnProcessDestroy");
lua_pushnumber(L, pid); lua_pushnumber(L, pid);
if (lua_pcall(L, 1, 0, 0) != LUA_OK) { if (LuaCallWithTrace(L, 1, 0) != LUA_OK) {
WARNF("%s: %s", "OnProcessDestroy", lua_tostring(L, -1)); WARNF("%s: %s", "OnProcessDestroy", lua_tostring(L, -1));
lua_pop(L, 1); lua_pop(L, 1);
} }
@ -1121,7 +1150,7 @@ static inline bool IsHookDefined(const char *s) {
static void CallSimpleHook(const char *s) { static void CallSimpleHook(const char *s) {
lua_getglobal(L, s); lua_getglobal(L, s);
if (lua_pcall(L, 0, 0, 0) != LUA_OK) { if (LuaCallWithTrace(L, 0, 0) != LUA_OK) {
WARNF("%s: %s", s, lua_tostring(L, -1)); WARNF("%s: %s", s, lua_tostring(L, -1));
lua_pop(L, 1); lua_pop(L, 1);
} }
@ -2504,7 +2533,7 @@ static char *CommitOutput(char *p) {
return p; return p;
} }
static char *ServeDefaultErrorPage(char *p, unsigned code, const char *reason) { static char *ServeDefaultErrorPage(char *p, unsigned code, const char *reason, const char *details) {
p = AppendContentType(p, "text/html; charset=ISO-8859-1"); p = AppendContentType(p, "text/html; charset=ISO-8859-1");
reason = FreeLater(EscapeHtml(reason, -1, 0)); reason = FreeLater(EscapeHtml(reason, -1, 0));
appends(&outbuf, "\ appends(&outbuf, "\
@ -2521,11 +2550,14 @@ img { vertical-align: middle; }\r\n\
AppendLogo(); AppendLogo();
appendf(&outbuf, "%d %s\r\n", code, reason); appendf(&outbuf, "%d %s\r\n", code, reason);
appends(&outbuf, "</h1>\r\n"); appends(&outbuf, "</h1>\r\n");
if (details) {
appendf(&outbuf, "<pre>%s</pre>\r\n", FreeLater(EscapeHtml(details, -1, 0)));
}
UseOutput(); UseOutput();
return p; return p;
} }
static char *ServeErrorImpl(unsigned code, const char *reason) { static char *ServeErrorImpl(unsigned code, const char *reason, const char *details) {
size_t n; size_t n;
char *p, *s; char *p, *s;
struct Asset *a; struct Asset *a;
@ -2536,7 +2568,7 @@ static char *ServeErrorImpl(unsigned code, const char *reason) {
a = GetAsset(s, strlen(s)); a = GetAsset(s, strlen(s));
free(s); free(s);
if (!a) { if (!a) {
return ServeDefaultErrorPage(p, code, reason); return ServeDefaultErrorPage(p, code, reason, details);
} else if (a->file) { } else if (a->file) {
LockInc(&shared->c.slurps); LockInc(&shared->c.slurps);
content = FreeLater(xslurp(a->file->path.s, &contentlength)); content = FreeLater(xslurp(a->file->path.s, &contentlength));
@ -2550,20 +2582,24 @@ static char *ServeErrorImpl(unsigned code, const char *reason) {
content = s; content = s;
contentlength = n; contentlength = n;
} else { } else {
return ServeDefaultErrorPage(p, code, reason); return ServeDefaultErrorPage(p, code, reason, details);
} }
} }
if (Verify(content, contentlength, ZIP_LFILE_CRC32(zbase + a->lf))) { if (Verify(content, contentlength, ZIP_LFILE_CRC32(zbase + a->lf))) {
return AppendContentType(p, "text/html; charset=utf-8"); return AppendContentType(p, "text/html; charset=utf-8");
} else { } else {
return ServeDefaultErrorPage(p, code, reason); return ServeDefaultErrorPage(p, code, reason, details);
} }
} }
} }
static char *ServeError(unsigned code, const char *reason) { static char *ServeErrorWithDetail(unsigned code, const char *reason, const char *details) {
LOGF("ERROR %d %s", code, reason); LOGF("ERROR %d %s", code, reason);
return ServeErrorImpl(code, reason); return ServeErrorImpl(code, reason, details);
}
static char *ServeError(unsigned code, const char *reason) {
return ServeErrorWithDetail(code, reason, NULL);
} }
static char *ServeFailure(unsigned code, const char *reason) { static char *ServeFailure(unsigned code, const char *reason) {
@ -2573,7 +2609,7 @@ static char *ServeFailure(unsigned code, const char *reason) {
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, HeaderLength(kHttpReferer), msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, HeaderLength(kHttpReferer),
HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent), HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent),
HeaderData(kHttpUserAgent)); HeaderData(kHttpUserAgent));
return ServeErrorImpl(code, reason); return ServeErrorImpl(code, reason, NULL);
} }
static ssize_t DeflateGenerator(struct iovec v[3]) { static ssize_t DeflateGenerator(struct iovec v[3]) {
@ -3134,16 +3170,26 @@ static char *GetLuaResponse(void) {
return p; return p;
} }
static bool IsLoopbackClient() {
uint32_t ip;
uint16_t port;
GetRemoteAddr(&ip, &port);
return IsLoopbackIp(ip);
}
static char *LuaOnHttpRequest(void) { static char *LuaOnHttpRequest(void) {
effectivepath.p = url.path.p; effectivepath.p = url.path.p;
effectivepath.n = url.path.n; effectivepath.n = url.path.n;
lua_getglobal(L, "OnHttpRequest"); lua_getglobal(L, "OnHttpRequest");
if (lua_pcall(L, 0, 0, 0) == LUA_OK) { if (LuaCallWithTrace(L, 0, 0) == LUA_OK) {
return CommitOutput(GetLuaResponse()); return CommitOutput(GetLuaResponse());
} else { } else {
char *error;
WARNF("%s", lua_tostring(L, -1)); WARNF("%s", lua_tostring(L, -1));
error = ServeErrorWithDetail(500, "Internal Server Error",
IsLoopbackClient() ? lua_tostring(L, -1) : NULL);
lua_pop(L, 1); lua_pop(L, 1);
return ServeError(500, "Internal Server Error"); return error;
} }
} }
@ -3154,16 +3200,17 @@ static char *ServeLua(struct Asset *a, const char *s, size_t n) {
effectivepath.p = s; effectivepath.p = s;
effectivepath.n = n; effectivepath.n = n;
if ((code = FreeLater(LoadAsset(a, &codelen)))) { if ((code = FreeLater(LoadAsset(a, &codelen)))) {
if (!luaL_loadbufferx(L, code, codelen, FreeLater(strndup(s, n)), 0) && int status = luaL_loadbuffer(L, code, codelen,
!lua_pcall(L, 0, LUA_MULTRET, 0)) { FreeLater(xasprintf("@%s", FreeLater(strndup(s, n)))));
if (status == LUA_OK && LuaCallWithTrace(L, 0, 0) == LUA_OK) {
return CommitOutput(GetLuaResponse()); return CommitOutput(GetLuaResponse());
} else { } else {
WARNF("failed to run lua code %s", lua_tostring(L, -1)); char *error;
/* WARNF("failed to run lua code: %s", lua_tostring(L, -1));
* TODO: Print backtrace, and then serve Django-like error page if error = ServeErrorWithDetail(500, "Internal Server Error",
* and only if IsLoopbackIp(GetRemoteAddr()) IsLoopbackClient() ? lua_tostring(L, -1) : NULL);
*/
lua_pop(L, 1); lua_pop(L, 1);
return error;
} }
} }
return ServeError(500, "Internal Server Error"); return ServeError(500, "Internal Server Error");
@ -5400,25 +5447,20 @@ static bool LuaRun(const char *path, bool mandatory) {
struct Asset *a; struct Asset *a;
const char *code; const char *code;
size_t pathlen, codelen; size_t pathlen, codelen;
int status;
pathlen = strlen(path); pathlen = strlen(path);
if ((a = GetAsset(path, pathlen))) { if ((a = GetAsset(path, pathlen))) {
if ((code = LoadAsset(a, &codelen))) { if ((code = FreeLater(LoadAsset(a, &codelen)))) {
effectivepath.p = path; effectivepath.p = path;
effectivepath.n = pathlen; effectivepath.n = pathlen;
DEBUGF("LuaRun(%`'s)", path); DEBUGF("LuaRun(%`'s)", path);
if (luaL_loadbufferx(L, code, codelen, path, 0) || status = luaL_loadbuffer(L, code, codelen,
lua_pcall(L, 0, LUA_MULTRET, 0)) { FreeLater(xasprintf("@%s", path)));
/* if (status != LUA_OK || LuaCallWithTrace(L, 0, 0) != LUA_OK) {
* TODO: There needs to be some reasonable way to get a WARNF("script failed to run: %s", lua_tostring(L, -1));
* backtrace. The best thing about Django was the
* fabulous backtrace page (and the admin panel).
*/
/* luaL_traceback(L, L, lua_tostring(L, -1), 0); */
WARNF("script failed to run %s", lua_tostring(L, -1));
if (mandatory) exit(1);
lua_pop(L, 1); lua_pop(L, 1);
if (mandatory) exit(1);
} }
free(code);
} }
} }
return !!a; return !!a;