-
Notifications
You must be signed in to change notification settings - Fork 80
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
Experimental Landlock based sandboxing #597
base: main
Are you sure you want to change the base?
Conversation
I would be in favor of option 1 since it's how unblob works. The extraction path provided with As long as we're clear about the fact that unblob limits itself to the path provided with |
We need this for the parent of extraction directory:
And this for the parent directory of report file:
The latter seems a bit much to me |
d2138c8
to
3c51313
Compare
Hehe, this change is incompatible with code coverage measurement :D
|
e92d6e3
to
52e9322
Compare
Two things to do before tagging as ready for review:
|
59659a3
to
c71c40a
Compare
Will rebase once version |
rebased and solved conflicts. IMO this is ready to be merged once the wip commit is validated |
I had an idea to make sandboxing more composable: spawn a new thread, drop privileges, run |
3dc2926
to
681a54b
Compare
It's interesting that it fails on aarch64-linux. Maybe because of qemu emulation? |
created a linux host on aarch64 (qemu) here, will do some experiments tomorrow |
@vlaci must be related to Nix, cause the tests are passing here:
aarch64 Debian running on |
Even though it seems to be supported by QEMU on aarch64 since qemu/qemu@3a2f19b, landlock syscalls are actually not supported in user mode emulation. For demo purposes, let's write this quick-and-dirty PoC: #include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
int main() {
int result = syscall(SYS_landlock_create_ruleset, NULL, 0, 0);
if (result == -1) {
printf("System call failed: %d (errno: %d)\n", result, errno);
} else {
printf("System call succeeded.\n");
}
return 0;
} Compile it, make sure it's aarch64, run it in user mode emulation:
errno So problem is on QEMU user mode emulation not doing the translation for landlock syscalls. However, it should be handled gracefully by unblob-native I think. Note: tested with qemu version 6.2.0 and 8.2.94 |
I built the
So the bug is probably in unblob-native or between unblob-native Rust and unblob-native Python. The exception is giving me the same feeling:
|
I'll add tests on the rust side as well then :) |
Ahh, we need this error check at the end... |
Please note that kernels not supporting Landlock should not be an error for programs sandboxing themselves, only for sandboxers that must create sandboxes or error out (like this example). It can be a warning though. |
Yep, unblob itself already just logs the problem, but I like clear failures in tests. |
Failing build resolved-by onekey-sec/unblob-native#65 |
all other tests in this file assert on `process_file` being called with correct arguments. We need specific tests which test that the configuration is interpreted correctly
01db1b2
to
71eac4c
Compare
The one thing requires thorough manual testing is keeping exit on CTRL-C and SIGTERM working. Many shenanigans are added for that purpose. |
Instead of juggling with signal handlers and hoping that `ShutDownRequired` will be fired in the appropriate place in `multiprocessing.BasePool`, on exceptional termination, we signal workers via `SIGTERM`. As a side-effect this makes it possible to run `process_file` in non-main thread.
Co-authored-by: Quentin Kaiser <[email protected]>
Unblob now requires 0.1.4, so pyproject.toml version is also updated
import sys | ||
import threading | ||
from multiprocessing.queues import JoinableQueue | ||
from typing import Any, Callable, Union | ||
from typing import Any, Callable, Set, Union |
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.
Maybe not in this MR, but Python 3.8 is EOL for 10 days now, we should probably drop support for it.
3.9 introduced support for generics on collection types.
3.10 introduced |
to replace Union
.
proc.terminate() | ||
|
||
self._input.close() | ||
if sys.version_info >= (3, 9): |
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.
(3.8 is EOL now)
result = self._output.get() | ||
self._result_callback(self, result) | ||
self._input.task_done() | ||
with contextlib.suppress(EOFError): |
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.
Where do this EOFError
come from?
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.
from multiprocessing's queue, as it is using pipes
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.
This went into the wrong commit...
|
||
from structlog import get_logger | ||
from unblob_native.sandbox import ( | ||
AccessFS, |
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 know this comes from Landlock terminology, but this AccessFS
name looks so alien to me (is it a file-system?).
I would use FSAccess
, if possible.
extra_restrictions: Iterable[AccessFS] = (), | ||
): | ||
self.restrictions = [ | ||
# Python, shared libraries, extractor binaries and so on | ||
AccessFS.read("/"), | ||
# Multiprocessing | ||
AccessFS.read_write("/dev/shm"), # noqa: S108 | ||
# Extracted contents | ||
AccessFS.read_write(config.extract_root), | ||
AccessFS.make_dir(config.extract_root.parent), | ||
AccessFS.read_write(log_path), | ||
*extra_restrictions, | ||
] | ||
|
||
if report_file: | ||
self.restrictions += [ | ||
AccessFS.read_write(report_file), | ||
AccessFS.make_reg(report_file.parent), | ||
] |
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.
Since we have a deny-all, then allow exceptions mechanism, maybe a more specific name could be used.
extra_restrictions: Iterable[AccessFS] = (), | |
): | |
self.restrictions = [ | |
# Python, shared libraries, extractor binaries and so on | |
AccessFS.read("/"), | |
# Multiprocessing | |
AccessFS.read_write("/dev/shm"), # noqa: S108 | |
# Extracted contents | |
AccessFS.read_write(config.extract_root), | |
AccessFS.make_dir(config.extract_root.parent), | |
AccessFS.read_write(log_path), | |
*extra_restrictions, | |
] | |
if report_file: | |
self.restrictions += [ | |
AccessFS.read_write(report_file), | |
AccessFS.make_reg(report_file.parent), | |
] | |
extra_passthrough: Iterable[AccessFS] = (), | |
): | |
self.passthrough = [ | |
# Python, shared libraries, extractor binaries and so on | |
AccessFS.read("/"), | |
# Multiprocessing | |
AccessFS.read_write("/dev/shm"), # noqa: S108 | |
# Extracted contents | |
AccessFS.read_write(config.extract_root), | |
AccessFS.make_dir(config.extract_root.parent), | |
AccessFS.read_write(log_path), | |
*extra_passthrough, | |
] | |
if report_file: | |
self.passthrough += [ | |
AccessFS.read_write(report_file), | |
AccessFS.make_reg(report_file.parent), | |
] |
Implementation of #594 together with onekey-sec/unblob-native#11
Having to first create the extraction directory complicates things a lot.
I am unsure what approach we should take here but it can be seen, that the first directory needs somewhat special treatment.
Alternative integration approaches:
LANDLOCK_ACCESS_FS_MAKE_DIR
on the parent of the extraction root as an escape hatch