about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--adapter/protocol/http.nim2
-rw-r--r--src/bindings/capsicum.nim6
-rw-r--r--src/io/bind_unix.c16
-rw-r--r--src/io/connect_unix.c16
-rw-r--r--src/io/serversocket.nim38
-rw-r--r--src/io/socketstream.nim30
-rw-r--r--src/loader/loader.nim21
-rw-r--r--src/local/client.nim7
-rw-r--r--src/main.nim7
-rw-r--r--src/server/buffer.nim20
-rw-r--r--src/server/forkserver.nim33
-rw-r--r--src/utils/sandbox.nim13
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