about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--adapter/img/sixel.nim87
-rw-r--r--adapter/img/stbi.nim7
-rw-r--r--src/bindings/libseccomp.nim3
-rw-r--r--src/utils/sandbox.nim25
5 files changed, 72 insertions, 54 deletions
diff --git a/Makefile b/Makefile
index 870382bd..9c4753fb 100644
--- a/Makefile
+++ b/Makefile
@@ -108,10 +108,10 @@ $(OUTDIR_CGI_BIN)/gopher: adapter/protocol/curlwrap.nim adapter/protocol/curlerr
 		adapter/gophertypes.nim adapter/protocol/curl.nim \
 		src/loader/connecterror.nim $(twtstr)
 $(OUTDIR_CGI_BIN)/stbi: adapter/img/stbi.nim adapter/img/stb_image.c \
-		adapter/img/stb_image.h src/utils/sandbox.nim
+		adapter/img/stb_image.h src/utils/sandbox.nim $(dynstream)
 $(OUTDIR_CGI_BIN)/jebp: adapter/img/jebp.c adapter/img/jebp.h \
 		src/utils/sandbox.nim
-$(OUTDIR_CGI_BIN)/sixel: src/types/color.nim src/utils/sandbox.nim $(twtstr)
+$(OUTDIR_CGI_BIN)/sixel: src/types/color.nim src/utils/sandbox.nim $(twtstr) $(dynstream)
 $(OUTDIR_CGI_BIN)/canvas: src/img/bitmap.nim src/img/painter.nim \
 	src/img/path.nim src/io/bufreader.nim src/types/color.nim \
 	src/types/line.nim src/utils/sandbox.nim $(dynstream) $(twtstr)
diff --git a/adapter/img/sixel.nim b/adapter/img/sixel.nim
index d34f8cf8..7f2cdb5d 100644
--- a/adapter/img/sixel.nim
+++ b/adapter/img/sixel.nim
@@ -20,26 +20,17 @@ import std/os
 import std/posix
 import std/strutils
 
+import io/dynstream
 import types/color
 import utils/sandbox
 import utils/twtstr
 
-const STDOUT_FILENO = 1
-
-proc writeAll(data: pointer; size: int) =
-  var n = 0
-  while n < size:
-    let i = write(STDOUT_FILENO, addr cast[ptr UncheckedArray[uint8]](data)[n],
-      int(size) - n)
-    assert i >= 0
-    n += i
-
-proc puts(s: string) =
-  if s.len > 0:
-    writeAll(unsafeAddr s[0], s.len)
+proc puts(os: PosixStream; s: string) =
+  os.sendDataLoop(s)
 
 proc die(s: string) {.noreturn.} =
-  puts(s)
+  let os = newPosixStream(STDOUT_FILENO)
+  os.puts(s)
   quit(1)
 
 const DCSSTART = "\eP"
@@ -147,18 +138,15 @@ proc trim(trimMap: var TrimMap; K: var int) =
   node.n = n
   K = k
 
-proc getPixel(s: string; m: int; bgcolor: ARGBColor): RGBColor {.inline.} =
-  let r = uint8(s[m])
-  let g = uint8(s[m + 1])
-  let b = uint8(s[m + 2])
-  let a = uint8(s[m + 3])
-  var c0 = RGBAColorBE(r: r, g: g, b: b, a: a)
+proc getPixel(img: seq[RGBAColorBE]; m: int; bgcolor: ARGBColor): RGBColor
+    {.inline.} =
+  var c0 = img[m]
   if c0.a != 255:
     let c1 = bgcolor.blend(c0)
     return RGBColor(uint32(rgb(c1.r, c1.g, c1.b)).fastmul(100))
   return RGBColor(uint32(rgb(c0.r, c0.g, c0.b)).fastmul(100))
 
-proc quantize(s: string; bgcolor: ARGBColor; palette: int): Node =
+proc quantize(img: seq[RGBAColorBE]; bgcolor: ARGBColor; palette: int): Node =
   let root = Node(leaf: false)
   # number of leaves
   var K = 0
@@ -168,9 +156,8 @@ proc quantize(s: string; bgcolor: ARGBColor; palette: int): Node =
   # batch together insertions of color runs
   var pc0 = RGBColor(0)
   var pcs = 0u32
-  for i in 0 ..< s.len div 4:
-    let m = i * 4
-    let c0 = s.getPixel(m, bgcolor)
+  for m in 0 ..< img.len:
+    let c0 = img.getPixel(m, bgcolor)
     if pc0 != c0:
       if pcs > 0:
         K += int(root.insert(pc0, trimMap, n = pcs))
