Skip to content
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

Multiple binary support api #196

Merged
merged 16 commits into from
Oct 5, 2024
Merged

Multiple binary support api #196

merged 16 commits into from
Oct 5, 2024

Conversation

ChrisLovering
Copy link
Member

@ChrisLovering ChrisLovering commented Oct 10, 2023

This allows setting a binary_path key in the request body to /eval to specify which binary to run under.

If not supplied, it defaults to "/snekbin/python/default/bin/python".

0c9b234 is needed as previously we were dropping the text after the last period when creating the path to install Python to. However, for versions such as3.13-dev this would result in it being installed into /snekbin/3. Instead, we now split on the last . or the last -

@ChrisLovering ChrisLovering changed the title Multi version api Multiple binary support api Oct 10, 2023
@ChrisLovering ChrisLovering force-pushed the multi-version-api branch 3 times, most recently from 1b512b8 to 5f67817 Compare October 10, 2023 13:49
@coveralls
Copy link

coveralls commented Oct 10, 2023

Coverage Status

coverage: 89.158% (-1.7%) from 90.826%
when pulling b58f98b on multi-version-api
into b276a04 on main.

Copy link
Member

@MarkKoz MarkKoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add something in the readme about multiple version support? Can just be a blurb I think.

if binary_path:
binary_path = Path(binary_path)
if (
not binary_path.resolve().as_posix().startswith("/lang/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why restrict it to just files in that directory? It seems like an arbitrary restriction I don't think we should have.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I missed this in your description

if supplied, snekbox ensures that the path is within /lang (to avoid executing arbitrary binaries on the fs) and that the file exists.

It is fairly trivial to bypass this restriction by using the execve syscall. We cannot block this syscall either (at least not through nsjail's seccomp filters). That is due to a quirk in which the filter is applied before nsjail's own execve call, causing nsjail to commit suicide.

Granted, there may be use cases which don't have binaries in /lang that make it possible to call syscalls. But I think if a request comes up to have such restriction, we can add it as a later feature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not binary_path.resolve().as_posix().startswith("/lang/")
or not binary_path.is_file()
):
raise falcon.HTTPBadRequest(title="binary_path file is invalid")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be more specific. We should also check that the file exists if we aren't already (and that should be a distinct error message)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""
if not binary_path:
binary_path = "/lang/python/default/bin/python"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's set the default in the function parameters instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to do something slightly idfferent so that hte default cna be used both by calling python3 directly, and when calling the API 66c6d27

@@ -188,7 +189,12 @@ def python3(
py_args: Arguments to pass to Python.
files: FileAttachments to write to the sandbox prior to running Python.
nsjail_args: Overrides for the NsJail configuration.
binary_path: The path to the binary to execute under.
If None, defaults to "/lang/python/default/bin/python"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should rename /lang since it's going to be more prominent now. Maybe /snekbin? — doubles as a kinda clever play on "snekbox". If so, we also have to rename our references to the path in our CONTRIBUTING.md docs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree this is a good change f726695

"""Test that passing invalid binary paths result in no code execution."""
with run_gunicorn():
cases = [
("/bin/bash", "test files outside of /lang cannot be ran"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Suggested change
("/bin/bash", "test files outside of /lang cannot be ran"),
("/bin/bash", "test files outside of /lang cannot be run"),

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"/lang/../bin/bash",
"test path traversal still stops files outside /lang from running",
),
("/foo/bar", "test non-existant files are not ran"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Suggested change
("/foo/bar", "test non-existant files are not ran"),
("/foo/bar", "test non-existant files are not run"),

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChrisLovering ChrisLovering force-pushed the multi-version-api branch 2 times, most recently from a784d78 to 6dcf094 Compare January 24, 2024 20:54
@ChrisLovering
Copy link
Member Author

I plan to pick this PR up soon just had time to rebase this evening

Copy link
Member

@jchristgit jchristgit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool!!!

binary_path = Path(binary_path)
if (
not binary_path.resolve().as_posix().startswith("/lang/")
or not binary_path.is_file()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check that it's executable?

Suggested change
or not binary_path.is_file()
or not binary_path.is_file()
or not binary_path.stat().st_mode & 0o100 == 0o100

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! 81573b2

if binary_path:
binary_path = Path(binary_path)
if (
not binary_path.resolve().as_posix().startswith("/lang/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move the resolve().as_posix() calls above so the pathlib calls below can make use of it? Or do they mutate the internal object (source of eldritch horrors)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed this func quite a bit since then, so this is no longer relevant

@HassanAbouelela
Copy link
Member

Hey how's this PR going? It would be very useful for a project I'm working on.

@ChrisLovering ChrisLovering mentioned this pull request Oct 3, 2024
@ChrisLovering ChrisLovering changed the base branch from main to py-3.13-rc October 3, 2024 21:03
This was needed due to wanting a default value when calling python3 diurectly, but also when not specified via the API call
@ChrisLovering
Copy link
Member Author

Now ready for (re)review

@ChrisLovering ChrisLovering marked this pull request as ready for review October 3, 2024 21:08
Base automatically changed from py-3.13-rc to main October 3, 2024 22:33
README.md Outdated Show resolved Hide resolved
tests/test_integration.py Outdated Show resolved Hide resolved
tests/test_integration.py Outdated Show resolved Hide resolved
@@ -43,6 +44,7 @@ class EvalResource:
"required": ["path"],
},
},
"binary_path": {"type": "string"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "executable_path" is a better name than "binary_path"? I'm also okay with the latter though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree, makes it clearer what is supported here, as it's not limitted to just binaries. edcaed6

Copy link
Member

@MarkKoz MarkKoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@ChrisLovering ChrisLovering merged commit b58f98b into main Oct 5, 2024
8 checks passed
@ChrisLovering ChrisLovering deleted the multi-version-api branch October 5, 2024 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants