Conversation
|
Can one of the admins verify this patch? |
0c95e3d to
394de48
Compare
Codecov ReportBase: 81.44% // Head: 84.17% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #903 +/- ##
==========================================
+ Coverage 81.44% 84.17% +2.72%
==========================================
Files 137 137
Lines 4920 4953 +33
==========================================
+ Hits 4007 4169 +162
+ Misses 913 784 -129
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
|
ok to test |
e2644de to
7f33dfa
Compare
|
Landlock applies |
1bdd49c to
7c5316f
Compare
|
Given that stuff is now functional, we can begin to review this. |
93f63b4 to
81d4bb3
Compare
Would this also mean we can do away with some of the header files that we copied? |
|
No, we need them to be able to do the initial Landlock calls to figure out if Landlock is supported at all. |
A helper header is provided so as to avoid issues with missing headers, so we can compile unconditionally.
a2c7e1e to
e8f4ed0
Compare
One key note to make here is that WRITE permissions imply READ permissions, because otherwise, calls with O_RDWR will fail even if one rule grants read and another grants write. Another thing to note is that under landlock, only the main process can access /proc. This is because we can't add rules when the children spawn.
Note that under the current version of landlock, some syscalls are not handled by landlock, including `stat` and `access`.
Under landlock, linking and renaming throw EXDEV if `src` and `dst` are not in the same directory. This is unacceptable for us, so we simulate the calls. Because we are simulating, we want to make sure that `flags` is zero for `linkat`, otherwise it's likely that we have done something unintended.
Under landlock, since /proc is not accessible in a subprocess, SCALA calls `mincore`. We allow this in order for it to pass. Also, since `execve` is checked under landlock, we need to add `/bin` to the list of readable directories.
de66427 to
d20bd11
Compare
|
|
||
| int landlock_version = get_landlock_version(); | ||
| if (landlock_version < 2) { | ||
| // ABI not old enough. Skip to seccomp |
There was a problem hiding this comment.
| // ABI not old enough. Skip to seccomp | |
| // ABI not new enough. Skip to seccomp |
| LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_READ_DIR | | ||
| LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SYM | LANDLOCK_ACCESS_FS_MAKE_DIR | | ||
| LANDLOCK_ACCESS_FS_REFER)) { | ||
| // landlock_add_rules logs errors |
There was a problem hiding this comment.
Could this comment have more details? I don't understand what it's referring to, is this comment unrelated to the following close?
| return 0; | ||
| return -1; | ||
| #else | ||
| return 0; // FreeBSD does not have landlock |
There was a problem hiding this comment.
How? Consumers of this function throw a python error, but what would be appropriate to do here?
There was a problem hiding this comment.
Got it. I think has_landlock should be implemented here rather than in Python, so we can return false order FREEBSD.
| #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) | ||
| #endif /* _LINUX_LANDLOCK_H */ | ||
|
|
||
| // Not always defined, depends on ABI version. |
| @@ -0,0 +1,3 @@ | |||
| #include "landlock_header.h" | |||
|
|
|||
| int landlock_add_rules(const int ruleset_fd, const char **paths, __u64 access_rule); | |||
There was a problem hiding this comment.
Could we name this cptbox_landlock_add_rules? Makes it easier to keep track of what's provided by the kernel versus by us.
|
|
||
| if (rule.parent_fd < 0) { | ||
| if (errno == ENOENT) | ||
| goto close_fd; // missing files are ignored |
There was a problem hiding this comment.
Nit: parens even around single-statement branches / loops / etc. I know not all cptbox code is written like this, but all new code should be.
| if security is not None: | ||
| self.configure_files(security.read_fs, security.write_fs) | ||
| else: | ||
| self.configure_files([], []) |
There was a problem hiding this comment.
Is this branch necessary at all? Seems a little weird to have.
There was a problem hiding this comment.
Unsure. security defaults to None. I don't know why.
There was a problem hiding this comment.
My question was more: it seems weird that the default uninitialized state would be different from initializing with [].
There was a problem hiding this comment.
We could maybe avoid it by having a default of empty list in _cptbox.pyx, but I don't see how else to avoid it. configure_files is the function that sets the landlock_* attributes.
We could, in theory, have them defaulted to empty and then have this branch to set them to something, but I don't love that.
| elif self.returncode == PTBOX_SPAWN_FAIL_SETAFFINITY: | ||
| raise RuntimeError('failed to set child affinity') | ||
| elif self.returncode == PTBOX_SPAWN_FAIL_LANDLOCK: | ||
| raise RuntimeError('landlock configuration failed') |
There was a problem hiding this comment.
| raise RuntimeError('landlock configuration failed') | |
| raise RuntimeError('Landlock configuration failed') |
Nit, here and in other user-facing strings as well as comments. The kernel docs refer to it as "Landlock" (despite "seccomp" being lowercased).
| ExactFile('/bin/bash'), | ||
| RecursiveDir('/etc/alternatives'), | ||
| ] | ||
| compiler_syscalls = ['mincore'] |
There was a problem hiding this comment.
SCALA takes a different path under landlock last I checked, and that path requires mincore. I believe it had to do with how landlock deals with /proc/<pid>.
We decided at some point to simply allow the call.
There was a problem hiding this comment.
Ack, thanks for explaining. Could we split this out in its own commit?
There was a problem hiding this comment.
Eh, I guess it kind of is already. Fine to leave it as-is.
| int get_landlock_version() { | ||
| #if !PTBOX_FREEBSD | ||
| char *sandbox_mode = getenv("DMOJ_SANDBOX_MODE"); | ||
| if (sandbox_mode != nullptr && strcmp(sandbox_mode, "ptrace+seccomp") == 0) { |
There was a problem hiding this comment.
This is a surprising place for this check to live, and I think the check should be stricter than this.
We should validate that the string is one of:
ptrace+seccompptrace+seccomp+landlockauto
and otherwise bail out. Otherwise we can't confidently make changes to the interpretation of this environment variable, as we'd have been too lax in its inputs in earlier versions.
There was a problem hiding this comment.
I agree that this location is strange. Where should it live?
There was a problem hiding this comment.
I think living in this file is probably fine, and it should return an enum. Then it should be consulted in the spawn routine alongside the Landlock version we have available. My concern here is more about subtly overloading the semantics of get_landlock_version than anything else.
We should bail if someone requests:
- seccomp or seccomp+landlock support on FreeBSD
- ptrace-only support on Linux
- seccomp+landlock on a Linux with too old a Landlock ABI
This sanity check could exist in judge.py alongside all the other sanity checks, but I don't feel strongly about that.
| int *seccomp_handlers; | ||
| // 64 cores ought to be enough for anyone. | ||
| unsigned long cpu_affinity_mask; | ||
| const char **landlock_read_exact_files; |
There was a problem hiding this comment.
This may end up looking a little cleaner if we have a
struct {
const char **read_exact_files;
...
} landlock;or even
const struct {
char **read_exact_files;
...
} landlock;| assert self._executable is not None | ||
| # Under landlock we need this for execve to work. | ||
| # We use `self._executable` because it is copied when caching executors, but other properties are not. | ||
| return super().get_fs() + [ExactFile(self._executable)] |
There was a problem hiding this comment.
The fact that both get_executable and get_compiled_file exist but return possibly different values is an abomination, and we should fix this. You don't have to do so in this PR, but please leave a comment to the effect of this being a hack to work around that.
There was a problem hiding this comment.
The problem isn't because get_executable and get_compiled_file both exist. Indeed, I expect them to return the same file for RUST.
The reason they don't is because the cached executor isn't a full copy of the original. Instead, it simply sets _executable and _dir on the returned object, and nothing else.
For rust, that means that we try and lock a new directory, which is obviously wrong.
To bypass it I use _executable here, but the proper fix may be instead to look into the caching.
Here's the relevant line: https://github.com/DMOJ/judge-server/blob/master/dmoj/executors/compiled_executor.py#L58
Also, as for get_executable vs get_compiled_file, check out Java's usage of the methods:
https://github.com/DMOJ/judge-server/blob/master/dmoj/executors/java_executor.py#L82
Maybe these exist because Java's compiled file isn't the executable?
| ExactFile('/dev/urandom'), | ||
| ExactFile('/dev/random'), | ||
| *USR_DIR, | ||
| RecursiveDir('/bin'), # required under landlock when /bin is not a symlink, since we check execve. |
| return PTBOX_SPAWN_FAIL_LANDLOCK; | ||
| } | ||
|
|
||
| // Note: WRITE must imply READ. This is required because even if one rule allows writing and another allows reading, |
There was a problem hiding this comment.
The O_TRUNC handling in Landlock scares me. Could we add some testcases with a custom grader that make sure that we don't allow truncating etc.?
No description provided.