-
-
Notifications
You must be signed in to change notification settings - Fork 794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SDL: Add option to drop privileges with unveil()/pledge() #1271
base: master
Are you sure you want to change the base?
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 |
@@ -149,6 +154,15 @@ int main(int argc, char** argv) { | |||
renderer.player.bindings = &renderer.core->inputMap; | |||
mSDLInitBindingsGBA(&renderer.core->inputMap); | |||
mSDLInitEvents(&renderer.events); | |||
|
|||
#ifdef USE_PLEDGE | |||
if (!mPledgeBroad(&args)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave this another try.
src/platform/sdl/main.c
Outdated
@@ -264,6 +278,12 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { | |||
state->close(state); | |||
} | |||
} | |||
#ifdef USE_PLEDGE | |||
if (!mPledgeNarrow(args)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that’s the purpose—pledge is intended to ratchet down permissions before entering the main loop.
dns
and unix
are needed for audio:
unix
is needed when the device is a local sndiod(8) sub-device.inet
anddns
are 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
audio
promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything after the runloop except for mCoreThreadHasCrashed
still needs to be called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty close this time. Just some minor stuff and one less minor thing.
src/platform/sdl/main.c
Outdated
@@ -264,6 +278,12 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { | |||
state->close(state); | |||
} | |||
} | |||
#ifdef USE_PLEDGE | |||
if (!mPledgeNarrow(args)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything after the runloop except for mCoreThreadHasCrashed
still needs to be called.
@@ -489,6 +496,10 @@ find_feature(USE_SQLITE3 "sqlite3") | |||
find_feature(USE_ELF "libelf") | |||
find_feature(ENABLE_PYTHON "PythonLibs") | |||
|
|||
if(USE_PLEDGE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot to delete it here.
renderer.core->deinit(renderer.core); | ||
mSDLDeinitEvents(&renderer.events); | ||
mSDLDeinit(&renderer); | ||
fputs("Broad pledge() failed\n", stderr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fputs
already appends the \n
.
mCoreConfigDeinit(&renderer.core->config); | ||
mInputMapDeinit(&renderer.core->inputMap); | ||
renderer.core->deinit(renderer.core); | ||
mSDLDeinitEvents(&renderer.events); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto about the \n
.
pledge()
andunveil()
are two OpenBSD APIs to restrict the capabilities of a program.pledge
whitelists the set of syscalls the program can call, whileunveil
whitelists a view of the filesystem to the program.pledge
can 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.unveil
is 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 inENOENT
orEACCES
. The kernel’s method of keeping track of these files is somewhat expensive, so typical practice is to callunveil
as 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
-d
usage (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?