Fix important bugs in redbean

This change upgrades to the latest Chromium Zlib, fixes bugs in redbean,
and introduces better support for reverse proxies like Cloudflare. This
change improves the security of redbean and it's recommended that users
upgrade to the release that'll follow. This change also updates the docs
to clarify how to use the security tools redbean provides e.g. pledge(),
unveil(), and the MODE=asan builds which improve memory safety.
This commit is contained in:
Justine Tunney 2022-09-17 01:37:33 -07:00
parent 994e1f4386
commit 775944a2d0
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
42 changed files with 8148 additions and 7298 deletions

View file

@ -255,7 +255,45 @@ SECURITY
-VVV log ssl informational messages too
-VVVV log ssl verbose details too
Redbean supports sandboxing flags on Linux and OpenBSD.
redbean provides hardened ASAN (Address Sanitizer) builds that
proactively guard against any potential memory weaknesses that may be
discovered, such as buffer overruns, use after free, etc. MDOE=asan is
recomended when serving on the public Internet.
redbean also supports robust sandboxing on Linux Kernel 5.13+ and
OpenBSD. The recommended way to harden your redbean is to call the
pledge() and unveil() functions. For example, if you have a SQLite app
then the key to using these features is to connect to the db first:
function OnWorkerStart()
db = sqlite3.open("db.sqlite3")
db:busy_timeout(1000)
db:exec[[PRAGMA journal_mode=WAL]]
db:exec[[PRAGMA synchronous=NORMAL]]
db:exec[[SELECT x FROM warmup WHERE x = 1]]
assert(unix.setrlimit(unix.RLIMIT_RSS, 100 * 1024 * 1024))
assert(unix.setrlimit(unix.RLIMIT_CPU, 4))
assert(unix.unveil("/var/tmp", "rwc"))
assert(unix.unveil("/tmp", "rwc"))
assert(unix.unveil(nil, nil))
assert(unix.pledge("stdio flock rpath wpath cpath", nil,
unix.PLEDGE_PENALTY_RETURN_EPERM))
end
What makes this technique interesting is redbean doesn't have file
system access to the database file, and instead uses an inherited file
descriptor that was opened beforehand. With SQLite the tmp access is
only needed to support things like covering indexes. The -Z flag is
also helpful to see where things go wrong, so you know which promises
are needed to support your use case.
pledge() will work on all Linux kernels since RHEL6 since it uses
SECCOMP BPF filtering. On the other hand, unveil() requires Landlock
LSM which was only introduced in 2021. If you need unveil() then be
sure to test the restrictions work. Most environments don't support
unveil(), so it's designed to be a no-op in unsupported environments.
Alternatively, there's CLI flags which make it simple to get started:
-S (online policy)

View file

