gtk/surface: Filter out the SNAP variables by their contents

When running in a snap context we need to filtering out all the SNAP_*
variables out there, but this is not enough, because it's also needed
to sanitize them by ensuring that no variable containing a path pointing
to a $SNAP folder is leaked there.

Otherwise we might have (for example) XDG_RUNTIME_DIRS containing a
"private" snap path, and that will be exposed to all the applications
that will be started from ghostty
pull/8771/head
Marco Trevisan (Treviño) 2025-09-19 06:40:11 +02:00 committed by Mitchell Hashimoto
parent 2de105e035
commit d55f3e5c41
2 changed files with 89 additions and 18 deletions

View File

@ -61,10 +61,4 @@ fi
[ "$needs_update" = true ] && echo "LAST_REVISION=$SNAP_REVISION" > "$SNAP_USER_DATA/.last_revision"
# Unset all SNAP specific environment variables to keep them from leaking
# into other snaps that might get executed from within the shell
for var in $(printenv | grep SNAP_ | cut -d= -f1); do
unset $var
done
exec "$@"

View File

@ -1228,18 +1228,7 @@ pub const Surface = extern struct {
// Unset environment varies set by snaps if we're running in a snap.
// This allows Ghostty to further launch additional snaps.
if (env.get("SNAP")) |_| {
env.remove("SNAP");
env.remove("DRIRC_CONFIGDIR");
env.remove("__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS");
env.remove("__EGL_VENDOR_LIBRARY_DIRS");
env.remove("LD_LIBRARY_PATH");
env.remove("LIBGL_DRIVERS_PATH");
env.remove("LIBVA_DRIVERS_PATH");
env.remove("VK_LAYER_PATH");
env.remove("XLOCALEDIR");
env.remove("GDK_PIXBUF_MODULEDIR");
env.remove("GDK_PIXBUF_MODULE_FILE");
env.remove("GTK_PATH");
try filterSnapPaths(alloc, &env);
}
// This is a hack because it ties ourselves (optionally) to the
@ -1253,6 +1242,94 @@ pub const Surface = extern struct {
return env;
}
/// Filter out environment variables that start with forbidden prefixes
fn filterSnapPaths(allocator: std.mem.Allocator, env_map: *std.process.EnvMap) !void {
const snap_paths_vars = [_][]const u8{
"SNAP",
"SNAP_USER_COMMON",
"SNAP_USER_DATA",
"SNAP_DATA",
"SNAP_COMMON",
};
var env_to_remove = std.ArrayList([]const u8).init(allocator);
defer env_to_remove.deinit();
var env_to_update = std.ArrayList(struct {
key: []const u8,
value: []const u8,
}).init(allocator);
defer {
for (env_to_update.items) |item| {
allocator.free(item.value);
}
env_to_update.deinit();
}
var it = env_map.iterator();
while (it.next()) |entry| {
const key = entry.key_ptr.*;
const value = entry.value_ptr.*;
if (std.mem.eql(u8, key, "TERMINFO")) {
continue;
}
if (std.mem.startsWith(u8, key, "GHOSTTY")) {
continue;
}
if (std.mem.startsWith(u8, key, "SNAP_")) {
try env_to_remove.append(key);
continue;
}
var paths = std.mem.splitAny(u8, value, ":");
var filtered_paths = std.ArrayList([]const u8).init(allocator);
defer filtered_paths.deinit();
var modified = false;
while (paths.next()) |path| {
var include = true;
for (snap_paths_vars) |snap_path_var| {
const snap_path_val = env_map.get(snap_path_var);
if (snap_path_val) |snap_path| {
if (snap_path.len == 0) {
continue;
}
if (std.mem.startsWith(u8, path, snap_path)) {
include = false;
modified = true;
break;
}
}
}
if (include) {
try filtered_paths.append(path);
}
}
if (modified) {
if (filtered_paths.items.len > 0) {
const new_value = try std.mem.join(allocator, ":", filtered_paths.items);
try env_to_update.append(.{ .key = key, .value = new_value });
} else {
try env_to_remove.append(key);
}
}
}
for (env_to_update.items) |item| {
try env_map.put(item.key, item.value);
}
for (env_to_remove.items) |key| {
_ = env_map.remove(key);
}
}
pub fn clipboardRequest(
self: *Self,
clipboard_type: apprt.Clipboard,