1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
# Security model with sandboxing:
#
# Buffer processes are the most security-sensitive, since they parse
# various resources retrieved from the network (CSS, HTML) and sometimes
# even execute untrusted code (JS, with an engine written in C). So the
# main goal is to give buffers as few permissions as possible.
#
# Aside from sandboxing in buffer processes, we also have a more
# restrictive "network" sandbox that is intended for CGI processes that
# just read/write from/to the network and stdin/stdout. At the moment this
# is used in the HTTP process and all image manipulation processes (codecs,
# resize).
#
# On FreeBSD, we create a file descriptor to the directory sockets
# reside in, and then use that for manipulating our sockets.
#
# Capsicum does not enable more fine-grained capability control, but
# in practice the things it does enable should not be enough to harm the
# user's system.
#
# On OpenBSD, we pledge the minimum amount of promises we need, and
# do not unveil anything. It seems to be roughly equivalent to the
# security we get with FreeBSD Capsicum, except connect(3) can connect
# to any UNIX domain socket on the file system.
#
# On Linux, we use chaseccomp which is a very dumb BPF assembler for
# seccomp-bpf. Like the OpenBSD filter, this does not prevent a
# connect(3) to UNIX domain sockets that we do not have access to.
#
# We do not have syscall sandboxing on other systems (yet).
const disableSandbox {.booldefine.} = false
type SandboxType* = enum
stNone = "no sandbox"
stCapsicum = "capsicum"
stPledge = "pledge"
stSeccomp = "seccomp-bpf"
const SandboxMode* = when disableSandbox:
stNone
elif defined(freebsd):
stCapsicum
elif defined(openbsd):
stPledge
elif defined(linux):
stSeccomp
else:
stNone
when SandboxMode == stCapsicum:
proc cap_enter(): cint {.importc, cdecl, header: "<sys/capsicum.h>".}
proc enterBufferSandbox*(sockPath: string) =
# 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()
proc enterNetworkSandbox*() =
# no difference between buffer; Capsicum is quite straightforward
# to use in this regard.
discard cap_enter()
elif SandboxMode == stPledge:
proc pledge(promises, execpromises: cstring): cint {.importc, cdecl,
header: "<unistd.h>".}
proc enterBufferSandbox*(sockPath: string) =
# take whatever we need to
# * fork
# * connect to UNIX domain sockets
# * take FDs from the main process
doAssert pledge("unix stdio sendfd recvfd proc", nil) == 0
proc enterNetworkSandbox*() =
# we don't need much to write out data from sockets to stdout.
doAssert pledge("stdio", nil) == 0
elif SandboxMode == stSeccomp:
proc sourceParent(): string =
var s = currentSourcePath()
while s.len > 0 and s[^1] != '/':
s.setLen(s.len - 1)
return s
{.passl: sourceParent() & "../../lib/chaseccomp/chaseccomp.o".}
import std/posix
proc cha_enter_buffer_sandbox(): cint {.importc, cdecl.}
proc cha_enter_network_sandbox(): cint {.importc, cdecl.}
proc enterBufferSandbox*(sockPath: string) =
doAssert cha_enter_buffer_sandbox() == 1
proc enterNetworkSandbox*() =
doAssert cha_enter_network_sandbox() == 1
else:
{.warning: "Building without syscall sandboxing!".}
proc enterBufferSandbox*(sockPath: string) =
discard
proc enterNetworkSandbox*() =
discard
|