61#if ENABLE(BUBBLEWRAP_SANDBOX)
62
63class DBusProxy {
64public:
65 void setSocket(const char* dbusSocket, const char* dbusPath)
66 {
67 m_socket = dbusSocket;
68 m_path = dbusPath;
69 };
70
71 bool isRunning() const { return m_isRunning; };
72 CString proxyPath() const { return m_proxyPath; };
73 CString path() const { return m_path; };
74
75 void appendPermissions(std::initializer_list<CString> permissions)
76 {
77 ASSERT_WITH_SECURITY_IMPLICATION(!m_isRunning);
78
79 Vector<CString> newPermissions(permissions);
80 m_permissions.appendVector(newPermissions);
81 };
82
83 void launch()
84 {
85 if (m_isRunning)
86 return;
87
88 GUniquePtr<char> proxySocket(makeProxySocket());
89 ASSERT_WITH_SECURITY_IMPLICATION(proxySocket.get());
90
91 m_proxyPath = proxySocket.get();
92 const char* runDir = g_get_user_runtime_dir();
93 GUniquePtr<char> appRunDir(g_build_filename(runDir, g_get_prgname(), nullptr));
94
95 Vector<CString> args = {
96 BWRAP_EXECUTABLE,
97 "--die-with-parent",
98 "--unshare-all",
99 "--ro-bind", DBUS_PROXY_EXECUTABLE, DBUS_PROXY_EXECUTABLE,
100 "--ro-bind", "/usr/lib", "/usr/lib",
101 "--ro-bind-try", "/lib", "/lib",
102 "--ro-bind-try", "/lib64", "/lib64",
103 "--dev", "/dev",
104 "--proc", "/proc",
105 "--dir", "/run",
106 "--symlink", "../run", "/var/run",
107 "--symlink", "../tmp", "/var/tmp",
108 "--tmpfs", "/tmp",
109 "--unsetenv", "TMPDIR",
110 "--bind-try", m_path, m_path,
111 "--bind", appRunDir.get(), appRunDir.get(),
112
113 DBUS_PROXY_EXECUTABLE,
114 m_socket, m_proxyPath,
115 "--filter",
116 };
117
118 if (!g_strcmp0(g_getenv("WEBKIT_DBUS_LOG"), "1"))
119 args.append("--log");
120
121 args.appendVector(m_permissions);
122
123 int nargs = args.size() + 1;
124 int i = 0;
125 char** argv = g_newa(char*, nargs);
126 for (const auto& arg : args)
127 argv[i++] = const_cast<char*>(arg.data());
128 argv[i] = nullptr;
129
130 GUniqueOutPtr<GError> error;
131 GRefPtr<GSubprocess> process(g_subprocess_newv(argv, G_SUBPROCESS_FLAGS_NONE, &error.outPtr()));
132 if (error.get()) {
133 g_error("Failed to start dbus proxy: %s", error.get()->message);
134 return;
135 }
136
137 m_isRunning = true;
138 };
139
140private:
141 static char* makeProxySocket()
142 {
143 const char* runDir = g_get_user_runtime_dir();
144 GUniquePtr<char> appRunDir(g_build_filename(runDir, g_get_prgname(), nullptr));
145
146 if (g_mkdir_with_parents(appRunDir.get(), 0700) == -1) {
147 g_error("Failed to mkdir for dbus proxy (%s): %s", appRunDir.get(), g_strerror(errno));
148 return nullptr;
149 }
150
151 char* proxySocketTemplate = g_build_filename(appRunDir.get(), "dbus-proxy-XXXXXX", nullptr);
152 int fd;
153 if ((fd = g_mkstemp(proxySocketTemplate)) == -1) {
154 g_free(proxySocketTemplate);
155 g_error("Failed to make socket file for dbus proxy: %s", g_strerror(errno));
156 return nullptr;
157 }
158
159 close(fd);
160 return proxySocketTemplate;
161 };
162
163 CString m_socket;
164 CString m_path;
165 CString m_proxyPath;
166 bool m_isRunning = false;
167 Vector<CString> m_permissions = { };
168};
169
170// From flatpak-run.c (LGPLv2.1+)
171static char* dbusAddressToPath(const char* address, bool abstract = false)
172{
173 const char* path;
174 const char* path_end;
175
176 if (!address)
177 return nullptr;
178
179 if (!g_str_has_prefix(address, "unix:"))
180 return nullptr;
181
182 path = strstr(address, abstract ? "abstract=" : "path=");
183 if (!path)
184 return nullptr;
185
186 path += strlen(abstract ? "abstract=" : "path=");
187 path_end = path;
188 while (*path_end && *path_end != ',')
189 path_end++;
190
191 return g_strndup(path, path_end - path);
192} // End LGPLv2.1+
193
194enum class BindFlags {
195 ReadOnly,
196 ReadWrite,
197 Device,
198};
199
200static void bindIfExists(Vector<CString>& args, const char* path, BindFlags bindFlags = BindFlags::ReadOnly)
201{
202 if (!path)
203 return;
204
205 const char* type;
206 if (bindFlags == BindFlags::Device)
207 type = "--dev-bind-try";
208 else if (bindFlags == BindFlags::ReadOnly)
209 type = "--ro-bind-try";
210 else
211 type = "--bind-try";
212 args.appendVector(Vector<CString>({ type, path, path }));
213}
214
215static void bindDBusSession(Vector<CString>& args, DBusProxy& proxy)
216{
217 if (!proxy.isRunning()) {
218 const char* sessionAddress = g_getenv("DBUS_SESSION_BUS_ADDRESS");
219 if (!sessionAddress)
220 return;
221
222 GUniquePtr<char> sessionPath(dbusAddressToPath(sessionAddress));
223 if (!sessionPath.get())
224 return;
225
226 proxy.setSocket(sessionAddress, sessionPath.get());
227 proxy.launch();
228 }
229
230 args.appendVector(Vector<CString>({
231 "--bind", proxy.proxyPath(), proxy.path(),
232 }));
233}
234
235#if 0
236static void bindDBusSystem(Vector<CString>& args)
237{
238 const char* systemAddress = g_getenv("DBUS_SYSTEM_BUS_ADDRESS");
239 if (!systemAddress)
240 systemAddress = "unix:path=/var/run/dbus/system_bus_socket";
241
242 GUniquePtr<char> systemPath(dbusAddressToPath(systemAddress));
243 if (!systemPath.get())
244 return;
245
246 DBusProxy proxy(systemAddress, systemPath.get());
247 args.appendVector(Vector<CString>({
248 "--bind", proxy.proxyPath(), systemPath.get(),
249 }));
250}
251#endif
252
253static void bindX11(Vector<CString>& args)
254{
255 const char* display = g_getenv("DISPLAY");
256 if (!display || display[0] != ':' || !g_ascii_isdigit(const_cast<char*>(display)[1]))
257 display = ":0";
258 GUniquePtr<char> x11File(g_strdup_printf("/tmp/.X11-unix/X%s", display + 1));
259 bindIfExists(args, x11File.get(), BindFlags::ReadWrite);
260
261 const char* xauth = g_getenv("XAUTHORITY");
262 if (!xauth) {
263 const char* homeDir = g_get_home_dir();
264 GUniquePtr<char> xauthFile(g_build_filename(homeDir, ".Xauthority", nullptr));
265 bindIfExists(args, xauthFile.get());
266 } else
267 bindIfExists(args, xauth);
268}
269
270static void bindDconf(Vector<CString>& args)
271{
272 const char* runtimeDir = g_get_user_runtime_dir();
273 GUniquePtr<char> dconfRuntimeDir(g_build_filename(runtimeDir, "dconf", nullptr));
274 args.appendVector(Vector<CString>({ "--bind", dconfRuntimeDir.get(), dconfRuntimeDir.get() }));
275
276 const char* dconfDir = g_getenv("DCONF_USER_CONFIG_DIR");
277 if (dconfDir)
278 bindIfExists(args, dconfDir);
279 else {
280 const char* configDir = g_get_user_config_dir();
281 GUniquePtr<char> dconfConfigDir(g_build_filename(configDir, "dconf", nullptr));
282 bindIfExists(args, dconfConfigDir.get(), BindFlags::ReadWrite);
283 }
284}
285
286static void bindWayland(Vector<CString>& args)
287{
288 const char* display = g_getenv("WAYLAND_DISPLAY");
289 if (!display)
290 display = "wayland-0";
291
292 const char* runtimeDir = g_get_user_runtime_dir();
293 GUniquePtr<char> waylandRuntimeFile(g_build_filename(runtimeDir, display, nullptr));
294 bindIfExists(args, waylandRuntimeFile.get(), BindFlags::ReadWrite);
295}
296
297static void bindPulse(Vector<CString>& args)
298{
299 // FIXME: The server can be defined in config files we'd have to parse.
300 // They can also be set as X11 props but that is getting a bit ridiculous.
301 const char* pulseServer = g_getenv("PULSE_SERVER");
302 if (pulseServer) {
303 if (g_str_has_prefix(pulseServer, "unix:"))
304 bindIfExists(args, pulseServer + 5, BindFlags::ReadWrite);
305 // else it uses tcp
306 } else {
307 const char* runtimeDir = g_get_user_runtime_dir();
308 GUniquePtr<char> pulseRuntimeDir(g_build_filename(runtimeDir, "pulse", nullptr));
309 bindIfExists(args, pulseRuntimeDir.get(), BindFlags::ReadWrite);
310 }
311
312 const char* pulseConfig = g_getenv("PULSE_CLIENTCONFIG");
313 if (pulseConfig)
314 bindIfExists(args, pulseConfig);
315
316 const char* configDir = g_get_user_config_dir();
317 GUniquePtr<char> pulseConfigDir(g_build_filename(configDir, "pulse", nullptr));
318 bindIfExists(args, pulseConfigDir.get());
319
320 const char* homeDir = g_get_home_dir();
321 GUniquePtr<char> pulseHomeConfigDir(g_build_filename(homeDir, ".pulse", nullptr));
322 GUniquePtr<char> asoundHomeConfigDir(g_build_filename(homeDir, ".asoundrc", nullptr));
323 bindIfExists(args, pulseHomeConfigDir.get());
324 bindIfExists(args, asoundHomeConfigDir.get());
325
326 // This is the ultimate fallback to raw ALSA
327 bindIfExists(args, "/dev/snd", BindFlags::Device);
328}
329
330static void bindFonts(Vector<CString>& args)
331{
332 const char* configDir = g_get_user_config_dir();
333 const char* homeDir = g_get_home_dir();
334 const char* dataDir = g_get_user_data_dir();
335 const char* cacheDir = g_get_user_cache_dir();
336
337 // Configs can include custom dirs but then we have to parse them...
338 GUniquePtr<char> fontConfig(g_build_filename(configDir, "fontconfig", nullptr));
339 GUniquePtr<char> fontCache(g_build_filename(cacheDir, "fontconfig", nullptr));
340 GUniquePtr<char> fontHomeConfig(g_build_filename(homeDir, ".fonts.conf", nullptr));
341 GUniquePtr<char> fontHomeConfigDir(g_build_filename(configDir, ".fonts.conf.d", nullptr));
342 GUniquePtr<char> fontData(g_build_filename(dataDir, "fonts", nullptr));
343 GUniquePtr<char> fontHomeData(g_build_filename(homeDir, ".fonts", nullptr));
344 bindIfExists(args, fontConfig.get());
345 bindIfExists(args, fontCache.get(), BindFlags::ReadWrite);
346 bindIfExists(args, fontHomeConfig.get());
347 bindIfExists(args, fontHomeConfigDir.get());
348 bindIfExists(args, fontData.get());
349 bindIfExists(args, fontHomeData.get());
350}
351
352#if PLATFORM(GTK)
353static void bindGtkData(Vector<CString>& args)
354{
355 const char* configDir = g_get_user_config_dir();
356 const char* dataDir = g_get_user_data_dir();
357 const char* homeDir = g_get_home_dir();
358
359 GUniquePtr<char> gtkConfig(g_build_filename(configDir, "gtk-3.0", nullptr));
360 GUniquePtr<char> themeData(g_build_filename(dataDir, "themes", nullptr));
361 GUniquePtr<char> themeHomeData(g_build_filename(homeDir, ".themes", nullptr));
362 GUniquePtr<char> iconHomeData(g_build_filename(homeDir, ".icons", nullptr));
363 bindIfExists(args, gtkConfig.get());
364 bindIfExists(args, themeData.get());
365 bindIfExists(args, themeHomeData.get());
366 bindIfExists(args, iconHomeData.get());
367}
368
369static void bindA11y(Vector<CString>& args)
370{
371 static NeverDestroyed<DBusProxy> staticProxy;
372 DBusProxy proxy = staticProxy.get();
373
374 if (!proxy.isRunning()) {
375 // FIXME: Avoid blocking IO... (It is at least a one-time cost)
376 GRefPtr<GDBusConnection> sessionBus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr));
377 if (!sessionBus.get())
378 return;
379
380 GRefPtr<GDBusMessage> msg(g_dbus_message_new_method_call("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress"));
381 g_dbus_message_set_body(msg.get(), g_variant_new("()"));
382 GRefPtr<GDBusMessage> reply(g_dbus_connection_send_message_with_reply_sync(
383 sessionBus.get(), msg.get(),
384 G_DBUS_SEND_MESSAGE_FLAGS_NONE,
385 30000,
386 nullptr,
387 nullptr,
388 nullptr));
389
390 GUniqueOutPtr<GError> error;
391 GUniqueOutPtr<char> a11yAddress;
392 if (reply.get()) {
393 if (g_dbus_message_to_gerror(reply.get(), &error.outPtr())) {
394 if (!g_error_matches(error.get(), G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
395 g_warning("Can't find a11y bus: %s", error.get()->message);
396 } else
397 g_variant_get(g_dbus_message_get_body(reply.get()), "(s)", &a11yAddress.outPtr());
398 }
399
400 if (!a11yAddress.get())
401 return;
402
403 GUniquePtr<char> a11yPath(dbusAddressToPath(a11yAddress.get(), true));
404 if (!a11yAddress.get())
405 return;
406
407 proxy.setSocket(a11yAddress.get(), a11yPath.get());
408 proxy.appendPermissions({
409 "--sloppy-names",
410 "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root",
411 "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root",
412 "--call=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry",
413 "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetKeystrokeListeners@/org/a11y/atspi/registry/deviceeventcontroller",
414 "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetDeviceEventListeners@/org/a11y/atspi/registry/deviceeventcontroller",
415 "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersSync@/org/a11y/atspi/registry/deviceeventcontroller",
416 "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersAsync@/org/a11y/atspi/registry/deviceeventcontroller",
417 });
418 proxy.launch();
419 }
420
421 args.appendVector(Vector<CString>({
422 "--bind", proxy.proxyPath(), proxy.path(),
423 }));
424}
425#endif
426
427static bool bindPathVar(Vector<CString>& args, const char* varname)
428{
429 const char* pathValue = g_getenv(varname);
430 if (!pathValue)
431 return false;
432
433 GUniquePtr<char*> splitPaths(g_strsplit(pathValue, ":", -1));
434 for (size_t i; splitPaths.get()[i]; ++i)
435 bindIfExists(args, splitPaths.get()[i]);
436
437 return true;
438}
439
440static void bindGStreamerData(Vector<CString>& args)
441{
442 if (!bindPathVar(args, "GST_PLUGIN_PATH_1_0"))
443 bindPathVar(args, "GST_PLUGIN_PATH");
444
445 if (!bindPathVar(args, "GST_PLUGIN_SYSTEM_PATH_1_0")) {
446 if (!bindPathVar(args, "GST_PLUGIN_SYSTEM_PATH")) {
447 GUniquePtr<char> gstData(g_build_filename(g_get_user_data_dir(), "gstreamer-1.0", nullptr));
448 bindIfExists(args, gstData.get());
449 }
450 }
451
452 GUniquePtr<char> gstCache(g_build_filename(g_get_user_cache_dir(), "gstreamer-1.0", nullptr));
453 bindIfExists(args, gstCache.get(), BindFlags::ReadWrite);
454
455 // /usr/lib is already added so this is only requried for other dirs
456 const char* scannerPath = g_getenv("GST_PLUGIN_SCANNER") ?: "/usr/libexec/gstreamer-1.0/gst-plugin-scanner";
457 const char* helperPath = g_getenv("GST_INSTALL_PLUGINS_HELPER ") ?: "/usr/libexec/gst-install-plugins-helper";
458
459 bindIfExists(args, scannerPath);
460 bindIfExists(args, helperPath);
461}
462
463static void bindOpenGL(Vector<CString>& args)
464{
465 args.appendVector(Vector<CString>({
466 "--dev-bind-try", "/dev/dri", "/dev/dri",
467 // Mali
468 "--dev-bind-try", "/dev/mali", "/dev/mali",
469 "--dev-bind-try", "/dev/mali0", "/dev/mali0",
470 "--dev-bind-try", "/dev/umplock", "/dev/umplock",
471 // Nvidia
472 "--dev-bind-try", "/dev/nvidiactl", "/dev/nvidiactl",
473 "--dev-bind-try", "/dev/nvidia0", "/dev/nvidia0",
474 "--dev-bind-try", "/dev/nvidia", "/dev/nvidia",
475 // Adreno
476 "--dev-bind-try", "/dev/kgsl-3d0", "/dev/kgsl-3d0",
477 "--dev-bind-try", "/dev/ion", "/dev/ion",
478#if PLATFORM(WPE)
479 "--dev-bind-try", "/dev/fb0", "/dev/fb0",
480 "--dev-bind-try", "/dev/fb1", "/dev/fb1",
481#endif
482 }));
483}
484
485static void bindV4l(Vector<CString>& args)
486{
487 args.appendVector(Vector<CString>({
488 "--dev-bind-try", "/dev/v4l", "/dev/v4l",
489 // Not pretty but a stop-gap for pipewire anyway
490 "--dev-bind-try", "/dev/video0", "/dev/video0",
491 "--dev-bind-try", "/dev/video1", "/dev/video1",
492 }));
493}
494
495static int memfd_create(const char* name, unsigned flags)
496{
497 return syscall(__NR_memfd_create, name, flags);
498}
499
500static int setupSeccomp()
501{
502 // NOTE: This is shared code (flatpak-run.c - LGPLv2.1+)
503 // There are today a number of different Linux container
504 // implementations. That will likely continue for long into the
505 // future. But we can still try to share code, and it's important
506 // to do so because it affects what library and application writers
507 // can do, and we should support code portability between different
508 // container tools.
509 //
510 // This syscall blacklist is copied from linux-user-chroot, which was in turn
511 // clearly influenced by the Sandstorm.io blacklist.
512 //
513 // If you make any changes here, I suggest sending the changes along
514 // to other sandbox maintainers. Using the libseccomp list is also
515 // an appropriate venue:
516 // https://groups.google.com/forum/#!topic/libseccomp
517 //
518 // A non-exhaustive list of links to container tooling that might
519 // want to share this blacklist:
520 //
521 // https://github.com/sandstorm-io/sandstorm
522 // in src/sandstorm/supervisor.c++
523 // http://cgit.freedesktop.org/xdg-app/xdg-app/
524 // in common/flatpak-run.c
525 // https://git.gnome.org/browse/linux-user-chroot
526 // in src/setup-seccomp.c
527 struct scmp_arg_cmp clone_arg = SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER);
528 struct scmp_arg_cmp tty_arg = SCMP_A1(SCMP_CMP_EQ, (int)TIOCSTI);
529 struct {
530 int scall;
531 struct scmp_arg_cmp* arg;
532 } syscall_blacklist[] = {
533 // Block dmesg
534 { SCMP_SYS(syslog), nullptr },
535 // Useless old syscall
536 { SCMP_SYS(uselib), nullptr },
537 // Don't allow disabling accounting
538 { SCMP_SYS(acct), nullptr },
539 // 16-bit code is unnecessary in the sandbox, and modify_ldt is a
540 // historic source of interesting information leaks.
541 { SCMP_SYS(modify_ldt), nullptr },
542 // Don't allow reading current quota use
543 { SCMP_SYS(quotactl), nullptr },
544
545 // Don't allow access to the kernel keyring
546 { SCMP_SYS(add_key), nullptr },
547 { SCMP_SYS(keyctl), nullptr },
548 { SCMP_SYS(request_key), nullptr },
549
550 // Scary VM/NUMA ops
551 { SCMP_SYS(move_pages), nullptr },
552 { SCMP_SYS(mbind), nullptr },
553 { SCMP_SYS(get_mempolicy), nullptr },
554 { SCMP_SYS(set_mempolicy), nullptr },
555 { SCMP_SYS(migrate_pages), nullptr },
556
557 // Don't allow subnamespace setups:
558 { SCMP_SYS(unshare), nullptr },
559 { SCMP_SYS(mount), nullptr },
560 { SCMP_SYS(pivot_root), nullptr },
561 { SCMP_SYS(clone), &clone_arg },
562
563 // Don't allow faking input to the controlling tty (CVE-2017-5226)
564 { SCMP_SYS(ioctl), &tty_arg },
565
566 // Profiling operations; we expect these to be done by tools from outside
567 // the sandbox. In particular perf has been the source of many CVEs.
568 { SCMP_SYS(perf_event_open), nullptr },
569 // Don't allow you to switch to bsd emulation or whatnot
570 { SCMP_SYS(personality), nullptr },
571 { SCMP_SYS(ptrace), nullptr }
572 };
573
574 scmp_filter_ctx seccomp = seccomp_init(SCMP_ACT_ALLOW);
575 if (!seccomp)
576 return -1;
577
578 for (auto& rule : syscall_blacklist) {
579 int scall = rule.scall;
580 int r;
581 if (rule.arg)
582 r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), scall, 1, rule.arg);
583 else
584 r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), scall, 0);
585 if (r == -EFAULT) {
586 g_warning("Failed to add seccomp rule");
587 seccomp_release(seccomp);
588 return -1;
589 }
590 }
591
592 int tmpfd = memfd_create("seccomp-bpf", 0);
593 if (tmpfd == -1) {
594 g_warning("Failed to create memfd: %s", g_strerror(errno));
595 seccomp_release(seccomp);
596 return -1;
597 }
598
599 if (seccomp_export_bpf(seccomp, tmpfd)) {
600 g_warning("Failed to export seccomp bpf");
601 seccomp_release(seccomp);
602 close(tmpfd);
603 return -1;
604 }
605
606 lseek(tmpfd, 0, SEEK_SET);
607 seccomp_release(seccomp);
608 return tmpfd;
609}
610
611static void ensureDirectoryExists(const String& path)
612{
613 if (path.isEmpty())
614 return;
615
616 // FIXME: Blocking
617 if (g_mkdir_with_parents(path.utf8().data(), 0700) == -1)
618 g_warning("Could not create directory \"%s\": %s", path.utf8().data(), g_strerror(errno));
619}
620
621static std::tuple<Vector<CString>, int> getBwrapArgs(ProcessLauncher::ProcessType processType, const Vector<String>& extraPaths)
622{
623 // It is impossible to know what access arbitrary plugins need and since it is for legacy
624 // reasons lets just leave it unsandboxed.
625 if (processType == ProcessLauncher::ProcessType::Plugin64
626 || processType == ProcessLauncher::ProcessType::Plugin32)
627 return {{ }, -1};
628
629 // NOTE: This is not a great solution but we just assume that applications create this directory
630 // ahead of time if they require it
631 GUniquePtr<char> configDir(g_build_filename(g_get_user_config_dir(), g_get_prgname(), nullptr));
632
633 Vector<CString> sandboxArgs = {
634 BWRAP_EXECUTABLE,
635 "--die-with-parent",
636 "--unshare-pid",
637 "--unshare-uts",
638
639 // We assume /etc has safe permissions
640 // At a later point we can start masking privacy-concerning files
641 "--ro-bind", "/etc", "/etc",
642 "--dev", "/dev",
643 "--proc", "/proc",
644 "--tmpfs", "/tmp",
645 "--unsetenv", "TMPDIR",
646 "--dir", "/run",
647 "--symlink", "../run", "/var/run",
648 "--symlink", "../tmp", "/var/tmp",
649 "--ro-bind", "/sys/block", "/sys/block",
650 "--ro-bind", "/sys/bus", "/sys/bus",
651 "--ro-bind", "/sys/class", "/sys/class",
652 "--ro-bind", "/sys/dev", "/sys/dev",
653 "--ro-bind", "/sys/devices", "/sys/devices",
654
655 "--ro-bind-try", "/usr/share", "/usr/share",
656 "--ro-bind-try", "/usr/local/share", "/usr/local/share",
657
658 // We only grant access to the libdirs webkit is built with and
659 // guess system libdirs. This will always have some edge cases
660 "--ro-bind-try", "/lib", "/lib",
661 "--ro-bind-try", "/usr/lib", "/usr/lib",
662 "--ro-bind-try", "/usr/local/lib", "/usr/local/lib",
663 "--ro-bind-try", LIBDIR, LIBDIR,
664 "--ro-bind-try", "/lib64", "/lib64",
665 "--ro-bind-try", "/usr/lib64", "/usr/lib64",
666 "--ro-bind-try", "/usr/local/lib64", "/usr/local/lib64",
667 "--ro-bind-try", PKGLIBEXECDIR, PKGLIBEXECDIR,
668
669 "--bind-try", configDir.get(), configDir.get(),
670 };
671 // We would have to parse ld config files for more info
672 bindPathVar(sandboxArgs, "LD_LIBRARY_PATH");
673
674 if (processType == ProcessLauncher::ProcessType::Network) {
675 static NeverDestroyed<DBusProxy> staticProxy;
676 DBusProxy proxy = staticProxy.get();
677
678 // FIXME: The network process is used for `file://` URIs and
679 // we would have to pass through most paths for this to work
680
681 // FIXME: pkcs11? (~/.config/pkcs11)
682
683 // glib-networking can use dbus for proxy information
684 // FIXME: Find and add the permissions it needs for this (pacrunner?)
685 bindDBusSession(sandboxArgs, proxy);
686 }
687
688 // NOTE: This has network access for HLS via GStreamer
689 if (processType == ProcessLauncher::ProcessType::Web) {
690 static NeverDestroyed<DBusProxy> staticProxy;
691 DBusProxy proxy = staticProxy.get();
692
693 if (!proxy.isRunning()) {
694 proxy.appendPermissions({
695 "--talk=ca.desrt.dconf", // Used by GTK on Wayland
696 "--talk=org.freedesktop.ScreenSaver", // GStreamer inhibits, FIXME: Use xdg-desktop-portal
697 "--talk=org.freedesktop.portal.Desktop",
698 // GStreamers plugin install helper
699 "--call=org.freedesktop.PackageKit=org.freedesktop.PackageKit.Modify2.InstallGStreamerResources@/org/freedesktop/PackageKit"
700 // "--talk=org.freedesktop.secrets" // FIXME: WebKitGTK uses for HTTP Auth
701 });
702 }
703
704 bindX11(sandboxArgs);
705 bindWayland(sandboxArgs);
706 bindDBusSession(sandboxArgs, proxy);
707
708 // FIXME: This needs to be restricted, upstream is working on it
709 bindDconf(sandboxArgs);
710 // FIXME: We should move to Pipewire as soon as viable, Pulse
711 // doesn't restrict clients atm.
712 bindPulse(sandboxArgs);
713 bindFonts(sandboxArgs);
714 bindGStreamerData(sandboxArgs);
715 bindOpenGL(sandboxArgs);
716 bindV4l(sandboxArgs);
717#if PLATFORM(GTK)
718 bindA11y(sandboxArgs);
719 bindGtkData(sandboxArgs);
720#endif
721 } else {
722 // Only X11 users need this for XShm which is only the Web process
723 sandboxArgs.append("--unshare-ipc");
724 }
725
726 // Only process without any network access for now
727 if (processType == ProcessLauncher::ProcessType::Storage)
728 sandboxArgs.append("--unshare-net");
729
730#if ENABLE(DEVELOPER_MODE)
731 const char* execDirectory = g_getenv("WEBKIT_EXEC_PATH");
732 if (execDirectory) {
733 String parentDir = FileSystem::directoryName(FileSystem::stringFromFileSystemRepresentation(execDirectory));
734 bindIfExists(sandboxArgs, parentDir.utf8().data());
735 }
736
737 CString executablePath = getCurrentExecutablePath();
738 if (!executablePath.isNull()) {
739 // Our executable is `/foo/bar/bin/Process`, we want `/foo/bar` as a usable prefix
740 String parentDir = FileSystem::directoryName(FileSystem::directoryName(FileSystem::stringFromFileSystemRepresentation(executablePath.data())));
741 bindIfExists(sandboxArgs, parentDir.utf8().data());
742 }
743#endif
744
745 int seccompFd = setupSeccomp();
746 if (seccompFd != -1) {
747 GUniquePtr<char> fdStr(g_strdup_printf("%d", seccompFd));
748 sandboxArgs.appendVector(Vector<CString>({ "--seccomp", fdStr.get() }));
749 }
750
751 for (const String& path : extraPaths) {
752 ensureDirectoryExists(path);
753 if (path.isEmpty())
754 g_warning("Empty path passed to sandbox, this is probably a bug");
755 else {
756 const char* cpath = path.utf8().data();
757 sandboxArgs.appendVector(Vector<CString>({ "--bind", cpath, cpath }));
758 }
759 }
760
761 return {sandboxArgs, seccompFd};
762}
763
764#endif
765
766#if OS(LINUX)
767static bool isInsideFlatpak()
768{
769 static int ret = -1;
770 if (ret != -1)
771 return ret;
772
773 GUniquePtr<GKeyFile> infoFile(g_key_file_new());
774 if (!g_key_file_load_from_file(infoFile.get(), "/.flatpak-info", G_KEY_FILE_NONE, nullptr)) {
775 ret = false;
776 return ret;
777 }
778
779 // If we are in a `flatpak build` session we cannot launch ourselves since we aren't installed
780 ret = !g_key_file_get_boolean(infoFile.get(), "Instance", "build", nullptr);
781 return ret;
782}
783
784static Vector<CString> getFlatpakArgs(ProcessLauncher::ProcessType processType)
785{
786 // When we are running inside of flatpak's sandbox we do not have permissions to
787 // use the same sandbox we do outside but flatpak offers to create new sandboxes
788 // for us using flatpak-spawn.
789 //
790 // This is extremely bare-bones atm though as the Spawn interface does not expose
791 // much outside of `--sandbox` (no permissions) and `--no-network`. We need to
792 // add some permissions in between those for this to provide meaningful security.
793 Vector<CString> sandboxArgs = {
794 "/usr/bin/flatpak-spawn",
795 };
796
797 if (processType == ProcessLauncher::ProcessType::Storage)
798 sandboxArgs.append("--no-network");
799
800 return sandboxArgs;
801}
802#endif
803