Make improvements

- Introduce path module to redbean
- Fix glitch with linenoise printing extra line on eof
- Introduce closefrom() and close_range() system calls
- Make file descriptor closing more secure in pledge.com
This commit is contained in:
Justine Tunney 2022-07-20 15:13:39 -07:00
parent 439ad21b12
commit 1837dc2e85
31 changed files with 806 additions and 75 deletions

View file

@ -214,8 +214,11 @@ int GetPollMaxFds(void) {
}
void NormalizeFileDescriptors(void) {
int i, n, fd;
int e, i, n, fd;
n = GetPollMaxFds();
e = errno;
closefrom(3); // more secure if linux 5.9+
errno = e;
for (i = 0; i < n; ++i) {
pfds[i].fd = i;
pfds[i].events = POLLIN;

View file

@ -1959,6 +1959,96 @@ RE MODULE
and re.Regex:search.
────────────────────────────────────────────────────────────────────────────────
PATH MODULE
The path module may be used to manipulate unix paths.
Note that we use unix paths on Windows. For example, if you have a
path like C:\foo\bar then it should be /c/foo/bar with redbean. It
should also be noted the unix module is more permissive when using
Windows paths, where translation to win32 is very light.
path.dirname(str)
└─→ str
Strips final component of path, e.g.
path │ dirname
───────────────────
. │ .
.. │ .
/ │ /
usr │ .
/usr/ │ /
/usr/lib │ /usr
/usr/lib/ │ /usr
path.basename(path:str)
└─→ str
Returns final component of path, e.g.
path │ basename
─────────────────────
. │ .
.. │ ..
/ │ /
usr │ usr
/usr/ │ usr
/usr/lib │ lib
/usr/lib/ │ lib
path.join(str, ...)
└─→ str
Concatenates path components, e.g.
x │ y │ joined
─────────────────────────────────
/ │ / │ /
/usr │ lib │ /usr/lib
/usr/ │ lib │ /usr/lib
/usr/lib │ /lib │ /lib
You may specify 1+ arguments.
Specifying no arguments will raise an error. If nil arguments are
specified, then they're skipped over. If exclusively nil arguments
are passed, then nil is returned. Empty strings behave similarly to
nil, but unlike nil may coerce a trailing slash.
path.exists(path:str)
└─→ bool
Returns true if path exists.
This function is inclusive of regular files, directories, and
special files. Symbolic links are followed are resolved. On error,
false is returned.
path.isfile(path:str)
└─→ bool
Returns true if path exists and is regular file.
Symbolic links are not followed. On error, false is returned.
path.isdir(path:str)
└─→ bool
Returns true if path exists and is directory.
Symbolic links are not followed. On error, false is returned.
path.islink(path:str)
└─→ bool
Returns true if path exists and is symbolic link.
Symbolic links are not followed. On error, false is returned.
────────────────────────────────────────────────────────────────────────────────
MAXMIND MODULE
@ -2220,12 +2310,36 @@ UNIX MODULE
Closes file descriptor.
This function should never be called twice for the same file
descriptor, regardless of whether or not an error happened. The file
descriptor is always gone after close is called. So it technically
always succeeds, but that doesn't mean an error should be ignored.
For example, on NFS a close failure could indicate data loss.
Closing does not mean that scheduled i/o operations have been
completed. You'd need to use fsync() or fdatasync() beforehand to
ensure that. You shouldn't need to do that normally, because our
close implementation guarantees a consistent view, since on systems
where it isn't guaranteed (like Windows) close will implicitly sync.
File descriptors are automatically closed on exit().
Returns `EBADF` if `fd` wasn't valid.
Returns `EINTR` possibly maybe.
Returns `EIO` if an i/o error occurred.
unix.read(fd:int[, bufsiz:str[, offset:int]])
├─→ data:str
└─→ nil, unix.Errno
Reads from file descriptor.
This function returns empty string on end of file. The exception is
if `bufsiz` is zero, in which case an empty returned string means
the file descriptor works.
unix.write(fd:int, data:str[, offset:int])
├─→ wrotebytes:int
└─→ nil, unix.Errno
@ -3778,14 +3892,66 @@ UNIX MODULE
├─→ true
└─→ nil, unix.Errno
Unveil parts of a restricted filesystem view, e.g.
Restricts filesystem operations, e.g.
unix.unveil(".", "r")
unix.unveil(nil, nil)
unix.unveil(".", "r"); -- current dir + children visible
unix.unveil("/etc", "r"); -- make /etc readable too
unix.unveil(0, 0); -- commit and lock policy
This can be used for sandboxing file system access.
Unveiling restricts a thread's view of the filesystem to a set of
allowed paths with specific privileges.
Unveil support is a work in progress.
Once you start using unveil(), the entire file system is considered
hidden. You then specify, by repeatedly calling unveil(), which paths
should become unhidden. When you're finished, you call `unveil(0,0)`
which commits your policy, after which further use is forbidden, in
the current thread, as well as any threads or processes it spawns.
There are some differences between unveil() on Linux versus OpenBSD.
1. Build your policy and lock it in one go. On OpenBSD, policies take
effect immediately and may evolve as you continue to call unveil()
but only in a more restrictive direction. On Linux, nothing will
happen until you call `unveil(0,0)` which commits and locks.
2. Try not to overlap directory trees. On OpenBSD, if directory trees
overlap, then the most restrictive policy will be used for a given
file. On Linux overlapping may result in a less restrictive policy
and possibly even undefined behavior.
3. OpenBSD and Linux disagree on error codes. On OpenBSD, accessing
paths outside of the allowed set raises ENOENT, and accessing ones
with incorrect permissions raises EACCES. On Linux, both these
cases raise EACCES.
4. Unlike OpenBSD, Linux does nothing to conceal the existence of
paths. Even with an unveil() policy in place, it's still possible
to access the metadata of all files using functions like stat()
and open(O_PATH), provided you know the path. A sandboxed process
can always, for example, determine how many bytes of data are in
/etc/passwd, even if the file isn't readable. But it's still not
possible to use opendir() and go fishing for paths which weren't
previously known.
This system call is supported natively on OpenBSD and polyfilled on
Linux using the Landlock LSM[1].
`path` is the file or directory to unveil
`permissions` is a string consisting of zero or more of the
following characters:
- 'r' makes `path` available for read-only path operations,
corresponding to the pledge promise "rpath".
- `w` makes `path` available for write operations, corresponding
to the pledge promise "wpath".
- `x` makes `path` available for execute operations,
corresponding to the pledge promises "exec" and "execnative".
- `c` allows `path` to be created and removed, corresponding to
the pledge promise "cpath".
unix.gmtime(unixts:int)
├─→ year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst:int,zone:str

166
tool/net/lpath.c Normal file
View file

@ -0,0 +1,166 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/stat.h"
#include "libc/errno.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/s.h"
#include "third_party/lua/lauxlib.h"
/**
* @fileoverview redbean unix path manipulation module
*/
// path.basename(str)
// └─→ str
static int LuaPathBasename(lua_State *L) {
size_t i, n;
const char *p;
if ((p = luaL_optlstring(L, 1, 0, &n)) && n) {
while (n > 1 && p[n - 1] == '/') --n;
i = n - 1;
while (i && p[i - 1] != '/') --i;
lua_pushlstring(L, p + i, n - i);
} else {
lua_pushlstring(L, ".", 1);
}
return 1;
}
// path.dirname(str)
// └─→ str
static int LuaPathDirname(lua_State *L) {
size_t n;
const char *p;
if ((p = luaL_optlstring(L, 1, 0, &n)) && n--) {
for (; p[n] == '/'; n--) {
if (!n) goto ReturnSlash;
}
for (; p[n] != '/'; n--) {
if (!n) goto ReturnDot;
}
for (; p[n] == '/'; n--) {
if (!n) goto ReturnSlash;
}
lua_pushlstring(L, p, n + 1);
return 1;
}
ReturnDot:
lua_pushlstring(L, ".", 1);
return 1;
ReturnSlash:
lua_pushlstring(L, "/", 1);
return 1;
}
// path.join(str, ...)
// └─→ str
static int LuaPathJoin(lua_State *L) {
int i, n;
size_t z;
bool gotstr;
const char *c;
luaL_Buffer b;
bool needslash;
if ((n = lua_gettop(L))) {
luaL_buffinit(L, &b);
gotstr = false;
needslash = false;
for (i = 1; i <= n; ++i) {
if (lua_isnoneornil(L, i)) continue;
gotstr = true;
c = luaL_checklstring(L, i, &z);
if (z) {
if (c[0] == '/') {
luaL_buffsub(&b, luaL_bufflen(&b));
} else if (needslash) {
luaL_addchar(&b, '/');
}
luaL_addlstring(&b, c, z);
needslash = c[z - 1] != '/';
} else if (needslash) {
luaL_addchar(&b, '/');
needslash = false;
}
}
if (gotstr) {
luaL_pushresult(&b);
} else {
lua_pushnil(L);
}
return 1;
} else {
luaL_error(L, "missing argument");
unreachable;
}
}
static int CheckPath(lua_State *L, int type, int flags) {
int olderr;
struct stat st;
const char *path;
path = luaL_checkstring(L, 1);
olderr = errno;
if (fstatat(AT_FDCWD, path, &st, flags) != -1) {
lua_pushboolean(L, !type || (st.st_mode & S_IFMT) == type);
} else {
errno = olderr;
lua_pushboolean(L, false);
}
return 1;
}
// path.exists(str)
// └─→ bool
static int LuaPathExists(lua_State *L) {
return CheckPath(L, 0, 0);
}
// path.isfile(str)
// └─→ bool
static int LuaPathIsfile(lua_State *L) {
return CheckPath(L, S_IFREG, AT_SYMLINK_NOFOLLOW);
}
// path.islink(str)
// └─→ bool
static int LuaPathIslink(lua_State *L) {
return CheckPath(L, S_IFLNK, AT_SYMLINK_NOFOLLOW);
}
// path.isdir(str)
// └─→ bool
static int LuaPathIsdir(lua_State *L) {
return CheckPath(L, S_IFDIR, AT_SYMLINK_NOFOLLOW);
}
static const luaL_Reg kLuaPath[] = {
{"basename", LuaPathBasename}, //
{"dirname", LuaPathDirname}, //
{"exists", LuaPathExists}, //
{"isdir", LuaPathIsdir}, //
{"isfile", LuaPathIsfile}, //
{"islink", LuaPathIslink}, //
{"join", LuaPathJoin}, //
{0}, //
};
int LuaPath(lua_State *L) {
luaL_newlib(L, kLuaPath);
return 1;
}

11
tool/net/lpath.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_TOOL_NET_LPATH_H_
#define COSMOPOLITAN_TOOL_NET_LPATH_H_
#include "third_party/lua/lauxlib.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int LuaPath(lua_State *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_NET_LPATH_H_ */

View file

@ -96,6 +96,7 @@ o/$(MODE)/tool/net/redbean.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lpath.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
@ -217,6 +218,7 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lpath.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
@ -336,6 +338,7 @@ o/$(MODE)/tool/net/redbean-unsecure.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean-unsecure.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lpath.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \

View file

@ -115,6 +115,7 @@
#include "tool/net/lfinger.h"
#include "tool/net/lfuncs.h"
#include "tool/net/ljson.h"
#include "tool/net/lpath.h"
#include "tool/net/luacheck.h"
#include "tool/net/sandbox.h"
@ -5272,6 +5273,7 @@ static const luaL_Reg kLuaLibs[] = {
{"lsqlite3", luaopen_lsqlite3}, //
{"maxmind", LuaMaxmind}, //
{"finger", LuaFinger}, //
{"path", LuaPath}, //
{"re", LuaRe}, //
{"unix", LuaUnix}, //
};
@ -6323,8 +6325,7 @@ static bool HandleMessageActual(void) {
if (hasonloglatency) LuaOnLogLatency(reqtime, contime);
if (loglatency || LOGGABLE(kLogDebug))
LOGF(kLogDebug, "(stat) %`'.*s latency r: %,ldµs c: %,ldµs",
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a,
reqtime, contime);
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, reqtime, contime);
}
if (!generator) {
return TransmitResponse(p);