diff options
-rw-r--r-- | adapter/protocol/http.nim | 2 | ||||
-rw-r--r-- | src/bindings/capsicum.nim | 6 | ||||
-rw-r--r-- | src/io/bind_unix.c | 16 | ||||
-rw-r--r-- | src/io/connect_unix.c | 16 | ||||
-rw-r--r-- | src/io/serversocket.nim | 38 | ||||
-rw-r--r-- | src/io/socketstream.nim | 30 | ||||
-rw-r--r-- | src/loader/loader.nim | 21 | ||||
-rw-r--r-- | src/local/client.nim | 7 | ||||
-rw-r--r-- | src/main.nim | 7 | ||||
-rw-r--r-- | src/server/buffer.nim | 20 | ||||
-rw-r--r-- | src/server/forkserver.nim | 33 | ||||
-rw-r--r-- | src/utils/sandbox.nim | 13 |
12 files changed, 171 insertions, 38 deletions
diff --git a/adapter/protocol/http.nim b/adapter/protocol/http.nim index 25e39330..2bdd6858 100644 --- a/adapter/protocol/http.nim +++ b/adapter/protocol/http.nim @@ -4,6 +4,7 @@ else: import std/os import std/posix import std/strutils +import utils/sandbox import curl import curlerrors @@ -76,6 +77,7 @@ proc curlPreRequest(clientp: pointer, conn_primary_ip, conn_local_ip: cstring, let op = cast[HttpHandle](clientp) op.connectreport = true puts("Cha-Control: Connected\n") + enterSandbox() return 0 # ok proc main() = diff --git a/src/bindings/capsicum.nim b/src/bindings/capsicum.nim new file mode 100644 index 00000000..e01c3efb --- /dev/null +++ b/src/bindings/capsicum.nim @@ -0,0 +1,6 @@ +{.push header: "sys/capsicum.h", importc.} + +proc cap_enter*(): cint +proc cap_getmode*(modep: ptr cuint): cint + +{.pop.} diff --git a/src/io/bind_unix.c b/src/io/bind_unix.c index 02e86eed..398c2c9e 100644 --- a/src/io/bind_unix.c +++ b/src/io/bind_unix.c @@ -1,4 +1,5 @@ #include <stddef.h> +#include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> @@ -12,3 +13,18 @@ int bind_unix_from_c(int socket, const char *path, int pathlen) memcpy(sa.sun_path, path, pathlen + 1); return bind(socket, (struct sockaddr *)&sa, len); } + +#ifdef __FreeBSD__ +#include <fcntl.h> + +int bindat_unix_from_c(int fd, int socket, const char *path, int pathlen) +{ + struct sockaddr_un sa = { + .sun_family = AF_UNIX + }; + int len = offsetof(struct sockaddr_un, sun_path) + pathlen + 1; + + memcpy(sa.sun_path, path, pathlen + 1); + return bindat(fd, socket, (struct sockaddr *)&sa, len); +} +#endif diff --git a/src/io/connect_unix.c b/src/io/connect_unix.c index 26b3d6db..41faab11 100644 --- a/src/io/connect_unix.c +++ b/src/io/connect_unix.c @@ -1,4 +1,5 @@ #include <stddef.h> +#include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> @@ -12,3 +13,18 @@ int connect_unix_from_c(int socket, const char *path, int pathlen) memcpy(sa.sun_path, path, pathlen + 1); return connect(socket, (struct sockaddr *)&sa, len); } + +#ifdef __FreeBSD__ +#include <fcntl.h> + +int connectat_unix_from_c(int fd, int socket, const char *path, int pathlen) +{ + struct sockaddr_un sa = { + .sun_family = AF_UNIX + }; + int len = offsetof(struct sockaddr_un, sun_path) + pathlen + 1; + + memcpy(sa.sun_path, path, pathlen + 1); + return connectat(fd, socket, (struct sockaddr *)&sa, len); +} +#endif diff --git a/src/io/serversocket.nim b/src/io/serversocket.nim index a6acc555..ea5bc97d 100644 --- a/src/io/serversocket.nim +++ b/src/io/serversocket.nim @@ -9,26 +9,44 @@ type ServerSocket* = object sock*: Socket path*: string -var SocketDirectory* = "/tmp/cha" const SocketPathPrefix = "cha_sock_" -proc getSocketPath*(pid: int): string = - SocketDirectory / SocketPathPrefix & $pid +proc getSocketName*(pid: int): string = + SocketPathPrefix & $pid + +proc getSocketPath*(socketDir: string; pid: int): string = + socketDir / getSocketName(pid) # The way stdlib does bindUnix is utterly broken at least on FreeBSD. # It seems that just writing it in C is the easiest solution. {.compile: "bind_unix.c".} -proc bind_unix_from_c(fd: cint, path: cstring, pathlen: cint): cint {.importc.} +proc bind_unix_from_c(fd: cint; path: cstring; pathlen: cint): cint + {.importc.} + +when defined(freebsd): + # capsicum stuff + proc unlinkat(dfd: cint; path: cstring; flag: cint): cint + {.importc, header: "<unistd.h>".} + proc bindat_unix_from_c(dfd, sock: cint; path: cstring; pathlen: cint): cint + {.importc.} -proc initServerSocket*(pid: int; blocking = true): ServerSocket = - createDir(SocketDirectory) +proc initServerSocket*(sockDir: string; sockDirFd, pid: int; blocking = true): + ServerSocket = let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP, buffered = false) if not blocking: sock.getFd().setBlocking(false) - let path = getSocketPath(pid) - discard unlink(cstring(path)) - if bind_unix_from_c(cint(sock.getFd()), cstring(path), cint(path.len)) != 0: - raiseOSError(osLastError()) + let path = getSocketPath(sockDir, pid) + if sockDirFd == -1: + discard unlink(cstring(path)) + if bind_unix_from_c(cint(sock.getFd()), cstring(path), cint(path.len)) != 0: + raiseOSError(osLastError()) + else: + when defined(freebsd): + let name = getSocketName(pid) + discard unlinkat(cint(sockDirFd), cstring(name), 0) + if bindat_unix_from_c(cint(sockDirFd), cint(sock.getFd()), cstring(name), + cint(name.len)) != 0: + raiseOSError(osLastError()) listen(sock) return ServerSocket(sock: sock, path: path) diff --git a/src/io/socketstream.nim b/src/io/socketstream.nim index 3c8e6fa6..32aff96d 100644 --- a/src/io/socketstream.nim +++ b/src/io/socketstream.nim @@ -58,27 +58,41 @@ method sclose*(s: SocketStream) = # see serversocket.nim for an explanation {.compile: "connect_unix.c".} -proc connect_unix_from_c(fd: cint, path: cstring, pathlen: cint): cint +proc connect_unix_from_c(fd: cint; path: cstring; pathlen: cint): cint {.importc.} +when defined(freebsd): + # for FreeBSD/capsicum + proc connectat_unix_from_c(baseFd, sockFd: cint; rel_path: cstring; + rel_pathlen: cint): cint {.importc.} -proc connectSocketStream*(path: string; blocking = true): SocketStream = +proc connectAtSocketStream0(socketDir: string; baseFd, pid: int; + blocking = true): SocketStream = let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP, buffered = false) if not blocking: sock.getFd().setBlocking(false) - if connect_unix_from_c(cint(sock.getFd()), cstring(path), - cint(path.len)) != 0: - raiseOSError(osLastError()) + let path = getSocketPath(socketDir, pid) + if baseFd == -1: + if connect_unix_from_c(cint(sock.getFd()), cstring(path), + cint(path.len)) != 0: + raiseOSError(osLastError()) + else: + when defined(freebsd): + doAssert baseFd != -1 + let name = getSocketName(pid) + if connectat_unix_from_c(cint(baseFd), cint(sock.getFd()), cstring(name), + cint(name.len)) != 0: + raiseOSError(osLastError()) return SocketStream( source: sock, fd: cint(sock.getFd()), blocking: blocking ) -proc connectSocketStream*(pid: int; blocking = true): - SocketStream = +proc connectSocketStream*(socketDir: string; baseFd, pid: int; + blocking = true): SocketStream = try: - return connectSocketStream(getSocketPath(pid), blocking) + return connectAtSocketStream0(socketDir, baseFd, pid, blocking) except OSError: return nil diff --git a/src/loader/loader.nim b/src/loader/loader.nim index 295062b5..9f64b440 100644 --- a/src/loader/loader.nim +++ b/src/loader/loader.nim @@ -60,6 +60,10 @@ type unregistered*: seq[int] registerFun*: proc(fd: int) unregisterFun*: proc(fd: int) + # directory where we store UNIX domain sockets + sockDir*: string + # (FreeBSD only) fd for the socket directory so we can connectat() on it + sockDirFd*: int ConnectData = object promise: Promise[JSResult[Response]] @@ -678,7 +682,8 @@ proc initLoaderContext(fd: cint; config: LoaderConfig): LoaderContext = ) gctx = ctx let myPid = getCurrentProcessId() - ctx.ssock = initServerSocket(myPid, blocking = true) + # we don't capsicumize loader, so -1 is appropriate here + ctx.ssock = initServerSocket(config.tmpdir, -1, myPid, blocking = true) let sfd = int(ctx.ssock.sock.getFd()) ctx.selector.registerHandle(sfd, {Read}, 0) # The server has been initialized, so the main process can resume execution. @@ -847,7 +852,8 @@ template withLoaderPacketWriter(stream: SocketStream; loader: FileLoader; body proc connect(loader: FileLoader): SocketStream = - return connectSocketStream(loader.process, blocking = true) + return connectSocketStream(loader.sockDir, loader.sockDirFd, loader.process, + blocking = true) # Start a request. This should not block (not for a significant amount of time # anyway). @@ -1092,3 +1098,14 @@ proc removeClient*(loader: FileLoader; pid: int) = w.swrite(lcRemoveClient) w.swrite(pid) stream.sclose() + + +when defined(freebsd): + let O_DIRECTORY* {.importc, header: "<fcntl.h>", noinit.}: cint + +proc setSocketDir*(loader: FileLoader; path: string) = + loader.sockDir = path + when defined(freebsd): + loader.sockDirFd = open(cstring(path), O_DIRECTORY) + else: + loader.sockDirFd = -1 diff --git a/src/local/client.nim b/src/local/client.nim index c63a18db..c4aad504 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -407,7 +407,8 @@ proc acceptBuffers(client: Client) = client.selector.registerHandle(fd, {Read, Write}, 0) for item in pager.procmap: let container = item.container - let stream = connectSocketStream(container.process) + let stream = connectSocketStream(client.config.external.tmpdir, + client.loader.sockDirFd, container.process) if stream == nil: pager.alert("Error: failed to set up buffer") continue @@ -812,12 +813,14 @@ proc newClient*(config: Config; forkserver: ForkServer; jsctx: JSContext; let jsrt = JS_GetRuntime(jsctx) JS_SetModuleLoaderFunc(jsrt, normalizeModuleName, clientLoadJSModule, nil) let pager = newPager(config, forkserver, jsctx, warnings) - let loader = forkserver.newFileLoader(LoaderConfig( + let loaderPid = forkserver.forkLoader(LoaderConfig( urimethodmap: config.external.urimethodmap, w3mCGICompat: config.external.w3m_cgi_compat, cgiDir: seq[string](config.external.cgi_dir), tmpdir: config.external.tmpdir )) + let loader = FileLoader(process: loaderPid, clientPid: getCurrentProcessId()) + loader.setSocketDir(config.external.tmpdir) pager.setLoader(loader) let client = Client(config: config, jsrt: jsrt, jsctx: jsctx, pager: pager) jsrt.setInterruptHandler(interruptHandler, cast[pointer](client)) diff --git a/src/main.nim b/src/main.nim index 20d9c564..338e9518 100644 --- a/src/main.nim +++ b/src/main.nim @@ -1,13 +1,12 @@ import version # Note: we can't just import std/os or the compiler cries. (No idea why.) -from std/os import getEnv, putEnv, commandLineParams, getCurrentDir +from std/os import getEnv, putEnv, commandLineParams, getCurrentDir, createDir import std/options import server/forkserver import config/chapath import config/config -import io/serversocket import js/javascript import local/client import local/term @@ -204,8 +203,10 @@ proc main() = if pages.len == 0 and not config.start.headless: if stdin.isatty(): help(1) + # make sure tmpdir actually exists; if we do this later, then forkserver may + # try to open an empty dir + createDir(config.external.tmpdir) forkserver.loadForkServerConfig(config) - SocketDirectory = config.external.tmpdir let client = newClient(config, forkserver, jsctx, warnings) try: client.launchClient(pages, ctx.contentType, ctx.charset, ctx.dump) diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 0df80567..12665334 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -904,6 +904,10 @@ const bsdPlatform = defined(macosx) or defined(freebsd) or defined(netbsd) or proc onload(buffer: Buffer) +when defined(freebsd): + # necessary for an ugly hack we will do later + import std/kqueue + # Create an exact clone of the current buffer. # This clone will share the loader process with the previous buffer. proc clone*(buffer: Buffer, newurl: URL): int {.proxy.} = @@ -932,7 +936,14 @@ proc clone*(buffer: Buffer, newurl: URL): int {.proxy.} = # Closing seems to suffice here. when not bsdPlatform: buffer.selector.close() - buffer.selector = newSelector[int]() + when defined(freebsd): + # hack necessary because newSelector calls sysctl, but capsicum really + # dislikes that. + let fd = kqueue() + doAssert fd != -1 + cast[ptr cint](buffer.selector)[] = fd + else: + buffer.selector = newSelector[int]() #TODO set buffer.window.timeouts.selector var cfds: seq[int] = @[] for fd in buffer.loader.connecting.keys: @@ -964,7 +975,8 @@ proc clone*(buffer: Buffer, newurl: URL): int {.proxy.} = # We ignore errors; not much we can do with them here :/ discard buffer.rewind(buffer.bytesRead, unregister = false) buffer.pstream.sclose() - let ssock = initServerSocket(myPid) + let ssock = initServerSocket(buffer.loader.sockDir, buffer.loader.sockDirFd, + myPid) buffer.ssock = ssock ps.write(char(0)) buffer.url = newurl @@ -1866,7 +1878,7 @@ proc cleanup(buffer: Buffer) = proc launchBuffer*(config: BufferConfig; url: URL; request: Request; attrs: WindowAttributes; ishtml: bool; charsetStack: seq[Charset]; - loader: FileLoader; ssock: ServerSocket) = + loader: FileLoader; ssock: ServerSocket; selector: Selector[int]) = let pstream = ssock.acceptSocketStream() let buffer = Buffer( attrs: attrs, @@ -1878,7 +1890,7 @@ proc launchBuffer*(config: BufferConfig; url: URL; request: Request; pstream: pstream, request: request, rfd: pstream.fd, - selector: newSelector[int](), + selector: selector, ssock: ssock, url: url, charsetStack: charsetStack, diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim index 0e1f1d3b..c1bbdedb 100644 --- a/src/server/forkserver.nim +++ b/src/server/forkserver.nim @@ -1,6 +1,7 @@ import std/options import std/os import std/posix +import std/selectors import std/tables import config/config @@ -16,6 +17,7 @@ import types/urimethodmap import types/url import types/winattrs import utils/proctitle +import utils/sandbox import utils/strwidth import chagashi/charset @@ -34,15 +36,17 @@ type ostream: PosixStream children: seq[int] loaderPid: int + sockDirFd: int + sockDir: string -proc newFileLoader*(forkserver: ForkServer; config: LoaderConfig): FileLoader = +proc forkLoader*(forkserver: ForkServer; config: LoaderConfig): int = forkserver.ostream.withPacketWriter w: w.swrite(fcForkLoader) w.swrite(config) var r = forkserver.istream.initPacketReader() var process: int r.sread(process) - return FileLoader(process: process, clientPid: getCurrentProcessId()) + return process proc loadForkServerConfig*(forkserver: ForkServer, config: Config) = forkserver.ostream.withPacketWriter w: @@ -137,10 +141,18 @@ proc forkBuffer(ctx: var ForkServerContext; r: var BufferedReader): int = for i in 0 ..< ctx.children.len: ctx.children[i] = 0 ctx.children.setLen(0) let loaderPid = ctx.loaderPid + let sockDir = ctx.sockDir + let sockDirFd = ctx.sockDirFd zeroMem(addr ctx, sizeof(ctx)) discard close(pipefd[0]) # close read + closeStdin() + closeStdout() + # must call before entering the sandbox, or capsicum cries because of Nim + # calling sysctl + let selector = newSelector[int]() + enterSandbox() let pid = getCurrentProcessId() - let ssock = initServerSocket(pid) + let ssock = initServerSocket(sockDir, sockDirFd, pid) gssock = ssock onSignal SIGTERM: # This will be overridden after buffer has been set up; it is only @@ -150,16 +162,16 @@ proc forkBuffer(ctx: var ForkServerContext; r: var BufferedReader): int = let ps = newPosixStream(pipefd[1]) ps.write(char(0)) ps.sclose() - closeStdin() - closeStdout() let loader = FileLoader( process: loaderPid, - clientPid: pid + clientPid: pid, + sockDir: sockDir, + sockDirFd: sockDirFd ) try: setBufferProcessTitle(url) launchBuffer(config, url, request, attrs, ishtml, charsetStack, loader, - ssock) + ssock, selector) except CatchableError: let e = getCurrentException() # taken from system/excpt.nim @@ -180,7 +192,8 @@ proc runForkServer() = setProcessTitle("cha forkserver") var ctx = ForkServerContext( istream: newPosixStream(stdin.getFileHandle()), - ostream: newPosixStream(stdout.getFileHandle()) + ostream: newPosixStream(stdout.getFileHandle()), + sockDirFd: -1 ) signal(SIGCHLD, SIG_IGN) while true: @@ -212,7 +225,9 @@ proc runForkServer() = var config: ForkServerConfig r.sread(config) set_cjk_ambiguous(config.ambiguous_double) - SocketDirectory = config.tmpdir + ctx.sockDir = config.tmpdir + when defined(freebsd): + ctx.sockDirFd = open(cstring(ctx.sockDir), O_DIRECTORY) except EOFError: # EOF break diff --git a/src/utils/sandbox.nim b/src/utils/sandbox.nim new file mode 100644 index 00000000..88fc5c10 --- /dev/null +++ b/src/utils/sandbox.nim @@ -0,0 +1,13 @@ +when defined(freebsd): + import bindings/capsicum + +when defined(freebsd): + proc enterSandbox*() = + # per man:cap_enter(2), it may return ENOSYS if the kernel was compiled + # without CAPABILITY_MODE. So it seems better not to panic in this case. + # (But TODO: when we get enough sandboxing coverage it should print a + # warning or something.) + discard cap_enter() +else: + proc enterSandbox*() = + discard |