mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
fix: Chromium browsers start on the desktop (#1280)
A process's status read for another process is served read-only, so Chromium's ReadOnlyFile check no longer kills the browser. Chromium-based AppImages without chrome-sandbox get --no-sandbox, and Flathub Chromium and Electron apps skip zypak's spawn portal, which cannot work here.
This commit is contained in:
1 parent
400325fa8f
commit
ed29f77fc2
5 files changed
+72
-12
No files matched your search
@@ -55,4 +55,4 @@ Joy-Con VR controllers port the arm model of Google's [Google VR SDK for Unity](
|
||||
|
||||
Camera hand tracking runs Google's [MediaPipe](https://github.com/google-ai-edge/mediapipe) Tasks Vision library with its Pose Landmarker (Lite) model, BlazePose GHUM 3D, and its Hand Landmarker model (all Apache-2.0), on frames from AndroidX [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) (Apache-2.0); MediaPipe's usage reports are switched off. The design was checked against [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr) camera app (MIT), and the short Joy-Con movements follow the accelerometer mode of the [Google VR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk)'s arm model, Eric Foxlin's zero-velocity updates and Frosio, Pedersini and Borghese's accelerometer autocalibration, and the camera and Joy-Con are joined by a Kalman filter (R. E. Kalman, 1960). Bare camera hands take their gestures after [ALVR](https://github.com/alvr-org/ALVR)'s `hand_gestures.rs` (MIT) and Meta Quest's menu pinch, with distances checked on photos from the [HaGRID](https://github.com/hukenovs/hagrid) gesture set (CC BY-SA 4.0, not included), fit the palm with Horn's quaternion method (1987) and smooth it with the 1€ filter (Casiez, Roussel and Vogel, 2012); the white glove model follows Valve's OpenVR render model reference and Index controller poses, is meshed with surface nets (Gibson, 1998; Mikola Lysenko) and blends and shades it with Inigo Quilez's smooth minimum and distance-field ambient occlusion, porting his polygon distance (MIT); see [docs/third-party/handtracking](docs/third-party/handtracking/README.md).
|
||||
|
||||
The Store installs and runs apps with [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1). Its sandbox goes through DroidDeck's own bubblewrap shim, which follows how Flatpak hands bwrap its environment.
|
||||
The Store installs and runs apps with [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1). Its sandbox goes through DroidDeck's own bubblewrap shim, which follows how Flatpak hands bwrap its environment. Chromium and Electron apps from Flathub start their browser processes through [zypak](https://github.com/refi64/zypak) by [@refi64](https://github.com/refi64) (BSD-3-Clause); its source was read to see that it always spawns them through Flatpak's spawn portal, which is why DroidDeck starts them directly without Chromium's sandbox instead.
|
||||
@@ -181,6 +181,7 @@ object BwrapSpawner {
|
||||
request.optJSONObject("env")?.let { e -> e.keys().forEach { k -> env[k] = e.getString(k) } }
|
||||
if (binds.any { it.guest == "/dev" }) gpu(rootfs, root, env)?.forEach { addBind(it) }
|
||||
preload(session, rootfs, env).forEach { addBind(it) }
|
||||
zypakShims(binds, root, dir).forEach { addBind(it) }
|
||||
browserSandboxes(env)
|
||||
return Plan(root, binds, request.optString("cwd", "/").ifEmpty { "/" }, argv, env)
|
||||
}
|
||||
@@ -227,6 +228,20 @@ object BwrapSpawner {
|
||||
|
||||
private const val PRELOAD_DIR = "/run/droiddeck-preload"
|
||||
|
||||
private const val ZYPAK_SHIM = "#!/bin/sh\nprogram=\$1\nshift\nexec \"\$program\" --no-sandbox \"\$@\"\n"
|
||||
|
||||
private fun zypakShims(binds: List<Bind>, root: File, dir: File): List<Bind> {
|
||||
val wrappers = listOf("/app/bin/zypak-wrapper.sh", "/app/bin/zypak-wrapper").filter { guest ->
|
||||
val host = File(hostPath(binds, root.path, guest))
|
||||
host.isFile && !Files.isSymbolicLink(host.toPath())
|
||||
}
|
||||
if (wrappers.isEmpty()) return emptyList()
|
||||
val shim = File(dir, "zypak-wrapper")
|
||||
shim.writeText(ZYPAK_SHIM)
|
||||
chmod(shim, 0x1ed)
|
||||
return wrappers.map { Bind(shim.path, it) }
|
||||
}
|
||||
|
||||
private fun browserSandboxes(env: MutableMap<String, String>) {
|
||||
env.putIfAbsent("ZYPAK_ZYGOTE_STRATEGY_SPAWN", "0")
|
||||
env.putIfAbsent("QTWEBENGINE_DISABLE_SANDBOX", "1")
|
||||
|
||||
@@ -118,4 +118,31 @@ class BwrapSpawnerPlanTest {
|
||||
assertEquals("/app", plan.cwd)
|
||||
assertEquals("org.example.Game", plan.env["FLATPAK_ID"])
|
||||
}
|
||||
|
||||
@Test fun zypakLaunchesTheBrowserWithoutItsSandbox() {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
val app = File(runtime, "app").apply { File(this, "bin").mkdirs() }
|
||||
File(app, "bin/zypak-wrapper.sh").writeText("#!/usr/bin/bash\nexec zypak-helper host - \"\$@\"\n")
|
||||
Files.createSymbolicLink(File(app, "bin/zypak-wrapper").toPath(), java.nio.file.Paths.get("/app/bin/zypak-wrapper.sh"))
|
||||
val plan = BwrapSpawner.plan(context, request(
|
||||
op("t" to "bind", "src" to "/runtime-files/app", "dest" to "/app", "ro" to true),
|
||||
), dir)
|
||||
|
||||
val shim = plan.binds.single { it.guest == "/app/bin/zypak-wrapper.sh" }
|
||||
assertTrue(shim.host.startsWith(dir.path))
|
||||
assertTrue(File(shim.host).readText().contains("exec \"\$program\" --no-sandbox \"\$@\""))
|
||||
assertFalse(plan.binds.any { it.guest == "/app/bin/zypak-wrapper" })
|
||||
assertTrue(File(app, "bin/zypak-wrapper.sh").readText().contains("zypak-helper"))
|
||||
}
|
||||
|
||||
@Test fun appsWithoutZypakKeepTheirFiles() {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
File(runtime, "app/bin").mkdirs()
|
||||
val plan = BwrapSpawner.plan(context, request(
|
||||
op("t" to "bind", "src" to "/runtime-files/app", "dest" to "/app", "ro" to true),
|
||||
), dir)
|
||||
|
||||
assertFalse(plan.binds.any { it.guest.startsWith("/app/bin/") })
|
||||
assertFalse(File(dir, "zypak-wrapper").exists())
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ if [ -s /etc/ld.so.preload ]; then
|
||||
export LD_PRELOAD="${preload%:}${LD_PRELOAD:+:$LD_PRELOAD}" SHARUN_ALLOW_LD_PRELOAD=1
|
||||
fi
|
||||
flags=()
|
||||
if [ -e "$APPDIR/chrome-sandbox" ] || compgen -G "$APPDIR/*/chrome-sandbox" >/dev/null; then
|
||||
if [ -n "$(find -H "$APPDIR" -maxdepth 4 \( -name chrome-sandbox -o -name chrome_100_percent.pak -o -name v8_context_snapshot.bin \) -print -quit 2>/dev/null)" ]; then
|
||||
flags+=(--no-sandbox)
|
||||
if [ -n "${DISPLAY:-}" ]; then
|
||||
export XDG_SESSION_TYPE=x11
|
||||
|
||||
@@ -22,15 +22,17 @@ static const char *skip_digits(const char *s) {
|
||||
return end == s ? NULL : end;
|
||||
}
|
||||
|
||||
static int is_status_path(const char *path) {
|
||||
static long status_pid(const char *path) {
|
||||
const char *rest;
|
||||
if (path == NULL || strncmp(path, "/proc/", 6) != 0) return 0;
|
||||
long pid = 0;
|
||||
if (path == NULL || strncmp(path, "/proc/", 6) != 0) return -1;
|
||||
path += 6;
|
||||
if (strncmp(path, "self/", 5) == 0) rest = path + 4;
|
||||
else if (strncmp(path, "thread-self/", 12) == 0) rest = path + 11;
|
||||
else if ((rest = skip_digits(path)) == NULL) return 0;
|
||||
if (strncmp(rest, "/task/", 6) == 0 && (rest = skip_digits(rest + 6)) == NULL) return 0;
|
||||
return strcmp(rest, "/status") == 0;
|
||||
else if ((rest = skip_digits(path)) == NULL) return -1;
|
||||
else pid = strtol(path, NULL, 10);
|
||||
if (strncmp(rest, "/task/", 6) == 0 && (rest = skip_digits(rest + 6)) == NULL) return -1;
|
||||
return strcmp(rest, "/status") == 0 ? pid : -1;
|
||||
}
|
||||
|
||||
static int is_web_helper(const void *caller) {
|
||||
@@ -56,13 +58,26 @@ static int write_all(int fd, const char *data, size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_only_copy(const char *data, size_t len, int flags) {
|
||||
char path[] = "/tmp/.droiddeck-status-XXXXXX";
|
||||
int rw = mkostemp(path, O_CLOEXEC);
|
||||
if (rw < 0) return -1;
|
||||
int ro = -1;
|
||||
if (write_all(rw, data, len) == 0)
|
||||
ro = (int)syscall(SYS_openat, AT_FDCWD, path, O_RDONLY | (flags & O_CLOEXEC));
|
||||
unlink(path);
|
||||
close(rw);
|
||||
return ro;
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) int bl_status_without_tracer(const char *path, int flags, const void *caller) {
|
||||
char text[STATUS_MAX];
|
||||
size_t len = 0;
|
||||
int saved = errno;
|
||||
int out = -1;
|
||||
|
||||
if ((flags & O_ACCMODE) != O_RDONLY || !is_status_path(path) || is_web_helper(caller)) return -1;
|
||||
long pid;
|
||||
if ((flags & O_ACCMODE) != O_RDONLY || (pid = status_pid(path)) < 0 || is_web_helper(caller)) return -1;
|
||||
int in = (int)syscall(SYS_openat, AT_FDCWD, path, O_RDONLY | O_CLOEXEC);
|
||||
if (in < 0) goto done;
|
||||
for (;;) {
|
||||
@@ -78,13 +93,16 @@ __attribute__((visibility("hidden"))) int bl_status_without_tracer(const char *p
|
||||
if (field == NULL) goto done;
|
||||
char *value = field + strlen(TRACER_FIELD);
|
||||
char *line_end = strchr(value, '\n');
|
||||
if (line_end == NULL) goto done;
|
||||
if (line_end == NULL || line_end == value) goto done;
|
||||
|
||||
memmove(value + 1, line_end, len - (size_t)(line_end - text) + 1);
|
||||
*value = '0';
|
||||
len = (size_t)(value + 1 - text) + strlen(value + 1);
|
||||
if (pid != 0 && pid != getpid() && (out = read_only_copy(text, len, flags)) >= 0) goto done;
|
||||
|
||||
out = memfd_create("status", (flags & O_CLOEXEC) ? MFD_CLOEXEC : 0);
|
||||
if (out < 0) goto done;
|
||||
if (write_all(out, text, (size_t)(value - text)) != 0 || write_all(out, "0", 1) != 0
|
||||
|| write_all(out, line_end, len - (size_t)(line_end - text)) != 0
|
||||
|| lseek(out, 0, SEEK_SET) != 0) {
|
||||
if (write_all(out, text, len) != 0 || lseek(out, 0, SEEK_SET) != 0) {
|
||||
close(out);
|
||||
out = -1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user