Add a BPF seccomp syscall filter on Linux. Not enabled by default. To
compile:
XMPPIPE_SANDBOX=XMPPIPE_SANDBOX_SECCOMP make
The sandbox is derived from OpenSSH's seccomp sandbox by Will Drewry and
Kees Cook's tutorial on seccomp:
http://outflux.net/teach-seccomp/
The number of file descriptors enforced by setrlimit() can now be set at
compile time using a flag. The flag defaults to 0 on Linux and -1
everywhere else:
XMPPIPE_SANDBOX=XMPPIPE_SANDBOX_RLIMIT \
XMPPIPE_SANDBOX_RLIMIT_NOFILE=-1 \
make
The meaning of the XMPPIPE_SANDBOX_RLIMIT_NOFILE is:
* -1 : set rlim_cur/rlim_max to the lowest allocated file desciptor
* >=0: set rlim_cur/rlim_max to this number
On some platforms, setting rlim_cur below the value of the highest
allocated fd may interfere with polling. See commit a34d5766c5 for
details.
The rlimit sandbox disables forking processes and opening files.
The rlimit sandbox is not used by default yet. To compile it:
XMPPIPE_SANDBOX=XMPPIPE_SANDBOX_RLIMIT make
The rlimit sandbox should work on any platform. However the interaction
of RLIMIT_NOFILE with poll(2) (and select(2)?) on some platforms (FreeBSD
but really any OS besides Linux) is problematic:
* opening a number of fd's, setting RLIMIT_NOFILE to 0, calling
poll(2) on the fdset
Linux: works
FreeBSD: fails
* opening a number of fd's, setting RLIMIT_NOFILE to maxfd+1, calling
poll(2) on the fdset
Linux: works
FreeBSD: works
The issue with the second option is that a library may have opened a
sequence of file descriptors then closed the lower numbered fd's:
open() => 3
open() => 4
open() => 5
close(3)
close(4)
maxfd = 5
RLIMIT_NOFILE would be set to 6 (stdin, stdout, stderr, 3, 4, 5) and the
sandbox would allow re-opening fd's 3 and 4.
One possible fix would be to run through the sequence of fd's before
entering the rlimit sandbox:
* test if the fd is closed
* if the fd is closed, dup2(STDIN_FILENO, fd)
Since the closed fd's are not part of the pollset, they will not be
polled and should be ignored.
Note we can't simply move maxfd to the lowest unused fd because
libstrophe maintains the fd number as internal, opaque state.
Empirically, the xmpp fd is always 3. Another option would be to abort
the process if the fd does not equal 3.
Prepare for sandboxing the xmppipe process by adding a function called
after all file descriptors are allocated.
The intent of the sandbox is to limit the xmppipe process to the role
of a component in a shell pipeline: reading from stdin, reading/writing
to the XMPP socket and writing to stdout. Any activity not involved with
using stdio should force the process to exit.
The sandbox function will vary based on the capabilities of the
platform. The default sandbox function does nothing.
Limitations of the sandbox:
Probably the biggest risk is in session establishment:
* the TLS handshake
* the XML parsing
The sandbox is enforced after the TLS connection is established, i.e.,
after the file descriptor for the XMPP session is allocated and so has no
effect on the TLS handshake or the initial XMPP handshake.
Possibly an initial sandbox could be setup for the connection phase
followed by a stricter sandbox for the stdio phase.
Use uuid_create(3) and uuid_to_string(3) to create the message id on
BSDs. Only tested on FreeBSD but should work on OpenBSD and NetBSD.
Add untested support for compiling on Solaris and Mac OS X:
* SmartOS has libuuid installed by default with rsyslog via pkgsrc
* Mac OS X has libuuid as part of libSystem:
http://lists.apple.com/archives/unix-porting/2009/Aug/msg00006.html
Lengthen the poll delay to read in larger chunks of data. With a poll
value of 1 ms, there could be a large protocol message overhead: 1
character per XML stanza.
Server (system with access to the SSH and XMPP server):
# ssh-over-xmpp server <conference> <IP address> <port>
ssh-over-xmpp server sshxmpp 1.2.3.4 22
Client (system with access to the XMPP server):
ssh -o ProxyCommand="ssh-over-xmpp client sshxmpp" 127.0.0.1
Use a fixed ping handler rather than scheduling a ping handler per ping
request.
If the XMPP client or server is busy, many outstanding ping requests
can be waiting, resulting in a number of ping handlers being queued.
Each of the ping handlers does the same thing (resets the failure count
to 0) but any dropped pings resulted in the handlers being queued
forever.
For ejabberd and the mongooseim fork of ejabbered, 4097 bytes is around
the maximum message size before messages are dropped (the extra byte is
for libstrophe to put the terminating NULL. Otherwise, with a buffer
of 4096, the message will be broken up into 2 parts, with the second
consisting of the last byte of the message).
With base64 encoding, the message size will increase to 4097 * 4 / 3
bytes. When sending streams of binary data (cat'ing a file over XMPP,
ssh over XMPP, ...), stream management will get backlogged and messages
will disappear.
So halve the default buffer size. Is it too big for other jabber
servers? It is one of the mysteries but the "-b" option exists for those.
Implement flow control by monitoring the number of unacked stream
requests and the number of unprocessed stanzas. If either exceeds a user
controlled limit, xmppipe will stop reading from stdin. Further stream
requests will be generated whenever the keepalive timeout is reached and
an XMPP ping is sent.
ejabberd/mongooseim will close the connection if there are about 5
unacked stream requests. xmppipe should probably do the same thing.
With these changes, binaries can be reliably (but slowly) sent over
XMPP. ssh over XMPP is also working reliably (with light testing). Because
of the increase in size due to base64 encoding, the buffer size needs
to be reduced so the encoded message is under 4k (-m 1024 is ok).
When stdin is closed with stream management enabled, xmppipe waits for
the ack to match the number of stanzas sent to ensure all the data has
been flushed to the server.
The server may not respond to every stream request and a stream request
message is generated only when a stanza is sent. Since stdin is closed,
the only traffic is the XMPP pings which are sent out every 60 seconds
by default. The result is, when stdin is closed, it may take several
minutes for xmppipe to exit.
Work around the problem by sending a stream management request for every
stanza.