SDL: Add option to drop privileges with unveil()/pledge()#1271
SDL: Add option to drop privileges with unveil()/pledge()#1271bentley wants to merge 5 commits intomgba-emu:masterfrom
Conversation
8ba3c7b to
e74e5de
Compare
|
New revision, pulling in the savestate path the same way savestate writes do. (Kosher?) |
|
New commit will fail with other VDir types, like ZIP files. |
|
I expected that, but was surprised to find it actually worked for zip files. |
|
That's not reliable though, it just happens to be that the struct elements were in the right order for that to work out. |
e74e5de to
9a6bab0
Compare
|
Bummer… reverted. So hooking the VDir creation routines will be the next step. |
|
Clearly I’m not going to get around to finishing unveil support soon… however, the pledge support works well, and has been part of the OpenBSD package for over a year, including the last three OpenBSD releases. So let’s reduce the scope of this pull request to pledge’s syscall filtering, which is ready to go in. What are your thoughts? |
|
I'll take a more in-depth look tomorrow (it's 4 AM here, I shouldn't still be awake) but you should probably start by restricting the status output and checks in CMake to just being on OSes that have it (OpenBSD and...SerenityOS I think?) |
|
I think SerenityOS shows up as “Generic” in CMake and isn’t otherwise detectable, but I don’t know for sure. Because of that I only gated pledge() support behind a variable that defaults to off. I can change that to |
| mSDLInitEvents(&renderer.events); | ||
|
|
||
| #ifdef USE_PLEDGE | ||
| if (!mPledgeBroad(&args)) { |
There was a problem hiding this comment.
Is there any reason this isn't done immediately after generating the args struct? There's a lot of stuff you're not freeing or deiniting here.
There was a problem hiding this comment.
Joystick initialization calls some USB ioctls that are not allowed by any pledge, so this is as early as the pledge call can go.
There was a problem hiding this comment.
I gave this another try.
src/platform/sdl/main.c
Outdated
| } | ||
| } | ||
| #ifdef USE_PLEDGE | ||
| if (!mPledgeNarrow(args)) { |
There was a problem hiding this comment.
What's the purpose of this second, narrower call? To drop further privs after everything is initialized? It looks like all you drop is dns and unix, and I'm not sure what those are used for in the first place. SDL I guess. Note that the runloop will still get called if this fails.
Also the indentation doesn't match.
There was a problem hiding this comment.
Yes, that’s the purpose—pledge is intended to ratchet down permissions before entering the main loop.
dns and unix are needed for audio:
unixis needed when the device is a local sndiod(8) sub-device.inetanddnsare needed when the device is a remote sndiod(8) sub-device.Once no further calls to sio_open() will be made, all these pledge(2) promises may be dropped, except for the
audiopromise.
There was a problem hiding this comment.
Is the right thing here to return didFail right away and let the caller deinit everything?
Edit:
Narrow pledge() failed
Segmentation fault (core dumped)
Apparently not.
There was a problem hiding this comment.
Everything after the runloop except for mCoreThreadHasCrashed still needs to be called.
endrift
left a comment
There was a problem hiding this comment.
Pretty close this time. Just some minor stuff and one less minor thing.
src/platform/sdl/main.c
Outdated
| } | ||
| } | ||
| #ifdef USE_PLEDGE | ||
| if (!mPledgeNarrow(args)) { |
There was a problem hiding this comment.
Everything after the runloop except for mCoreThreadHasCrashed still needs to be called.
| find_feature(USE_ELF "libelf") | ||
| find_feature(ENABLE_PYTHON "PythonLibs") | ||
|
|
||
| if(USE_PLEDGE) |
| renderer.core->deinit(renderer.core); | ||
| mSDLDeinitEvents(&renderer.events); | ||
| mSDLDeinit(&renderer); | ||
| fputs("Broad pledge() failed\n", stderr); |
| mCoreConfigDeinit(&renderer.core->config); | ||
| mInputMapDeinit(&renderer.core->inputMap); | ||
| renderer.core->deinit(renderer.core); | ||
| mSDLDeinitEvents(&renderer.events); |
There was a problem hiding this comment.
mSDLDeinitEvents is called by mSDLDeinit, you don't need to call it yourself. mCoreConfigFreeOpts should be called though.
| #ifdef USE_PLEDGE | ||
| if (!mPledgeNarrow(args)) { | ||
| didFail = true; | ||
| fputs("Narrow pledge() failed\n", stderr); |
pledge()andunveil()are two OpenBSD APIs to restrict the capabilities of a program.pledgewhitelists the set of syscalls the program can call, whileunveilwhitelists a view of the filesystem to the program.pledgecan be called repeatedly to reduce the list of allowed syscalls even further. This patch calls pledge twice, initially with a very broad list and finally with"stdio rpath wpath cpath fattr"(for savestates)"drm"(for graphics)"audio"(for audio). The debuggers additionally need"tty"(-d) and"net"(-g). The kernel kills a program that violates its promises. Obviously if that ever happens, the arguments to pledge need to be fixed.unveilis called once per file or directory to whitelist, with a specified permission (read/write/execute/create), and a final call with null arguments to prevent further whitelisting. Attempts to access anything not unveiled result inENOENTorEACCES. The kernel’s method of keeping track of these files is somewhat expensive, so typical practice is to callunveilas late as possible with as few files as possible. Here I only pass it the savestates.Libepoxy is not compatible with these pledges; its deferred
dlopen()calls cause the the program to be killed onglDeleteTextures(). I disabled it conditionally in CMake.I’ve tested normal gameplay, USB gamepad, networked sndio audio, and basic
-dusage (read: started program and hit “c”).Questions:
Right now only the base names of the savestates are unveiled, so they only work if the ROM is in$PWD. How do I pass the directory through to the SDL frontend?