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

Reply via email to