@@ -347,13 +334,13 @@ proc createBands(bands: var seq[SixelBand]; chunkMap: seq[SixelChunk];
     if not found:
       bands.add(@[unsafeAddr chunk])
 
-proc encode(s: string; width, height, offx, offy, cropw: int; halfdump: bool;
-    bgcolor: ARGBColor; palette: int) =
+proc encode(img: seq[RGBAColorBE]; width, height, offx, offy, cropw: int;
+    halfdump: bool; bgcolor: ARGBColor; palette: int) =
   # reserve one entry for transparency
   # (this is necessary so that cropping works properly when the last
   # sixel would not fit on the screen, and also for images with !(height % 6).)
   let palette = palette - 1
-  let node = s.quantize(bgcolor, palette)
+  let node = img.quantize(bgcolor, palette)
   # prelude
   var outs = "Cha-Image-Dimensions: " & $width & 'x' & $height & "\n\n"
   let preludeLenPos = outs.len
@@ -368,13 +355,13 @@ proc encode(s: string; width, height, offx, offy, cropw: int; halfdump: bool;
     # prepend prelude size
     let L = outs.len - 4 - preludeLenPos # subtract length field
     outs.setU32BE(uint32(L), preludeLenPos)
-  puts(outs)
-  let W = width * 4
+  let os = newPosixStream(STDOUT_FILENO)
+  let W = width
   let H = W * height
   let realw = cropw - offx
   var n = offy * W
   var ymap = ""
-  var totalLen = 0
+  var totalLen = 0u32
   # add +2 so we don't have to bounds check
   var dither = Dither(
     d1: newSeq[DitherDiff](realw + 2),
@@ -382,17 +369,19 @@ proc encode(s: string; width, height, offx, offy, cropw: int; halfdump: bool;
   )
   var chunkMap = newSeq[SixelChunk](palette)
   var nrow = 1
+  # buffer to 64k, just because.
+  const MaxBuffer = 65546
   while true:
     if halfdump:
-      ymap.putU32BE(uint32(totalLen))
+      ymap.putU32BE(totalLen)
     for i in 0 ..< 6:
       if n >= H:
         break
       let mask = 1u8 shl i
       var chunk: ptr SixelChunk = nil
       for j in 0 ..< realw:
-        let m = n + (offx + j) * 4
-        let c0 = s.getPixel(m, bgcolor).correctDither(j, dither)
+        let m = n + offx + j
+        let c0 = img.getPixel(m, bgcolor).correctDither(j, dither)
         var diff: DitherDiff
         let c = node.getColor(c0, nodes, diff)
         dither.fs(j, diff)
@@ -424,27 +413,27 @@ proc encode(s: string; width, height, offx, offy, cropw: int; halfdump: bool;
       zeroMem(addr dither.d2[0], dither.d2.len * sizeof(dither.d2[0]))
     var bands: seq[SixelBand] = @[]
     bands.createBands(chunkMap, nrow)
-    outs.setLen(0)
-    for band in bands:
-      if outs.len > 0:
+    let olen = outs.len
+    for i in 0 ..< bands.len:
+      if i > 0:
         outs &= '$'
-      outs.compressSixel(band)
+      outs.compressSixel(bands[i])
     if n >= H:
       outs &= ST
-      totalLen += outs.len
+      totalLen += uint32(outs.len - olen)
       break
     else:
       outs &= '-'
-      totalLen += outs.len
-      puts(outs)
+      totalLen += uint32(outs.len - olen)
+      if outs.len >= MaxBuffer:
+        os.sendDataLoop(outs)
+        outs.setLen(0)
     inc nrow
   if halfdump:
-    ymap.putU32BE(uint32(totalLen))
+    ymap.putU32BE(totalLen)
     ymap.putU32BE(uint32(ymap.len))
     outs &= ymap
-    puts(outs)
-  else:
-    puts(outs)
+  os.sendDataLoop(outs)
 
 proc parseDimensions(s: string): (int, int) =
   let s = s.split('x')
@@ -512,9 +501,13 @@ proc main() =
       else:
         palette = 1024
     if width == 0 or height == 0:
-      puts("Cha-Image-Dimensions: 0x0\n")
+      let os = newPosixStream(STDOUT_FILENO)
+      os.sendDataLoop("Cha-Image-Dimensions: 0x0\n")
       quit(0) # done...
-    let s = stdin.readAll()
-    s.encode(width, height, offx, offy, cropw, halfdump, bgcolor, palette)
+    let n = width * height
+    var img = cast[seq[RGBAColorBE]](newSeqUninitialized[uint32](n))
+    let ps = newPosixStream(STDIN_FILENO)
+    ps.recvDataLoop(addr img[0], n * 4)
+    img.encode(width, height, offx, offy, cropw, halfdump, bgcolor, palette)
 
 main()
diff --git a/adapter/img/stbi.nim b/adapter/img/stbi.nim
index 8a1442f2..f0cfe2bb 100644
--- a/adapter/img/stbi.nim
+++ b/adapter/img/stbi.nim
@@ -3,6 +3,7 @@ import std/os
 import std/posix
 import std/strutils
 
+import io/dynstream
 import utils/sandbox
 import utils/twtstr
 
@@ -183,9 +184,9 @@ proc main() =
         if q < 1 or 100 < q:
           die("Cha-Control: ConnectionError 1 wrong quality")
         quality = cint(q)
-    let s = stdin.readAll()
-    if s.len != width * height * 4:
-      die("Cha-Control: ConnectionError 1 wrong size")
+    let ps = newPosixStream(STDIN_FILENO)
+    var s = newSeqUninitialized[uint8](width * height * 4)
+    ps.recvDataLoop(s)
     puts("Cha-Image-Dimensions: " & $width & 'x' & $height & "\n\n")
     let p = unsafeAddr s[0]
     case f
diff --git a/src/bindings/libseccomp.nim b/src/bindings/libseccomp.nim
index 81a6e969..3f02e4d9 100644
--- a/src/bindings/libseccomp.nim
+++ b/src/bindings/libseccomp.nim
@@ -37,6 +37,9 @@ const SCMP_ACT_KILL_PROCESS* = 0x80000000u32
 const SCMP_ACT_ALLOW* = 0x7FFF0000u32
 const SCMP_ACT_TRAP* = 0x00030000u32
 
+template SCMP_ACT_ERRNO*(x: uint16): uint32 =
+  0x50000u32 or x
+
 proc seccomp_init*(def_action: uint32): scmp_filter_ctx
 proc seccomp_reset*(ctx: scmp_filter_ctx; def_action: uint32): cint
 proc seccomp_syscall_resolve_name*(name: cstring): cint
diff --git a/src/utils/sandbox.nim b/src/utils/sandbox.nim
index 059bfe4b..a7168408 100644
--- a/src/utils/sandbox.nim
+++ b/src/utils/sandbox.nim
@@ -128,6 +128,27 @@ elif SandboxMode == stLibSeccomp:
         # PROT_WRITE (w/o PROT_READ) and PROT_NONE, which does no harm.
         doAssert seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscall, 1, arg2) == 0
 
+  proc blockStat(ctx: scmp_filter_ctx) =
+    # glibc calls fstat and its variants on fread, and it's quite hard
+    # to ensure we never use it. Plus, in older glibc versions (< 2.39),
+    # fstat is implemented as fstatat, and allowing that would imply
+    # access to arbitrary paths. So for consistency, we make all of them
+    # return an error.
+    #
+    # The offending function is _IO_file_doallocate; it doesn't actually
+    # look at errno, so EPERM should work fine.
+    const err = SCMP_ACT_ERRNO(uint16(EPERM))
+    const fstatList = [
+      cstring"fstat",
+      "fstat64",
+      "fstatat64",
+      "newfstatat",
+      "statx"
+    ]
+    for it in fstatList:
+      let syscall = seccomp_syscall_resolve_name(it)
+      doAssert seccomp_rule_add(ctx, err, syscall, 0) == 0
+
   proc enterBufferSandbox*(sockPath: string) =
     onSignal SIGSYS:
       discard sig
@@ -148,7 +169,6 @@ elif SandboxMode == stLibSeccomp:
       "exit_group", # for quit
       "fcntl", "fcntl64", # for changing blocking status
       "fork", # for when fork is really fork
-      "fstat", # glibc fread seems to call it
       "getpid", # for determining current PID after we fork
       "getrlimit", # glibc uses it after fork it seems
       "getsockname", # Nim needs it for connecting
@@ -185,6 +205,7 @@ elif SandboxMode == stLibSeccomp:
         datum_a: 1 # PF_LOCAL == PF_UNIX == AF_UNIX
       )
       doAssert seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscall, 1, arg0) == 0
+    ctx.blockStat()
     when defined(android):
       ctx.allowBionic()
     doAssert seccomp_load(ctx) == 0
@@ -204,7 +225,6 @@ elif SandboxMode == stLibSeccomp:
       "mmap", "mmap2", "mremap", "munmap", "brk", # memory allocation
       "poll", # curl needs poll
       "getpid", # used indirectly by OpenSSL EVP_RAND_CTX_new (through drbg)
-      "fstat", # glibc fread seems to call it
       # we either have to use CURLOPT_NOSIGNAL or allow signals.
       # do the latter, otherwise the default name resolver will never time out.
       "signal", "sigaction", "rt_sigaction",
@@ -212,6 +232,7 @@ elif SandboxMode == stLibSeccomp:
     for it in allowList:
       doAssert seccomp_rule_add(ctx, SCMP_ACT_ALLOW,
         seccomp_syscall_resolve_name(it), 0) == 0
+    ctx.blockStat()
     when defined(android):
       ctx.allowBionic()
     doAssert seccomp_load(ctx) == 0