Compare commits

...
5 changed files with 111 additions and 18 deletions

No files matched your search

+13 -6
View File
@@ -18,6 +18,8 @@
typedef int (*spawn_fn)(pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *,
char *const[], char *const[]);
__attribute__((visibility("hidden")))
char *const *bl_webhelper_argv(const char *path, char *const argv[], char *const envp[]);
#if defined(__aarch64__) || defined(BINFMT_TEST)
@@ -141,21 +143,26 @@ __attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char
}
int execvpe(const char *file, char *const argv[], char *const envp[]) {
char resolved[PATH_MAX];
const char *path = lookup(file, resolved, sizeof resolved) ? resolved : file;
char *const *adjusted = bl_webhelper_argv(path, argv, envp);
static int (*real)(const char *, char *const[], char *const[]);
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execvpe");
bl_binfmt_before_search(file, argv, envp);
real(file, argv, envp);
return bl_binfmt_after_search(file, argv, envp, errno);
bl_binfmt_before_search(file, (char *const *)adjusted, envp);
real(file, (char *const *)adjusted, envp);
return bl_binfmt_after_search(file, (char *const *)adjusted, envp, errno);
}
int posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *attr,
char *const argv[], char *const envp[]) {
char path[PATH_MAX];
int found = lookup(file, path, sizeof path);
char *const *adjusted = bl_webhelper_argv(found ? path : file, argv, envp);
static spawn_fn real, spawn;
if (!real) real = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawnp");
if (!spawn) spawn = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawn");
char path[PATH_MAX];
if (!handing_off() || !lookup(file, path, sizeof path)) return real(pid, file, fa, attr, argv, envp);
return bl_binfmt_spawn(pid, path, fa, attr, argv, envp, spawn);
if (!handing_off() || !found) return real(pid, file, fa, attr, (char *const *)adjusted, envp);
return bl_binfmt_spawn(pid, path, fa, attr, (char *const *)adjusted, envp, spawn);
}
#else
+7 -5
View File
@@ -271,7 +271,8 @@ __attribute__((visibility("hidden"))) void bl_binfmt_before(const char *path, ch
__attribute__((visibility("hidden"))) int bl_binfmt_retry(const char *path, char *const argv[], char *const envp[], int error);
__attribute__((visibility("hidden"))) void bl_binfmt_before_search(const char *file, char *const argv[], char *const envp[]);
__attribute__((visibility("hidden"))) int bl_binfmt_after_search(const char *file, char *const argv[], char *const envp[], int error);
__attribute__((visibility("hidden"))) char *const *bl_webhelper_argv(const char *path, char *const argv[]);
__attribute__((visibility("hidden")))
char *const *bl_webhelper_argv(const char *path, char *const argv[], char *const envp[]);
__attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa,
const posix_spawnattr_t *attr, char *const argv[], char *const envp[],
spawn_fn real);
@@ -341,7 +342,7 @@ static char **web_helper_redirect(const char *path, char *const argv[], const ch
}
int execve(const char *path, char *const argv_in[], char *const envp[]) {
char *const *argv = bl_webhelper_argv(path, argv_in);
char *const *argv = bl_webhelper_argv(path, argv_in, envp);
static int (*real)(const char *, char *const[], char *const[]);
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve");
maybe_answer_lsof(path, argv);
@@ -372,7 +373,7 @@ static int answer_lsof_via_spawn(const char *path, char *const argv[], pid_t *pi
}
int execvp(const char *file, char *const argv_in[]) {
char *const *argv = bl_webhelper_argv(file, argv_in);
char *const *argv = bl_webhelper_argv(file, argv_in, environ);
static int (*real)(const char *, char *const[]);
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execvp");
maybe_answer_lsof(file, argv);
@@ -382,7 +383,7 @@ int execvp(const char *file, char *const argv_in[]) {
}
int execv(const char *path, char *const argv_in[]) {
char *const *argv = bl_webhelper_argv(path, argv_in);
char *const *argv = bl_webhelper_argv(path, argv_in, environ);
static int (*real)(const char *, char *const[]);
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execv");
maybe_answer_lsof(path, argv);
@@ -406,5 +407,6 @@ int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *
}
static spawn_fn real;
if (!real) real = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawn");
return bl_binfmt_spawn(pid, path, fa, attr, argv, envp, real);
char *const *adjusted = bl_webhelper_argv(path, argv, envp);
return bl_binfmt_spawn(pid, path, fa, attr, (char *const *)adjusted, envp, real);
}
+11 -1
View File
@@ -26,6 +26,14 @@ static char *join_features(const char *have, const char *add) {
return strcat(out, add);
}
static int electron_node_mode(char *const envp[]) {
static const char variable[] = "ELECTRON_RUN_AS_NODE=";
for (int i = 0; envp && envp[i]; i++)
if (!strncmp(envp[i], variable, sizeof variable - 1))
return envp[i][sizeof variable - 1] != 0;
return 0;
}
static char *const *chromium_argv(const char *path, char *const argv[]) {
const char *slash = path ? strrchr(path, '/') : NULL;
if (!slash || !argv || !argv[0]) return argv;
@@ -48,7 +56,9 @@ static char *const *chromium_argv(const char *path, char *const argv[]) {
return out;
}
__attribute__((visibility("hidden"))) char *const *bl_webhelper_argv(const char *path, char *const argv[]) {
__attribute__((visibility("hidden")))
char *const *bl_webhelper_argv(const char *path, char *const argv[], char *const envp[]) {
if (electron_node_mode(envp)) return argv;
if (!is_webhelper_browser(path, argv)) return chromium_argv(path, argv);
const char *spec = getenv("BL_WEBHELPER_ARGS");
if (!spec || !*spec) spec = WEBHELPER_DEFAULT;
+30 -3
View File
@@ -22,22 +22,29 @@ extern char **environ;
int main(int argc, char **argv) {
if (argc < 3) return 2;
const char *how = argv[1], *file = argv[2];
char *node_env[] = {"ELECTRON_RUN_AS_NODE=1", NULL};
char *const *child_env = !strcmp(how, "execve-node") || !strcmp(how, "execvpe-node") ||
!strcmp(how, "spawn-node") || !strcmp(how, "spawnp-node") ? node_env : environ;
char **args = argv + 2;
if (!strcmp(how, "access")) {
int result = access(file, X_OK);
printf("access %d %d\n", result, result ? errno : 0);
return 0;
}
if (!strcmp(how, "spawn") || !strcmp(how, "spawnp")) {
if (!strcmp(how, "spawn") || !strcmp(how, "spawnp") || !strcmp(how, "spawn-node") ||
!strcmp(how, "spawnp-node")) {
pid_t pid;
int error = how[5] ? posix_spawnp(&pid, file, NULL, NULL, args, environ) : posix_spawn(&pid, file, NULL, NULL, args, environ);
int spawnp = !strcmp(how, "spawnp") || !strcmp(how, "spawnp-node");
int error = spawnp ? posix_spawnp(&pid, file, NULL, NULL, args, child_env)
: posix_spawn(&pid, file, NULL, NULL, args, child_env);
if (error) { printf("errno %d\n", error); return 3; }
int status;
waitpid(pid, &status, 0);
printf("exit %d\n", WEXITSTATUS(status));
return 0;
}
if (!strcmp(how, "execve")) execve(file, args, environ);
if (!strcmp(how, "execve") || !strcmp(how, "execve-node")) execve(file, args, (char *const *)child_env);
else if (!strcmp(how, "execvpe-node")) execvpe(file, args, (char *const *)child_env);
else if (!strcmp(how, "execv")) execv(file, args);
else if (!strcmp(how, "execvp")) execvp(file, args);
else if (!strcmp(how, "execvpe")) execvpe(file, args, environ);
@@ -88,6 +95,15 @@ class BinfmtPreloadTest(unittest.TestCase):
path.chmod(mode)
return path
def chromium_script(self):
folder = self.dir / "vscode"
folder.mkdir()
executable = folder / "code"
executable.write_text("#!/bin/sh\nprintf '%s\\n' \"$@\"\n")
executable.chmod(0o755)
(folder / "chrome-sandbox").touch()
return executable
def environment(self, desktop=True, **extra):
env = {"PATH": "%s:/usr/bin:/bin" % self.dir, "LD_PRELOAD": str(self.library), "HANDLER_LOG": str(self.log)}
if desktop:
@@ -161,6 +177,17 @@ class BinfmtPreloadTest(unittest.TestCase):
self.file("script.sh", b"#!/bin/sh\nexit 5\n")
self.assertEqual(self.run_program("spawnp", "script.sh"), (0, "exit 5", None))
def test_explicit_node_child_environment_skips_injection_for_execve_and_spawn(self):
code = self.chromium_script()
self.assertEqual(self.run_program("execve-node", code, "--version"), (0, "--version", None))
self.assertEqual(self.run_program("execvpe-node", code, "--version"), (0, "--version", None))
self.assertEqual(self.run_program("spawn-node", code, "--version"), (0, "--version\nexit 0", None))
self.assertEqual(self.run_program("spawnp-node", code, "--version"), (0, "--version\nexit 0", None))
def test_chromium_gui_launch_without_node_mode_still_receives_no_sandbox(self):
code = self.chromium_script()
self.assertEqual(self.run_program("execve", code, "--version"), (0, "--no-sandbox\n--version", None))
def test_the_hand_off_switch_works_without_the_desktop(self):
tool = self.x86()
for how in ("execve", "execvp"):
+50 -3
View File
@@ -9,9 +9,16 @@ PRELOAD = Path(__file__).resolve().parents[1] / "linuxfs/preload"
PROGRAM = r"""
#include <stdio.h>
char *const *bl_webhelper_argv(const char *path, char *const argv[]);
#include <stdlib.h>
char *const *bl_webhelper_argv(const char *path, char *const argv[], char *const envp[]);
int main(int argc, char **argv) {
char *const *out = bl_webhelper_argv(argv[1], argv + 1);
const char *mode = getenv("TEST_NODE_MODE_CHILD");
static char node[64];
snprintf(node, sizeof node, "ELECTRON_RUN_AS_NODE=%s", mode ? mode : "");
char *node_env[] = {node, NULL};
char *plain_env[] = {"PATH=/usr/bin", NULL};
char *const *envp = mode ? node_env : plain_env;
char *const *out = bl_webhelper_argv(argv[1], argv + 1, (char *const *)envp);
for (int i = 0; out[i]; i++) printf("%s\n", out[i]);
return 0;
}
@@ -35,13 +42,28 @@ class WebhelperPreloadTest(unittest.TestCase):
def tearDownClass(cls):
shutil.rmtree(cls.tmp, ignore_errors=True)
def argv(self, *args, spec=None):
def argv(self, *args, spec=None, node_child=False, parent_node=False):
env = {k: v for k, v in os.environ.items() if k != "BL_WEBHELPER_ARGS"}
env.pop("ELECTRON_RUN_AS_NODE", None)
if spec is not None:
env["BL_WEBHELPER_ARGS"] = spec
if node_child is not False:
env["TEST_NODE_MODE_CHILD"] = "1" if node_child is True else node_child
else:
env.pop("TEST_NODE_MODE_CHILD", None)
if parent_node:
env["ELECTRON_RUN_AS_NODE"] = "1"
result = subprocess.run([str(self.program), *args], env=env, capture_output=True, text=True, check=True)
return result.stdout.splitlines()
def chromium(self, directory, name):
root = self.tmp / directory
root.mkdir(exist_ok=True)
executable = root / name
executable.touch()
(root / "chrome-sandbox").touch()
return str(executable)
def test_the_browser_process_keeps_its_helpers_in_process(self):
out = self.argv(WEBHELPER, "-lang=en_US", "--enable-features=A", "--no-sandbox", "--disable-features=B,C")
self.assertEqual(out, [WEBHELPER, "-lang=en_US", "--no-sandbox", "--no-zygote", "--in-process-gpu",
@@ -57,6 +79,31 @@ class WebhelperPreloadTest(unittest.TestCase):
other = ["/usr/bin/steam", "--enable-features=A"]
self.assertEqual(self.argv(*other), other)
def test_node_mode_from_the_child_environment_skips_sandbox_injection(self):
code = self.chromium("vscode", "code")
self.assertEqual(self.argv(code, node_child=True), [code])
def test_any_nonempty_node_mode_value_skips_sandbox_injection(self):
code = self.chromium("vscode-true", "code")
for value in ("true", "0", "yes"):
self.assertEqual(self.argv(code, node_child=value), [code])
def test_empty_node_mode_value_still_gets_sandbox_flag(self):
chrome = self.chromium("chromium-empty-mode", "chrome")
self.assertEqual(self.argv(chrome, node_child=""), [chrome, "--no-sandbox"])
def test_non_browser_helper_beside_chrome_sandbox_skips_injection_in_node_mode(self):
helper = self.chromium("vscode-helper", "remote-cli")
self.assertEqual(self.argv(helper, node_child=True), [helper])
def test_chromium_gui_launch_still_gets_sandbox_flag_from_explicit_environment(self):
chrome = self.chromium("chromium", "chrome")
self.assertEqual(self.argv(chrome), [chrome, "--no-sandbox"])
def test_parent_node_mode_does_not_override_the_explicit_child_environment(self):
chrome = self.chromium("chromium-parent-mode", "chrome")
self.assertEqual(self.argv(chrome, parent_node=True), [chrome, "--no-sandbox"])
def test_stock_turns_it_off(self):
args = [WEBHELPER, "--enable-features=A"]
self.assertEqual(self.argv(*args, spec="stock"), args)