mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-03 19:22:27 +00:00
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:
parent
994e1f4386
commit
775944a2d0
42 changed files with 8148 additions and 7298 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue