diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | adapter/img/sixel.nim | 87 | ||||
-rw-r--r-- | adapter/img/stbi.nim | 7 | ||||
-rw-r--r-- | src/bindings/libseccomp.nim | 3 | ||||
-rw-r--r-- | src/utils/sandbox.nim | 25 |
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 |