@ -206,7 +206,14 @@ static int LuaMaxmindResultGet(lua_State *L) {
for (i = 0; i < n; ++i) path[i] = lua_tostring(L, 2 + i);
err = MMDB_aget_value(&(*ur)->mmlr.entry, &edata, path);
free(path);
if (err) LuaThrowMaxmindIpError(L, "getpath", (*ur)->ip, err);
if (err) {
if (err == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) {
lua_pushnil(L);
return 1;
} else {
LuaThrowMaxmindIpError(L, "getpath", (*ur)->ip, err);
}
}
if (!edata.offset) {
lua_pushnil(L);
return 1;

View file

@ -36,6 +36,7 @@
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsr.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/likely.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/safemacros.internal.h"
@ -332,6 +333,14 @@ static struct Assets {
} * p;
} assets;
static struct ProxyIps {
size_t n;
struct ProxyIp {
uint32_t ip;
uint32_t mask;
} * p;
} proxyips;
static struct Shared {
int workers;
struct timespec nowish;
@ -859,6 +868,28 @@ static void ProgramRedirectArg(int code, const char *s) {
ProgramRedirect(code, s, p - s, p + 1, n - (p - s + 1));
}
static void TrustProxy(uint32_t ip, int cidr) {
uint32_t mask;
mask = 0xffffffffu << (32 - cidr);
proxyips.p = xrealloc(proxyips.p, ++proxyips.n * sizeof(*proxyips.p));
proxyips.p[proxyips.n - 1].ip = ip;
proxyips.p[proxyips.n - 1].mask = mask;
}
static bool IsTrustedProxy(uint32_t ip) {
int i;
if (proxyips.n) {
for (i = 0; i < proxyips.n; ++i) {
if ((ip & proxyips.p[i].mask) == proxyips.p[i].ip) {
return true;
}
}
return false;
} else {
return IsPrivateIp(ip) || IsLoopbackIp(ip);
}
}
static void DescribeAddress(char buf[40], uint32_t addr, uint16_t port) {
char *p;
const char *s;
@ -872,34 +903,56 @@ static void DescribeAddress(char buf[40], uint32_t addr, uint16_t port) {
assert(p - buf < 40);
}
static inline void GetServerAddr(uint32_t *ip, uint16_t *port) {
static inline int GetServerAddr(uint32_t *ip, uint16_t *port) {
*ip = ntohl(serveraddr->sin_addr.s_addr);
if (port) *port = ntohs(serveraddr->sin_port);
return 0;
}
static inline void GetClientAddr(uint32_t *ip, uint16_t *port) {
static inline int GetClientAddr(uint32_t *ip, uint16_t *port) {
*ip = ntohl(clientaddr.sin_addr.s_addr);
if (port) *port = ntohs(clientaddr.sin_port);
return 0;
}
static inline void GetRemoteAddr(uint32_t *ip, uint16_t *port) {
static inline int GetRemoteAddr(uint32_t *ip, uint16_t *port) {
char str[40];
GetClientAddr(ip, port);
if (HasHeader(kHttpXForwardedFor) &&
(IsPrivateIp(*ip) || IsLoopbackIp(*ip))) {
if (ParseForwarded(HeaderData(kHttpXForwardedFor),
HeaderLength(kHttpXForwardedFor), ip, port) == -1)
WARNF("(srvr) invalid X-Forwarded-For value: %`'.*s",
HeaderLength(kHttpXForwardedFor), HeaderData(kHttpXForwardedFor));
if (HasHeader(kHttpXForwardedFor)) {
if (IsTrustedProxy(*ip)) {
if (ParseForwarded(HeaderData(kHttpXForwardedFor),
HeaderLength(kHttpXForwardedFor), ip, port) == -1) {
VERBOSEF("could not parse x-forwarded-for %`'.*s len=%ld",
HeaderLength(kHttpXForwardedFor),
HeaderData(kHttpXForwardedFor),
HeaderLength(kHttpXForwardedFor));
return -1;
}
} else {
WARNF(
"%hhu.%hhu.%hhu.%hhu isn't authorized to send x-forwarded-for %`'.*s",
*ip >> 24, *ip >> 16, *ip >> 8, *ip, HeaderLength(kHttpXForwardedFor),
HeaderData(kHttpXForwardedFor));
}
}
return 0;
}
static char *DescribeClient(void) {
uint32_t ip;
char str[40];
uint16_t port;
static char clientaddrstr[40];
GetRemoteAddr(&ip, &port);
DescribeAddress(clientaddrstr, ip, port);
return clientaddrstr;
uint32_t client;
static char description[128];
GetClientAddr(&client, &port);
if (HasHeader(kHttpXForwardedFor) && IsTrustedProxy(client)) {
DescribeAddress(str, client, port);
snprintf(description, sizeof(description), "%'.*s via %s",
HeaderLength(kHttpXForwardedFor), HeaderData(kHttpXForwardedFor),
str);
} else {
DescribeAddress(description, client, port);
}
return description;
}
static char *DescribeServer(void) {
@ -2225,10 +2278,8 @@ static bool Verify(void *data, size_t size, uint32_t crc) {
static void *Deflate(const void *data, size_t size, size_t *out_size) {
void *res;
z_stream zs;
z_stream zs = {0};
LockInc(&shared->c.deflates);
zs.zfree = 0;
zs.zalloc = 0;
CHECK_EQ(Z_OK, deflateInit2(&zs, 4, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL,
Z_DEFAULT_STRATEGY));
zs.next_in = data;
@ -3093,8 +3144,7 @@ static bool ShouldServeCrashReportDetails(void) {
if (leakcrashreports) {
return true;
} else {
GetRemoteAddr(&ip, &port);
return IsLoopbackIp(ip) || IsPrivateIp(ip);
return !GetRemoteAddr(&ip, &port) && (IsLoopbackIp(ip) || IsPrivateIp(ip));
}
}
@ -3354,6 +3404,42 @@ static int LuaRoute(lua_State *L) {
return 1;
}
static int LuaTrustProxy(lua_State *L) {
lua_Integer ip, cidr;
uint32_t ip32, imask;
ip = luaL_checkinteger(L, 1);
cidr = luaL_optinteger(L, 2, 32);
if (!(0 <= ip && ip <= 0xffffffff)) {
luaL_argerror(L, 1, "ip out of range");
unreachable;
}
if (!(0 <= cidr && cidr <= 32)) {
luaL_argerror(L, 2, "cidr should be 0 .. 32");
unreachable;
}
ip32 = ip;
imask = ~(0xffffffffu << (32 - cidr));
if (ip32 & imask) {
luaL_argerror(L, 1,
"ip address isn't the network address; "
"it has bits masked by the cidr");
unreachable;
}
TrustProxy(ip, cidr);
return 0;
}
static int LuaIsTrustedProxy(lua_State *L) {
lua_Integer ip;
ip = luaL_checkinteger(L, 1);
if (!(0 <= ip && ip <= 0xffffffff)) {
luaL_argerror(L, 1, "ip out of range");
unreachable;
}
lua_pushboolean(L, IsTrustedProxy(ip));
return 1;
}
static int LuaRespond(lua_State *L, char *R(unsigned, const char *)) {
char *p;
int code;
@ -3781,13 +3867,17 @@ static int LuaGetMethod(lua_State *L) {
return 1;
}
static int LuaGetAddr(lua_State *L, void GetAddr(uint32_t *, uint16_t *)) {
static int LuaGetAddr(lua_State *L, int GetAddr(uint32_t *, uint16_t *)) {
uint32_t ip;
uint16_t port;
GetAddr(&ip, &port);
lua_pushinteger(L, ip);
lua_pushinteger(L, port);
return 2;
if (!GetAddr(&ip, &port)) {
lua_pushinteger(L, ip);
lua_pushinteger(L, port);
return 2;
} else {
lua_pushnil(L);
return 1;
}
}
static int LuaGetServerAddr(lua_State *L) {
@ -4881,6 +4971,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"IsPrivateIp", LuaIsPrivateIp}, //
{"IsPublicIp", LuaIsPublicIp}, //
{"IsReasonablePath", LuaIsReasonablePath}, //
{"IsTrustedProxy", LuaIsTrustedProxy}, // undocumented
{"IsValidHttpToken", LuaIsValidHttpToken}, //
{"LaunchBrowser", LuaLaunchBrowser}, //
{"Lemur64", LuaLemur64}, //
@ -4906,13 +4997,13 @@ static const luaL_Reg kLuaFuncs[] = {
{"ProgramLogMessages", LuaProgramLogMessages}, //
{"ProgramLogPath", LuaProgramLogPath}, //
{"ProgramMaxPayloadSize", LuaProgramMaxPayloadSize}, //
{"ProgramMaxWorkers", LuaProgramMaxWorkers}, //
{"ProgramPidPath", LuaProgramPidPath}, //
{"ProgramPort", LuaProgramPort}, //
{"ProgramRedirect", LuaProgramRedirect}, //
{"ProgramTimeout", LuaProgramTimeout}, //
{"ProgramUid", LuaProgramUid}, //
{"ProgramUniprocess", LuaProgramUniprocess}, //
{"ProgramMaxWorkers", LuaProgramMaxWorkers}, //
{"Rand64", LuaRand64}, //
{"Rdrand", LuaRdrand}, //
{"Rdseed", LuaRdseed}, //
@ -4939,6 +5030,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"Sleep", LuaSleep}, //
{"Slurp", LuaSlurp}, //
{"StoreAsset", LuaStoreAsset}, //
{"TrustProxy", LuaTrustProxy}, // undocumented
{"Uncompress", LuaUncompress}, //
{"Underlong", LuaUnderlong}, //
{"VisualizeControlCodes", LuaVisualizeControlCodes}, //
@ -5584,9 +5676,8 @@ static void ParseRequestParameters(void) {
FreeLater(ParseRequestUri(inbuf.p + cpm.msg.uri.a,
cpm.msg.uri.b - cpm.msg.uri.a, &url));
if (!url.host.p) {
GetRemoteAddr(&ip, 0);
if (HasHeader(kHttpXForwardedHost) &&
(IsPrivateIp(ip) || IsLoopbackIp(ip))) {
if (HasHeader(kHttpXForwardedHost) && //
!GetRemoteAddr(&ip, 0) && IsTrustedProxy(ip)) {
FreeLater(ParseHost(HeaderData(kHttpXForwardedHost),
HeaderLength(kHttpXForwardedHost), &url));
} else if (HasHeader(kHttpHost)) {
@ -5689,7 +5780,6 @@ static char *Route(const char *host, size_t hostlen, const char *path,
// this function (as it always serves something); otherwise
// successful RoutePath and Route may fail with "508 loop detected"
cpm.loops.n = 0;
if (logmessages) LogMessage("received", inbuf.p, hdrsize);
if (hostlen && (p = RouteHost(host, hostlen, path, pathlen))) {
return p;
}
@ -6002,6 +6092,9 @@ static bool HandleMessageActual(void) {
if ((rc = ParseHttpMessage(&cpm.msg, inbuf.p, amtread)) != -1) {
if (!rc) return false;
hdrsize = rc;
if (logmessages) {
LogMessage("received", inbuf.p, hdrsize);
}
p = HandleRequest();
} else {
LockInc(&shared->c.badmessages);