%title: taming the fox %author: landry@openbsd.org %date: 2022-08-18 %comment: view with textproc/mdp -> Taming the fox <- === -> EuroBSDcon 2022, Vienna <- --- -> Landry Breuil <- --- --- -> who am i ? <- === * french guy living in the woods^Wfar from paris * sysadmin/devops in GIS field * OpenBSD developer since 16 years * Mozilla contributor since 12 years * Xfce contributor since 17 years * Xfce/Mozilla port maintainer since a while * geo/ subdir maintainer since 13 years --- -> today's topic - taming the fox <- === * eurobsdcon 2017 mozilla presentation said: 'sandboxing w/ `pledge()` ?' * WebRTC was also a challenge .. * adding `pledge()` was pretty easy at the beginning ... * ... but took some years to fully get there :) * and lots of hair pulling/scratching * learnt tons about internals and code layout * [searchfox](https://searchfox.org) was a great help --- -> Mozilla on OpenBSD, as of now <- === * ports: - `www/mozilla-firefox` 104.2.0 (105 tuesday) - `www/firefox-esr` 102.2.1 (102.3 tuesday) - `mail/mozilla-thunderbird` 102.2.2 - `www/tor-browser/browser` * *-stable* backports (when possible) since 6.1 * depends on rust, llvm, wasi-sysroot, cbindgen.. * working WebRTC, WASM, WebGL, etc... * main/content/GPU/RDD/socket(webrtc)/utility processes --- -> Sandboxing ? <- === * pledge/unveil, seccomp, capsicum, landlock... * limit what a process can do (capabilities, syscalls) * limit system resources usage * limit what a process can see about others * limit where a process can read/write * -> *prevent unwanted access to sensitive files* <- * ~/.ssh, gpg keys, /etc/master.passwd, kdbx files... --- -> Existing sandboxing in Mozilla <- === * [wiki.mo](https://wiki.mozilla.org/Security/Sandbox) * maintained upstream on Tier1 platforms * by [process type](https://firefox-source-docs.mozilla.org/dom/ipc/process_model.html) * closely linked to multiprocess work in Electrolysis/Fission * super fine-grained .. * .. but super complicated: - _MacOS_: 2k lines (trustedbsd mac) - _Win_: 3k lines - _Linux_: 10k lines (seccomp-bpf) --- -> OpenBSD: pledge()/unveil() <- === * [pledge](http://man.openbsd.org/pledge) - `pledge(promises)` - `pledge("")` -> only computation on memory shared with another process - promises can only be tightened, not widened - SIGABRT * [unveil](http://man.openbsd.org/unveil) - `unveil(path, mode)` - `unveil(NULL, NULL)` -> no more changes allowed - ENOENT/EACCESS * when to call them in the process lifecycle ? * by-process * a process cant 'know' if it has limitations --- -> pledge() promise 'classes' <- === * syscalls subsets, with limitations/exceptions (BYPASSUNVEIL) * promises used by firefox main process: - *stdio* / *rpath* / *cpath* / *wpath* / *tmppath* - *inet* / *dns* / *unix* - *recvfd* / *sendfd* / *exec* / *proc* - *ps* / *vminfo* / *prot_exec* - *flock* / *fattr* / *tty* / *drm* / *getpw* - *video* / *route* / *mcast* * more available.. - *audio* / *pf* / *wroute* / *chown* / *settime* / *id* --- -> pledge() examples in ports/base <- === * x11/gtk+3/patches/patch-gtk_updateiconcache_c pledge("stdio rpath wpath cpath fattr") * devel/desktop-file-utils/patches/patch-src_update-desktop-database_c pledge ("stdio rpath wpath cpath fattr unveil") * textproc/mupdf/patches/patch-source_tools_pdfshow_c pledge("stdio rpath") pledge("stdio") * archivers/pigz/patches/patch-pigz_c pledge("stdio rpath wpath cpath fattr chown") (or "stdio rpath cpath" if output is a pipe) * archivers/xz/patches/patch-src_xz_main_c pledge("stdio rpath wpath cpath fattr proc") (or "stdio rpath proc" or "stdio proc") * base daemons are pledged, some utilities --- -> unveil() examples in ports/base <- === * devel/desktop-file-utils/patches/patch-src_update-desktop-database_c unveil ("/usr/local/share/locale/locale.alias", "r") for (i = 0; desktop_dirs[i] != NULL; i++) unveil (desktop_dirs[i], "rwc") unveil(NULL, NULL) * misc/shared-mime-info/patches/patch-update-mime-database_c pledge("stdio rpath wpath cpath getpw unveil") unveil(mime_dir, "rwc") unveil(path, "r") * devel/got * most base daemons use `unveil()` --- -> pledge()/unveil() within firefox <- === * two files per process type, `{unveil,pledge}.{main,content,gpu,socket,rdd,utility-audioDecoder}` * defaults shipped in `/usr/local/lib//browser/defaults/preferences/` * @sampled in `/etc/firefox` * put *disable* in the first line to disable one * files read at process startup, some env vars expanded in [ExpandUnveilPath](https://searchfox.org/mozilla-central/source/dom/ipc/ContentChild.cpp#4732) - *$XDG_RUNTIME_DIR*, *$XDG_CACHE_HOME* -> `~/.cache` - *$XDG_CONFIG_HOME* -> `~/.config` - *$XDG_DATA_HOME* -> `~/.local/share` - *~* -> `/home/` --- -> unveil() examples in firefox <- === # bits of unveil.main $XDG_CACHE_HOME/mozilla/firefox rwc ~/.mozilla/firefox rwc /dev/dri/card0 rw /dev/video0 rw /usr/local/lib r /usr/local/lib/firefox rx /usr/local/bin/mupdf rx --- -> when to call pledge()/unveil() in a program <- === * as close as possible from the mainloop * open devices early, pass file descriptors * in theory... practice is different * cant easily move/'hoist' large chunks of code * codebase is ginormous * in the end -> initialized at the same spot as other sandboxes * preload some libs to avoid the need to unveil() them, idea inherited from windows * key entrypoints: - [StartOpenBSDSandbox](https://searchfox.org/mozilla-central/source/dom/ipc/ContentChild.cpp#4869) - [OpenBSDFindPledgeUnveilFilePath](https://searchfox.org/mozilla-central/source/dom/ipc/ContentChild.cpp#4660) - [OpenBSDPledgePromises](https://searchfox.org/mozilla-central/source/dom/ipc/ContentChild.cpp#4680) - [OpenBSDUnveilPaths](https://searchfox.org/mozilla-central/source/dom/ipc/ContentChild.cpp#4799) --- -> per-process init <- === * `main`: [AddSandboxAnnotations](https://searchfox.org/mozilla-central/source/toolkit/xre/nsAppRunner.cpp#5237) * `content`: [ContentChild::Init](https://searchfox.org/mozilla-central/source/dom/ipc/ContentChild.cpp#779) * `GPU`: [GPUProcessImpl::Init](https://searchfox.org/mozilla-central/source/gfx/ipc/GPUProcessImpl.cpp#29) * `RDD`: [RDDProcessImpl::Init](https://searchfox.org/mozilla-central/source/dom/media/ipc/RDDProcessImpl.cpp#34) * `socket`: [SocketProcessImpl::Init](https://searchfox.org/mozilla-central/source/netwerk/ipc/SocketProcessImpl.cpp#57) * `utility`: [UtilityProcessImpl::Init](https://searchfox.org/mozilla-central/source/ipc/glue/UtilityProcessImpl.cpp#56) * even the [doc](https://searchfox.org/mozilla-central/source/ipc/docs/processes.rst#653) mentions OpenBSD sandboxing :) --- -> incremental workflow to refine promises/unveiled paths <- === * use `ktrace` to figure out: - syscalls used -> pledge classes - files accessed -> unveil paths & access level * repeat until it stops crashing^Wworks * isolate access/try various things * beware of codepaths used by underlying libs (Gtk, X...) --- -> bugs/upstreaming <- === * #1457092 [Implement sandboxing on OpenBSD with pledge()](https://bugzilla.mozilla.org/show_bug.cgi?id=1457092) (63) * #1466593 [sandboxing prevents content process to spawn a session dbus](https://bugzilla.mozilla.org/show_bug.cgi?id=1466593) (64) * #1580268 [sandbox GPU process on OpenBSD with pledge()](https://bugzilla.mozilla.org/show_bug.cgi?id=1580268) (72) * #1580271 [enhance sandbox on OpenBSD with unveil()](https://bugzilla.mozilla.org/show_bug.cgi?id=1580271) (69) * #1584839 [move OpenBSD pledge() promises to files](https://bugzilla.mozilla.org/show_bug.cgi?id=1584839) (72) * #1596546 [disable cubeb lazy dlopening on OpenBSD to fix sound when sandboxed](https://bugzilla.mozilla.org/show_bug.cgi?id=1596546) (72) * #1623086 [lost middle and right-click in webpages in 75](https://bugzilla.mozilla.org/show_bug.cgi?id=1623086) * #1696958 [File downloads failing with sandboxing](https://bugzilla.mozilla.org/show_bug.cgi?id=1696958) --- -> moar bugs/upstreaming <- === * #1702919 [fallback to ximage for screensharing on openbsd to prevent a sandboxing violation](https://bugzilla.mozilla.org/show_bug.cgi?id=1702919) * #1713745 [enable RDD process on OpenBSD, sandbox it with pledge/unveil](https://bugzilla.mozilla.org/show_bug.cgi?id=1713745) (91) * #1713999 [Sandbox the socket process on OpenBSD with pledge/unveil](https://bugzilla.mozilla.org/show_bug.cgi?id=1713999) * #1714018 [fix dconf interaction with XDG_RUNTIME_DIR on OpenBSD](https://bugzilla.mozilla.org/show_bug.cgi?id=1714018) (91) * #1714919 [mime handler spawning with glib >= 2.64 badly interacting with OpenBSD sandboxing](https://bugzilla.mozilla.org/show_bug.cgi?id=1714919) * #1769033 [Add OpenBSD sandboxing for Utility AudioDecoder](https://bugzilla.mozilla.org/show_bug.cgi?id=1769033) (102) * #1770388 [Enable Utility Audio Decoder on Nightly for OpenBSD](https://bugzilla.mozilla.org/show_bug.cgi?id=1770388) (102) * #1790419 [hoist mozilla::BinaryPath::Get before OpenBSD sandboxing](https://bugzilla.mozilla.org/show_bug.cgi?id=1790419) --- -> runtime experience for users <- === * mostly transparent, no overhead * by default, only `/tmp` & `~/Downloads` to save/load files * need to disable pledge on main process for webrtc screen sharing ( `shmget()`) * need to add mime handlers to `unveil.main` as explained in `/usr/local/share/doc/pkg-readmes/firefox` /usr/local/bin/xarchiver rx /usr/local/bin/mupdf rx /usr/local/bin/ristretto rx /usr/local/bin/soffice rx --- -> key points <- === * all processes need *stdio* / *sendfd* / *recvfd* / *rpath* - `main` & `content` still have many things - `RDD` only needs *tmppath* / *unix*, */tmp rwc* -> no read access to anything else - `socket` only needs *inet* / *dns* (and no unveils, but for now only used for webrtc) - `audioDecoder` needs *tmppath*, *prot_exec*, *unix*, */tmp rwc* and read access to libs - `GPU` is weird, really used ? * `unveil` quirks wrt dir creation ( `~/Downloads` ) * external handlers spawned by `main` * could definitely be improved ... and everything should be revisited at each upgrade ? --- -> conclusion <- === * `pledge()` enabled by default since firefox 60 in may 2018 * `unveil()` enabled by default since firefox 71 in december 2019 * all upstreamed ! * just works, but somewhat wide promises ? * codebase not written with sandboxing in mind from the start makes it very hard * things being slowly moved out to other processes (socket) ? * a bit 'raw', eg either killed or UB/crashes - hard to debug ? * `ktrace` is your sole friend * `MOZ_LOG=OpenBSDSandbox:5` in the env prints unveil/pledge calls at process startup --- -> questions ? <- === -> Thx to EuroBSDcon organizers ! <- ---