Comment

Comment by kragen

Last night I was looking for how to run Graphviz on untrusted input in a secure way, because recent versions of Graphviz give untrusted input to a whole insane rat's nest of code: Harfbuzz, Pango, libfribidi, libthai, libgraphite2, and its own internal format parser, each of which has a rap sheet of CVEs that makes Charlie Manson look like a petty shoplifter. And apparently Pango is even multithreaded, so we can expect nondeterminism. (Most of this doesn't show up in a simple ldd check; Graphviz sneakily waits until runtime to dlopen graphviz/libgvplugin_pango.so.6!) So, naturally, I wanted to sandbox it so that the worst thing a malicious attacker could do would be to make it draw Dickbutt or something. What I ended up with was less than 500 lines of code using Claude's suggestion of Bubblewrap http://canonical.org/~kragen/sw/dev3/wrapdot:

    #!/bin/sh
    # Confine dot in bubblewrap, taking input from stdin and writing PNG
    # output to stdout.

    # 64 megs seems to be enough, 21 megs isn’t.
    address_space=64001000

    # With zero --fsize, we can’t write the output file on stdout if it's
    # redirected to a file, but you can pipe it to `cat`.
    file_size=0

    cpu_seconds=5

    # Apparently Pango or fontconfig is multithreaded now‽
    # (process:2): GLib-ERROR **: 00:37:18.600: creating thread '[pango] FcInit': Error creating thread: Resource temporarily unavailable
    processes=4

    # We’re using --unshare-user, etc., explicitly, because --unshare-all
    # uses the wimpy --unshare-user-try and --unshare-cgroup-try options.
    # --remount-ro / prevents malicious code from filling the root
    # filesystem with empty files.

    exec bwrap \
          --ro-bind /bin /bin \
          --ro-bind /lib /lib \
          --ro-bind /lib64 /lib64 \
          --ro-bind /sbin /sbin \
          --ro-bind /usr/lib /usr/lib \
          --ro-bind /usr/share/fonts /usr/share/fonts \
          --ro-bind /var/cache/fontconfig /var/cache/fontconfig \
          --ro-bind /etc/fonts /etc/fonts \
          --remount-ro / \
          --unshare-user --unshare-ipc --unshare-pid --unshare-net --unshare-uts \
          --unshare-cgroup --die-with-parent --new-session --cap-drop ALL \
          --clearenv --setenv PATH /bin \
          prlimit --as="$address_space" --fsize="$file_size" \
                  --cpu="$cpu_seconds" --nproc="$processes" \
                      dot -Tpng -Gdpi=192

    # For testing, to verify that network access is indeed blocked:
    #                 nc.traditional -v -v 127.0.0.1 8000
Still, this is enough code that I'm not sure I haven't left something out. Still pending: run ImageMagick or netpbm inside the sandbox to convert the PNG file into a PPM or BMP — there have been CVEs in libpng in the past, and of course it's potentially vulnerable to zip bombs.

Of course this is still exposing most of the Linux kernel system call interface, although fortunately not /proc and /dev. And it's still fucking insane that drawing a node-link graph with three nodes requires more virtual memory than my first Linux machine had in total, in which it ran web browsers and recompiled the kernel. But that's a little further down the line.

parent kragen 26 days ago

Discussion

Live from the official Hacker News API

A modern reader for Hacker News · SvelteKit + Skeleton