Hi, The diff below adds pledge to mgba, a Nintendo Game Boy Advance emulator. mgba supports keyboard or USB joystick input, and uses OpenGL and sndio (via SDL) for output.
By nature emulators parse and execute untrusted input (game programs and save files), and run for long periods of time. I can think of a handful of POCs the last few years that specifically targeted emulators. I use these programs quite a lot and would like to reduce their ability to harm my system. Upstream fuzzes mgba (https://mgba.io/2016/09/13/fuzzing-emulators/). A lot of the bugs found this way happened post-initialization, while loading savestates (done on-demand as the user plays). So I would definitely like to have this pledge that restricts the main emulation loop. Under normal circumstances, mgba has a final promise of "stdio rpath wpath cpath fattr drm audio". The file I/O is necessary because mgba creates, reads, and writes save files on demand; drm and audio are self-explanatory. Two (mutually exclusive) command-line options necessitate additional promises. -d (built-in debugger) uses libedit, which calls various ioctls and needs "tty"; -g starts a gdb stub on port 2345, and needs "net". For a while I thought prot_exec was necessary. But it turns out mgba's optional dependency libepoxy was dlopen-ing libEGL when mgba calls glDeleteTextures on exit. I turned libepoxy off and dropped the prot_exec promise. unveil would be really nice. I had a proof-of-concept that unveiled just the nine savestate files, but it dug around in the internals too much (mgba's virtual directory system doesn't expose dirnames), so I dropped it. I've been discussing options with upstream, but doing it in a clean (read: upstreamable) way will require significant plumbing. I will just stick to pledge for now. I've tested this on radeondrm, inteldrm, and software rendering, with local and networked sndio audio, with USB gamepads, and with the internal debugger. Index: Makefile =================================================================== RCS file: /cvs/ports/emulators/mgba/Makefile,v retrieving revision 1.27 diff -u -p -r1.27 Makefile --- Makefile 16 Nov 2018 06:03:26 -0000 1.27 +++ Makefile 21 Jan 2019 00:58:27 -0000 @@ -9,7 +9,7 @@ DISTNAME = mgba-$V PKGNAME-main = mgba-$V PKGNAME-qt = mgba-qt-$V PKGNAME-libretro = libretro-mgba-$V -REVISION-main = 0 +REVISION-main = 1 REVISION-qt = 2 MULTI_PACKAGES = -main -qt -libretro @@ -53,7 +53,8 @@ LIB_DEPENDS-libretro = RUN_DEPENDS-qt = devel/desktop-file-utils \ x11/gtk+3,-guic -CONFIGURE_ARGS += -DBUILD_LIBRETRO=ON +CONFIGURE_ARGS += -DBUILD_LIBRETRO=ON \ + -DUSE_PLEDGE_UNVEIL=ON .if ${BUILD_PACKAGES:M-qt} MODULES += x11/qt5 Index: patches/patch-CMakeLists_txt =================================================================== RCS file: /cvs/ports/emulators/mgba/patches/patch-CMakeLists_txt,v retrieving revision 1.1 diff -u -p -r1.1 patch-CMakeLists_txt --- patches/patch-CMakeLists_txt 16 Nov 2018 06:01:48 -0000 1.1 +++ patches/patch-CMakeLists_txt 21 Jan 2019 00:58:27 -0000 @@ -3,67 +3,40 @@ $OpenBSD: patch-CMakeLists_txt,v 1.1 201 Support libswresample. From upstream 0933bca85da256adb066e11b1e2b4d3a2d0f1bcb. +Add option to drop privileges with pledge(). +From https://github.com/mgba-emu/mgba/pull/1271. + Index: CMakeLists.txt --- CMakeLists.txt.orig +++ CMakeLists.txt -@@ -415,7 +415,7 @@ set(WANT_LIBZIP ${USE_LIBZIP}) - set(WANT_SQLITE3 ${USE_SQLITE3}) - set(USE_CMOCKA ${BUILD_SUITE}) +@@ -18,6 +18,7 @@ if (NOT WIN32) + set(USE_EDITLINE ON CACHE BOOL "Whether or not to enable the CLI-mode debugger") + endif() + set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger") ++set(USE_PLEDGE_UNVEIL OFF CACHE BOOL "Whether or not to drop privileges with pledge and unveil") + set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support") + set(USE_ZLIB ON CACHE BOOL "Whether or not to enable zlib support") + set(USE_MINIZIP ON CACHE BOOL "Whether or not to enable external minizip support") +@@ -456,6 +457,14 @@ if(USE_GDB_STUB) + endif() + source_group("Debugger" FILES ${DEBUGGER_SRC}) --find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") -+find_feature(USE_FFMPEG "libavcodec;libavformat;libavutil;libswscale") - find_feature(USE_ZLIB "ZLIB") - find_feature(USE_MINIZIP "minizip") - find_feature(USE_PNG "PNG") -@@ -425,6 +425,13 @@ find_feature(USE_EPOXY "epoxy") - find_feature(USE_CMOCKA "cmocka") - find_feature(USE_SQLITE3 "sqlite3") - -+if(USE_FFMPEG) -+ set(USE_LIBAVRESAMPLE OFF) -+ set(USE_LIBSWRESAMPLE ON) -+ find_feature(USE_LIBAVRESAMPLE "libavresample") -+ find_feature(USE_LIBSWRESAMPLE "libswresample") ++if(USE_PLEDGE_UNVEIL) ++ set(USE_EPOXY OFF) ++endif() ++ ++if(USE_PLEDGE_UNVEIL) ++ list(APPEND FEATURES PLEDGE_UNVEIL) +endif() + - # Features - set(DEBUGGER_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/debugger.c -@@ -458,22 +465,30 @@ source_group("Debugger" FILES ${DEBUGGER_SRC}) - if(USE_FFMPEG) list(APPEND FEATURES FFMPEG) -- pkg_search_module(LIBSWRESAMPLE QUIET libswresample) -- if(NOT LIBSWRESAMPLE_FOUND) -+ if(USE_LIBSWRESAMPLE) -+ list(APPEND FEATURES LIBSWRESAMPLE) -+ else() -+ list(APPEND FEATURES LIBAVRESAMPLE) - list(APPEND FEATURES LIBAV) + pkg_search_module(LIBSWRESAMPLE QUIET libswresample) +@@ -950,6 +959,7 @@ if(NOT QUIET) + message(STATUS " CLI debugger: ${USE_EDITLINE}") endif() -- include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS}) -- link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS}) -+ include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS}) -+ link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS}) - list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c") - string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION}) -- string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION}) -- list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES}) -+ list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES}) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}") - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}") -- set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}") -+ if(USE_LIBSWRESAMPLE) -+ string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION}) -+ math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") -+ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}") -+ else() -+ string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) -+ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}") -+ endif() - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavutil${LIBAVUTIL_VERSION_MAJOR}|libavutil-ffmpeg${LIBAVUTIL_VERSION_MAJOR}") - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswscale${LIBSWSCALE_VERSION_MAJOR}|libswscale-ffmpeg${LIBSWSCALE_VERSION_MAJOR}") - set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}") + message(STATUS " GDB stub: ${USE_GDB_STUB}") ++ message(STATUS " pledge/unveil: ${USE_PLEDGE_UNVEIL}") + message(STATUS " Video recording: ${USE_FFMPEG}") + message(STATUS " GIF recording: ${USE_MAGICK}") + message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}") Index: patches/patch-src_platform_sdl_main_c =================================================================== RCS file: patches/patch-src_platform_sdl_main_c diff -N patches/patch-src_platform_sdl_main_c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-src_platform_sdl_main_c 21 Jan 2019 00:58:27 -0000 @@ -0,0 +1,93 @@ +$OpenBSD$ + +Add option to drop privileges with pledge(). +From https://github.com/mgba-emu/mgba/pull/1271. + +Index: src/platform/sdl/main.c +--- src/platform/sdl/main.c.orig ++++ src/platform/sdl/main.c +@@ -36,6 +36,11 @@ static void mSDLDeinit(struct mSDLRenderer* renderer); + + static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args); + ++#ifdef USE_PLEDGE_UNVEIL ++static bool mPledgeBroad(struct mArguments* args); ++static bool mPledgeNarrow(struct mArguments* args); ++#endif ++ + int main(int argc, char** argv) { + struct mSDLRenderer renderer = {0}; + +@@ -137,6 +142,15 @@ int main(int argc, char** argv) { + renderer.player.bindings = &renderer.core->inputMap; + mSDLInitBindingsGBA(&renderer.core->inputMap); + mSDLInitEvents(&renderer.events); ++ ++#ifdef USE_PLEDGE_UNVEIL ++ if (!mPledgeBroad(&args)) { ++ freeArguments(&args); ++ printf("pledge\n"); ++ return 1; ++ } ++#endif ++ + mSDLEventsLoadConfig(&renderer.events, mCoreConfigGetInput(&renderer.core->config)); + mSDLAttachPlayer(&renderer.events, &renderer.player); + mSDLPlayerLoadConfig(&renderer.player, mCoreConfigGetInput(&renderer.core->config)); +@@ -207,6 +221,12 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArg + mSDLSuspendScreensaver(&renderer->events); + #endif + if (mSDLInitAudio(&renderer->audio, &thread)) { ++#ifdef USE_PLEDGE_UNVEIL ++ if (!mPledgeNarrow(args)) { ++ didFail = true; ++ printf("pledge\n"); ++ } ++#endif + renderer->runloop(renderer, &thread); + mSDLPauseAudio(&renderer->audio); + if (mCoreThreadHasCrashed(&thread)) { +@@ -250,3 +270,43 @@ static void mSDLDeinit(struct mSDLRenderer* renderer) + + SDL_Quit(); + } ++ ++#ifdef USE_PLEDGE_UNVEIL ++static bool mPledgeBroad(struct mArguments *args) { ++ if (args->debuggerType == DEBUGGER_CLI) { ++ if (pledge("stdio rpath wpath cpath inet fattr unix dns tty drm audio", NULL) == -1) { ++ return false; ++ } ++#ifdef USE_GDB_STUB ++ } else if (args->debuggerType == DEBUGGER_GDB) { ++ if (pledge("stdio rpath wpath cpath inet fattr unix dns drm audio", NULL) == -1) { ++ return false; ++ } ++#endif ++ } else { ++ if (pledge("stdio rpath wpath cpath inet fattr unix dns drm audio", NULL) == -1) { ++ return false; ++ } ++ } ++ return true; ++} ++ ++static bool mPledgeNarrow(struct mArguments *args) { ++ if (args->debuggerType == DEBUGGER_CLI) { ++ if (pledge("stdio rpath wpath cpath fattr tty drm audio", NULL) == -1) { ++ return false; ++ } ++#ifdef USE_GDB_STUB ++ } else if (args->debuggerType == DEBUGGER_GDB) { ++ if (pledge("stdio rpath wpath cpath inet fattr drm audio", NULL) == -1) { ++ return false; ++ } ++#endif ++ } else { ++ if (pledge("stdio rpath wpath cpath fattr drm audio", NULL) == -1) { ++ return false; ++ } ++ } ++ return true; ++} ++#endif