f8b102a68e971cc34d3d914e84d2a4ac42f28221
[WebKit-https.git] / Source / WebKit / UIProcess / Launcher / glib / BubblewrapLauncher.cpp
1 /*
2  * Copyright (C) 2018 Igalia S.L.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19 #include "BubblewrapLauncher.h"
20
21 #if ENABLE(BUBBLEWRAP_SANDBOX)
22
23 #include <WebCore/PlatformDisplay.h>
24 #include <fcntl.h>
25 #include <glib.h>
26 #include <seccomp.h>
27 #include <sys/ioctl.h>
28 #include <wtf/FileSystem.h>
29 #include <wtf/glib/GLibUtilities.h>
30 #include <wtf/glib/GRefPtr.h>
31 #include <wtf/glib/GUniquePtr.h>
32
33 #if __has_include(<sys/memfd.h>)
34
35 #include <sys/memfd.h>
36
37 #else
38
39 // These defines were added in glibc 2.27, the same release that added memfd_create.
40 // But the kernel added all of this in Linux 3.17. So it's totally safe for us to
41 // depend on, as long as we define it all ourselves. Remove this once we depend on
42 // glibc 2.27.
43
44 #define F_ADD_SEALS 1033
45 #define F_GET_SEALS 1034
46
47 #define F_SEAL_SEAL   0x0001
48 #define F_SEAL_SHRINK 0x0002
49 #define F_SEAL_GROW   0x0004
50 #define F_SEAL_WRITE  0x0008
51
52 #define MFD_ALLOW_SEALING 2U
53
54 static int memfd_create(const char* name, unsigned flags)
55 {
56     return syscall(__NR_memfd_create, name, flags);
57 }
58 #endif
59
60 namespace WebKit {
61 using namespace WebCore;
62
63 static int createSealedMemFdWithData(const char* name, gconstpointer data, size_t size)
64 {
65     int fd = memfd_create(name, MFD_ALLOW_SEALING);
66     if (fd == -1) {
67         g_warning("memfd_create failed: %s", g_strerror(errno));
68         return -1;
69     }
70
71     ssize_t bytesWritten = write(fd, data, size);
72     if (bytesWritten < 0) {
73         g_warning("Writing args to memfd failed: %s", g_strerror(errno));
74         close(fd);
75         return -1;
76     }
77
78     if (static_cast<size_t>(bytesWritten) != size) {
79         g_warning("Failed to write all args to memfd");
80         close(fd);
81         return -1;
82     }
83
84     if (lseek(fd, 0, SEEK_SET) == -1) {
85         g_warning("lseek failed: %s", g_strerror(errno));
86         close(fd);
87         return -1;
88     }
89
90     if (fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) == -1) {
91         g_warning("Failed to seal memfd: %s", g_strerror(errno));
92         close(fd);
93         return -1;
94     }
95
96     return fd;
97 }
98
99 static int
100 argsToFd(const Vector<CString>& args, const char *name)
101 {
102     GString* buffer = g_string_new(nullptr);
103
104     for (const auto& arg : args)
105         g_string_append_len(buffer, arg.data(), arg.length() + 1); // Include NUL
106
107     GRefPtr<GBytes> bytes = adoptGRef(g_string_free_to_bytes(buffer));
108
109     size_t size;
110     gconstpointer data = g_bytes_get_data(bytes.get(), &size);
111
112     int memfd = createSealedMemFdWithData(name, data, size);
113     if (memfd == -1)
114         g_error("Failed to write memfd");
115
116     return memfd;
117 }
118
119 enum class DBusAddressType {
120     Normal,
121     Abstract,
122 };
123
124 class XDGDBusProxyLauncher {
125 public:
126     void setAddress(const char* dbusAddress, DBusAddressType addressType)
127     {
128         GUniquePtr<char> dbusPath = dbusAddressToPath(dbusAddress, addressType);
129         if (!dbusPath.get())
130             return;
131
132         GUniquePtr<char> appRunDir(g_build_filename(g_get_user_runtime_dir(), g_get_prgname(), nullptr));
133         m_proxyPath = makeProxyPath(appRunDir.get()).get();
134
135         m_socket = dbusAddress;
136         m_path = dbusPath.get();
137     }
138
139     bool isRunning() const { return m_isRunning; };
140     const CString& path() const { return m_path; };
141     const CString& proxyPath() const { return m_proxyPath; };
142
143     void setPermissions(Vector<CString>&& permissions)
144     {
145         RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!isRunning());
146         m_permissions = WTFMove(permissions);
147     };
148
149     void launch()
150     {
151         RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!isRunning());
152
153         if (m_socket.isNull() || m_path.isNull() || m_proxyPath.isNull())
154             return;
155
156         int syncFds[2];
157         if (pipe2 (syncFds, O_CLOEXEC) == -1)
158             g_error("Failed to make syncfds for dbus-proxy: %s", g_strerror(errno));
159
160         GUniquePtr<char> syncFdStr(g_strdup_printf("--fd=%d", syncFds[1]));
161
162         Vector<CString> proxyArgs = {
163             m_socket, m_proxyPath,
164             "--filter",
165             syncFdStr.get(),
166         };
167
168         if (!g_strcmp0(g_getenv("WEBKIT_ENABLE_DBUS_PROXY_LOGGING"), "1"))
169             proxyArgs.append("--log");
170
171         proxyArgs.appendVector(m_permissions);
172
173         int proxyFd = argsToFd(proxyArgs, "dbus-proxy");
174         GUniquePtr<char> proxyArgsStr(g_strdup_printf("--args=%d", proxyFd));
175
176         Vector<CString> args = {
177             DBUS_PROXY_EXECUTABLE,
178             proxyArgsStr.get(),
179         };
180
181         int nargs = args.size() + 1;
182         int i = 0;
183         char** argv = g_newa(char*, nargs);
184         for (const auto& arg : args)
185             argv[i++] = const_cast<char*>(arg.data());
186         argv[i] = nullptr;
187
188         GRefPtr<GSubprocessLauncher> launcher = adoptGRef(g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_INHERIT_FDS));
189         g_subprocess_launcher_set_child_setup(launcher.get(), childSetupFunc, GINT_TO_POINTER(syncFds[1]), nullptr);
190         g_subprocess_launcher_take_fd(launcher.get(), proxyFd, proxyFd);
191         g_subprocess_launcher_take_fd(launcher.get(), syncFds[1], syncFds[1]);
192         // We are purposefully leaving syncFds[0] open here.
193         // xdg-dbus-proxy will exit() itself once that is closed on our exit
194
195         GUniqueOutPtr<GError> error;
196         GRefPtr<GSubprocess> process = adoptGRef(g_subprocess_launcher_spawnv(launcher.get(), argv, &error.outPtr()));
197         if (!process.get())
198             g_error("Failed to start dbus proxy: %s", error->message);
199
200         char out;
201         // We need to ensure the proxy has created the socket.
202         // FIXME: This is more blocking IO.
203         if (read (syncFds[0], &out, 1) != 1)
204             g_error("Failed to fully launch dbus-proxy %s", g_strerror(errno));
205
206         m_isRunning = true;
207     };
208
209 private:
210     static void childSetupFunc(gpointer userdata)
211     {
212         int fd = GPOINTER_TO_INT(userdata);
213         fcntl(fd, F_SETFD, 0); // Unset CLOEXEC
214     }
215
216     static GUniquePtr<char> makeProxyPath(const char* appRunDir)
217     {
218         if (g_mkdir_with_parents(appRunDir, 0700) == -1) {
219             g_warning("Failed to mkdir for dbus proxy (%s): %s", appRunDir, g_strerror(errno));
220             return GUniquePtr<char>(nullptr);
221         }
222
223         GUniquePtr<char> proxySocketTemplate(g_build_filename(appRunDir, "dbus-proxy-XXXXXX", nullptr));
224         int fd;
225         if ((fd = g_mkstemp(proxySocketTemplate.get())) == -1) {
226             g_warning("Failed to make socket file for dbus proxy: %s", g_strerror(errno));
227             return GUniquePtr<char>(nullptr);
228         }
229
230         close(fd);
231         return proxySocketTemplate;
232     };
233
234     static GUniquePtr<char> dbusAddressToPath(const char* address, DBusAddressType addressType = DBusAddressType::Normal)
235     {
236         if (!address)
237             return nullptr;
238
239         if (!g_str_has_prefix(address, "unix:"))
240             return nullptr;
241
242         const char* path = strstr(address, addressType == DBusAddressType::Abstract ? "abstract=" : "path=");
243         if (!path)
244             return nullptr;
245
246         path += strlen(addressType == DBusAddressType::Abstract ? "abstract=" : "path=");
247         const char* pathEnd = path;
248         while (*pathEnd && *pathEnd != ',')
249             pathEnd++;
250
251         return GUniquePtr<char>(g_strndup(path, pathEnd - path));
252 }
253
254     CString m_socket;
255     CString m_path;
256     CString m_proxyPath;
257     bool m_isRunning;
258     Vector<CString> m_permissions;
259 };
260
261 enum class BindFlags {
262     ReadOnly,
263     ReadWrite,
264     Device,
265 };
266
267 static void bindIfExists(Vector<CString>& args, const char* path, BindFlags bindFlags = BindFlags::ReadOnly)
268 {
269     if (!path)
270         return;
271
272     const char* bindType;
273     if (bindFlags == BindFlags::Device)
274         bindType = "--dev-bind-try";
275     else if (bindFlags == BindFlags::ReadOnly)
276         bindType = "--ro-bind-try";
277     else
278         bindType = "--bind-try";
279     args.appendVector(Vector<CString>({ bindType, path, path }));
280 }
281
282 static void bindDBusSession(Vector<CString>& args, XDGDBusProxyLauncher& proxy)
283 {
284     if (!proxy.isRunning())
285         proxy.setAddress(g_getenv("DBUS_SESSION_BUS_ADDRESS"), DBusAddressType::Normal);
286
287     if (proxy.proxyPath().data()) {
288         args.appendVector(Vector<CString>({
289             "--bind", proxy.proxyPath(), proxy.path(),
290         }));
291     }
292 }
293
294 static void bindX11(Vector<CString>& args)
295 {
296     const char* display = g_getenv("DISPLAY");
297     if (!display || display[0] != ':' || !g_ascii_isdigit(const_cast<char*>(display)[1]))
298         display = ":0";
299     GUniquePtr<char> x11File(g_strdup_printf("/tmp/.X11-unix/X%s", display + 1));
300     bindIfExists(args, x11File.get(), BindFlags::ReadWrite);
301
302     const char* xauth = g_getenv("XAUTHORITY");
303     if (!xauth) {
304         const char* homeDir = g_get_home_dir();
305         GUniquePtr<char> xauthFile(g_build_filename(homeDir, ".Xauthority", nullptr));
306         bindIfExists(args, xauthFile.get());
307     } else
308         bindIfExists(args, xauth);
309 }
310
311 #if PLATFORM(WAYLAND) && USE(EGL)
312 static void bindWayland(Vector<CString>& args)
313 {
314     const char* display = g_getenv("WAYLAND_DISPLAY");
315     if (!display)
316         display = "wayland-0";
317
318     const char* runtimeDir = g_get_user_runtime_dir();
319     GUniquePtr<char> waylandRuntimeFile(g_build_filename(runtimeDir, display, nullptr));
320     bindIfExists(args, waylandRuntimeFile.get(), BindFlags::ReadWrite);
321 }
322 #endif
323
324 static void bindPulse(Vector<CString>& args)
325 {
326     // FIXME: The server can be defined in config files we'd have to parse.
327     // They can also be set as X11 props but that is getting a bit ridiculous.
328     const char* pulseServer = g_getenv("PULSE_SERVER");
329     if (pulseServer) {
330         if (g_str_has_prefix(pulseServer, "unix:"))
331             bindIfExists(args, pulseServer + 5, BindFlags::ReadWrite);
332         // else it uses tcp
333     } else {
334         const char* runtimeDir = g_get_user_runtime_dir();
335         GUniquePtr<char> pulseRuntimeDir(g_build_filename(runtimeDir, "pulse", nullptr));
336         bindIfExists(args, pulseRuntimeDir.get(), BindFlags::ReadWrite);
337     }
338
339     const char* pulseConfig = g_getenv("PULSE_CLIENTCONFIG");
340     if (pulseConfig)
341         bindIfExists(args, pulseConfig);
342
343     const char* configDir = g_get_user_config_dir();
344     GUniquePtr<char> pulseConfigDir(g_build_filename(configDir, "pulse", nullptr));
345     bindIfExists(args, pulseConfigDir.get());
346
347     const char* homeDir = g_get_home_dir();
348     GUniquePtr<char> pulseHomeConfigDir(g_build_filename(homeDir, ".pulse", nullptr));
349     GUniquePtr<char> asoundHomeConfigDir(g_build_filename(homeDir, ".asoundrc", nullptr));
350     bindIfExists(args, pulseHomeConfigDir.get());
351     bindIfExists(args, asoundHomeConfigDir.get());
352
353     // This is the ultimate fallback to raw ALSA
354     bindIfExists(args, "/dev/snd", BindFlags::Device);
355 }
356
357 static void bindFonts(Vector<CString>& args)
358 {
359     const char* configDir = g_get_user_config_dir();
360     const char* homeDir = g_get_home_dir();
361     const char* dataDir = g_get_user_data_dir();
362     const char* cacheDir = g_get_user_cache_dir();
363
364     // Configs can include custom dirs but then we have to parse them...
365     GUniquePtr<char> fontConfig(g_build_filename(configDir, "fontconfig", nullptr));
366     GUniquePtr<char> fontCache(g_build_filename(cacheDir, "fontconfig", nullptr));
367     GUniquePtr<char> fontHomeConfig(g_build_filename(homeDir, ".fonts.conf", nullptr));
368     GUniquePtr<char> fontHomeConfigDir(g_build_filename(configDir, ".fonts.conf.d", nullptr));
369     GUniquePtr<char> fontData(g_build_filename(dataDir, "fonts", nullptr));
370     GUniquePtr<char> fontHomeData(g_build_filename(homeDir, ".fonts", nullptr));
371     bindIfExists(args, fontConfig.get());
372     bindIfExists(args, fontCache.get(), BindFlags::ReadWrite);
373     bindIfExists(args, fontHomeConfig.get());
374     bindIfExists(args, fontHomeConfigDir.get());
375     bindIfExists(args, fontData.get());
376     bindIfExists(args, fontHomeData.get());
377 }
378
379 #if PLATFORM(GTK)
380 static void bindGtkData(Vector<CString>& args)
381 {
382     const char* configDir = g_get_user_config_dir();
383     const char* dataDir = g_get_user_data_dir();
384     const char* homeDir = g_get_home_dir();
385
386     GUniquePtr<char> gtkConfig(g_build_filename(configDir, "gtk-3.0", nullptr));
387     GUniquePtr<char> themeData(g_build_filename(dataDir, "themes", nullptr));
388     GUniquePtr<char> themeHomeData(g_build_filename(homeDir, ".themes", nullptr));
389     GUniquePtr<char> iconHomeData(g_build_filename(homeDir, ".icons", nullptr));
390     bindIfExists(args, gtkConfig.get());
391     bindIfExists(args, themeData.get());
392     bindIfExists(args, themeHomeData.get());
393     bindIfExists(args, iconHomeData.get());
394 }
395
396 static void bindA11y(Vector<CString>& args)
397 {
398     static XDGDBusProxyLauncher proxy;
399
400     if (!proxy.isRunning()) {
401         // FIXME: Avoid blocking IO... (It is at least a one-time cost)
402         GRefPtr<GDBusConnection> sessionBus = adoptGRef(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr));
403         if (!sessionBus.get())
404             return;
405
406         GRefPtr<GDBusMessage> msg = adoptGRef(g_dbus_message_new_method_call(
407             "org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress"));
408         g_dbus_message_set_body(msg.get(), g_variant_new("()"));
409         GRefPtr<GDBusMessage> reply = adoptGRef(g_dbus_connection_send_message_with_reply_sync(
410             sessionBus.get(), msg.get(),
411             G_DBUS_SEND_MESSAGE_FLAGS_NONE,
412             30000,
413             nullptr,
414             nullptr,
415             nullptr));
416
417         if (reply.get()) {
418             GUniqueOutPtr<GError> error;
419             if (g_dbus_message_to_gerror(reply.get(), &error.outPtr())) {
420                 if (!g_error_matches(error.get(), G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
421                     g_warning("Can't find a11y bus: %s", error->message);
422             } else {
423                 GUniqueOutPtr<char> a11yAddress;
424                 g_variant_get(g_dbus_message_get_body(reply.get()), "(s)", &a11yAddress.outPtr());
425                 proxy.setAddress(a11yAddress.get(), DBusAddressType::Abstract);
426             }
427         }
428
429         proxy.setPermissions({
430             "--sloppy-names",
431             "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root",
432             "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root",
433             "--call=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry",
434             "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetKeystrokeListeners@/org/a11y/atspi/registry/deviceeventcontroller",
435             "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetDeviceEventListeners@/org/a11y/atspi/registry/deviceeventcontroller",
436             "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersSync@/org/a11y/atspi/registry/deviceeventcontroller",
437             "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersAsync@/org/a11y/atspi/registry/deviceeventcontroller",
438         });
439
440         proxy.launch();
441     }
442
443     if (proxy.proxyPath().data()) {
444         args.appendVector(Vector<CString>({
445             "--bind", proxy.proxyPath(), proxy.path(),
446         }));
447     }
448 }
449 #endif
450
451 static bool bindPathVar(Vector<CString>& args, const char* varname)
452 {
453     const char* pathValue = g_getenv(varname);
454     if (!pathValue)
455         return false;
456
457     GUniquePtr<char*> splitPaths(g_strsplit(pathValue, ":", -1));
458     for (size_t i = 0; splitPaths.get()[i]; ++i)
459         bindIfExists(args, splitPaths.get()[i]);
460
461     return true;
462 }
463
464 static void bindGStreamerData(Vector<CString>& args)
465 {
466     if (!bindPathVar(args, "GST_PLUGIN_PATH_1_0"))
467         bindPathVar(args, "GST_PLUGIN_PATH");
468
469     if (!bindPathVar(args, "GST_PLUGIN_SYSTEM_PATH_1_0")) {
470         if (!bindPathVar(args, "GST_PLUGIN_SYSTEM_PATH")) {
471             GUniquePtr<char> gstData(g_build_filename(g_get_user_data_dir(), "gstreamer-1.0", nullptr));
472             bindIfExists(args, gstData.get());
473         }
474     }
475
476     GUniquePtr<char> gstCache(g_build_filename(g_get_user_cache_dir(), "gstreamer-1.0", nullptr));
477     bindIfExists(args, gstCache.get(), BindFlags::ReadWrite);
478
479     // /usr/lib is already added so this is only requried for other dirs
480     const char* scannerPath = g_getenv("GST_PLUGIN_SCANNER") ?: "/usr/libexec/gstreamer-1.0/gst-plugin-scanner";
481     const char* helperPath = g_getenv("GST_INSTALL_PLUGINS_HELPER ") ?: "/usr/libexec/gst-install-plugins-helper";
482
483     bindIfExists(args, scannerPath);
484     bindIfExists(args, helperPath);
485 }
486
487 static void bindOpenGL(Vector<CString>& args)
488 {
489     args.appendVector(Vector<CString>({
490         "--dev-bind-try", "/dev/dri", "/dev/dri",
491         // Mali
492         "--dev-bind-try", "/dev/mali", "/dev/mali",
493         "--dev-bind-try", "/dev/mali0", "/dev/mali0",
494         "--dev-bind-try", "/dev/umplock", "/dev/umplock",
495         // Nvidia
496         "--dev-bind-try", "/dev/nvidiactl", "/dev/nvidiactl",
497         "--dev-bind-try", "/dev/nvidia0", "/dev/nvidia0",
498         "--dev-bind-try", "/dev/nvidia", "/dev/nvidia",
499         // Adreno
500         "--dev-bind-try", "/dev/kgsl-3d0", "/dev/kgsl-3d0",
501         "--dev-bind-try", "/dev/ion", "/dev/ion",
502 #if PLATFORM(WPE)
503         "--dev-bind-try", "/dev/fb0", "/dev/fb0",
504         "--dev-bind-try", "/dev/fb1", "/dev/fb1",
505 #endif
506     }));
507 }
508
509 static void bindV4l(Vector<CString>& args)
510 {
511     args.appendVector(Vector<CString>({
512         "--dev-bind-try", "/dev/v4l", "/dev/v4l",
513         // Not pretty but a stop-gap for pipewire anyway.
514         "--dev-bind-try", "/dev/video0", "/dev/video0",
515         "--dev-bind-try", "/dev/video1", "/dev/video1",
516     }));
517 }
518
519 static void bindSymlinksRealPath(Vector<CString>& args, const char* path)
520 {
521     char realPath[PATH_MAX];
522
523     if (realpath(path, realPath) && strcmp(path, realPath)) {
524         args.appendVector(Vector<CString>({
525             "--ro-bind", realPath, realPath,
526         }));
527     }
528 }
529
530 static int setupSeccomp()
531 {
532     // NOTE: This is shared code (flatpak-run.c - LGPLv2.1+)
533     // There are today a number of different Linux container
534     // implementations. That will likely continue for long into the
535     // future. But we can still try to share code, and it's important
536     // to do so because it affects what library and application writers
537     // can do, and we should support code portability between different
538     // container tools.
539     //
540     // This syscall blacklist is copied from linux-user-chroot, which was in turn
541     // clearly influenced by the Sandstorm.io blacklist.
542     //
543     // If you make any changes here, I suggest sending the changes along
544     // to other sandbox maintainers. Using the libseccomp list is also
545     // an appropriate venue:
546     // https://groups.google.com/forum/#!topic/libseccomp
547     //
548     // A non-exhaustive list of links to container tooling that might
549     // want to share this blacklist:
550     //
551     //  https://github.com/sandstorm-io/sandstorm
552     //    in src/sandstorm/supervisor.c++
553     //  http://cgit.freedesktop.org/xdg-app/xdg-app/
554     //    in common/flatpak-run.c
555     //  https://git.gnome.org/browse/linux-user-chroot
556     //    in src/setup-seccomp.c
557     struct scmp_arg_cmp cloneArg = SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER);
558     struct scmp_arg_cmp ttyArg = SCMP_A1(SCMP_CMP_EQ, static_cast<scmp_datum_t>(TIOCSTI), static_cast<scmp_datum_t>(0));
559     struct {
560         int scall;
561         struct scmp_arg_cmp* arg;
562     } syscallBlacklist[] = {
563         // Block dmesg
564         { SCMP_SYS(syslog), nullptr },
565         // Useless old syscall.
566         { SCMP_SYS(uselib), nullptr },
567         // Don't allow disabling accounting.
568         { SCMP_SYS(acct), nullptr },
569         // 16-bit code is unnecessary in the sandbox, and modify_ldt is a
570         // historic source of interesting information leaks.
571         { SCMP_SYS(modify_ldt), nullptr },
572         // Don't allow reading current quota use.
573         { SCMP_SYS(quotactl), nullptr },
574
575         // Don't allow access to the kernel keyring.
576         { SCMP_SYS(add_key), nullptr },
577         { SCMP_SYS(keyctl), nullptr },
578         { SCMP_SYS(request_key), nullptr },
579
580         // Scary VM/NUMA ops 
581         { SCMP_SYS(move_pages), nullptr },
582         { SCMP_SYS(mbind), nullptr },
583         { SCMP_SYS(get_mempolicy), nullptr },
584         { SCMP_SYS(set_mempolicy), nullptr },
585         { SCMP_SYS(migrate_pages), nullptr },
586
587         // Don't allow subnamespace setups:
588         { SCMP_SYS(unshare), nullptr },
589         { SCMP_SYS(mount), nullptr },
590         { SCMP_SYS(pivot_root), nullptr },
591         { SCMP_SYS(clone), &cloneArg },
592
593         // Don't allow faking input to the controlling tty (CVE-2017-5226)
594         { SCMP_SYS(ioctl), &ttyArg },
595
596         // Profiling operations; we expect these to be done by tools from outside
597         // the sandbox. In particular perf has been the source of many CVEs.
598         { SCMP_SYS(perf_event_open), nullptr },
599         // Don't allow you to switch to bsd emulation or whatnot.
600         { SCMP_SYS(personality), nullptr },
601         { SCMP_SYS(ptrace), nullptr }
602     };
603
604     scmp_filter_ctx seccomp = seccomp_init(SCMP_ACT_ALLOW);
605     if (!seccomp)
606         g_error("Failed to init seccomp");
607
608     for (auto& rule : syscallBlacklist) {
609         int scall = rule.scall;
610         int r;
611         if (rule.arg)
612             r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), scall, 1, rule.arg);
613         else
614             r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM), scall, 0);
615         if (r == -EFAULT) {
616             seccomp_release(seccomp);
617             g_error("Failed to add seccomp rule");
618         }
619     }
620
621     int tmpfd = memfd_create("seccomp-bpf", 0);
622     if (tmpfd == -1) {
623         seccomp_release(seccomp);
624         g_error("Failed to create memfd: %s", g_strerror(errno));
625     }
626
627     if (seccomp_export_bpf(seccomp, tmpfd)) {
628         seccomp_release(seccomp);
629         close(tmpfd);
630         g_error("Failed to export seccomp bpf");
631     }
632
633     if (lseek(tmpfd, 0, SEEK_SET) < 0)
634         g_error("lseek failed: %s", g_strerror(errno));
635
636     seccomp_release(seccomp);
637     return tmpfd;
638 }
639
640 static int createFlatpakInfo()
641 {
642     GUniquePtr<GKeyFile> keyFile(g_key_file_new());
643
644     const char* sharedPermissions[] = { "network", nullptr };
645     g_key_file_set_string_list(keyFile.get(), "Context", "shared", sharedPermissions, sizeof(sharedPermissions));
646
647     // xdg-desktop-portal relates your name to certain permissions so we want
648     // them to be application unique which is best done via GApplication.
649     GApplication* app = g_application_get_default();
650     if (!app) {
651         g_warning("GApplication is required for xdg-desktop-portal access in the WebKit sandbox. Actions that require xdg-desktop-portal will be broken.");
652         return -1;
653     }
654     g_key_file_set_string(keyFile.get(), "Application", "name", g_application_get_application_id(app));
655
656     size_t size;
657     GUniqueOutPtr<GError> error;
658     GUniquePtr<char> data(g_key_file_to_data(keyFile.get(), &size, &error.outPtr()));
659     if (error.get()) {
660         g_warning("%s", error->message);
661         return -1;
662     }
663
664     return createSealedMemFdWithData("flatpak-info", data.get(), size);
665 }
666
667 GRefPtr<GSubprocess> bubblewrapSpawn(GSubprocessLauncher* launcher, const ProcessLauncher::LaunchOptions& launchOptions, char** argv, GError **error)
668 {
669     ASSERT(launcher);
670
671     // It is impossible to know what access arbitrary plugins need and since it is for legacy
672     // reasons lets just leave it unsandboxed.
673     if (launchOptions.processType == ProcessLauncher::ProcessType::Plugin64
674         || launchOptions.processType == ProcessLauncher::ProcessType::Plugin32)
675         return adoptGRef(g_subprocess_launcher_spawnv(launcher, argv, error));
676
677     // For now we are just considering the network process trusted as it
678     // requires a lot of access but doesn't execute arbitrary code like
679     // the WebProcess where our focus lies.
680     if (launchOptions.processType == ProcessLauncher::ProcessType::Network)
681         return adoptGRef(g_subprocess_launcher_spawnv(launcher, argv, error));
682
683     Vector<CString> sandboxArgs = {
684         "--die-with-parent",
685         "--unshare-pid",
686         "--unshare-uts",
687
688         // We assume /etc has safe permissions.
689         // At a later point we can start masking privacy-concerning files.
690         "--ro-bind", "/etc", "/etc",
691         "--dev", "/dev",
692         "--proc", "/proc",
693         "--tmpfs", "/tmp",
694         "--unsetenv", "TMPDIR",
695         "--dir", "/run",
696         "--symlink", "../run", "/var/run",
697         "--symlink", "../tmp", "/var/tmp",
698         "--ro-bind", "/sys/block", "/sys/block",
699         "--ro-bind", "/sys/bus", "/sys/bus",
700         "--ro-bind", "/sys/class", "/sys/class",
701         "--ro-bind", "/sys/dev", "/sys/dev",
702         "--ro-bind", "/sys/devices", "/sys/devices",
703
704         "--ro-bind-try", "/usr/share", "/usr/share",
705         "--ro-bind-try", "/usr/local/share", "/usr/local/share",
706         "--ro-bind-try", DATADIR, DATADIR,
707
708         // We only grant access to the libdirs webkit is built with and
709         // guess system libdirs. This will always have some edge cases.
710         "--ro-bind-try", "/lib", "/lib",
711         "--ro-bind-try", "/usr/lib", "/usr/lib",
712         "--ro-bind-try", "/usr/local/lib", "/usr/local/lib",
713         "--ro-bind-try", LIBDIR, LIBDIR,
714         "--ro-bind-try", "/lib64", "/lib64",
715         "--ro-bind-try", "/usr/lib64", "/usr/lib64",
716         "--ro-bind-try", "/usr/local/lib64", "/usr/local/lib64",
717
718         "--ro-bind-try", PKGLIBEXECDIR, PKGLIBEXECDIR,
719     };
720     // We would have to parse ld config files for more info.
721     bindPathVar(sandboxArgs, "LD_LIBRARY_PATH");
722
723     const char* libraryPath = g_getenv("LD_LIBRARY_PATH");
724     if (libraryPath && libraryPath[0]) {
725         // On distros using a suid bwrap it drops this env var
726         // so we have to pass it through to the children.
727         sandboxArgs.appendVector(Vector<CString>({
728             "--setenv", "LD_LIBRARY_PATH", libraryPath,
729         }));
730     }
731
732     bindSymlinksRealPath(sandboxArgs, "/etc/resolv.conf");
733     bindSymlinksRealPath(sandboxArgs, "/etc/localtime");
734
735     // xdg-desktop-portal defaults to assuming you are host application with
736     // full permissions unless it can identify you as a snap or flatpak.
737     // The easiest method is for us to pretend to be a flatpak and if that
738     // fails just blocking portals entirely as it just becomes a sandbox escape.
739     int flatpakInfoFd = createFlatpakInfo();
740     if (flatpakInfoFd != -1) {
741         g_subprocess_launcher_take_fd(launcher, flatpakInfoFd, flatpakInfoFd);
742         GUniquePtr<char> flatpakInfoFdStr(g_strdup_printf("%d", flatpakInfoFd));
743
744         sandboxArgs.appendVector(Vector<CString>({
745             "--ro-bind-data", flatpakInfoFdStr.get(), "/.flatpak-info"
746         }));
747     }
748
749     // NOTE: This has network access for HLS via GStreamer.
750     if (launchOptions.processType == ProcessLauncher::ProcessType::Web) {
751         static XDGDBusProxyLauncher proxy;
752
753         // If Wayland in use don't grant X11
754 #if PLATFORM(WAYLAND) && USE(EGL)
755         if (PlatformDisplay::sharedDisplay().type() == PlatformDisplay::Type::Wayland) {
756             bindWayland(sandboxArgs);
757             sandboxArgs.append("--unshare-ipc");
758         } else
759 #endif
760             bindX11(sandboxArgs);
761
762         for (const auto& pathAndPermission : launchOptions.extraWebProcessSandboxPaths) {
763             sandboxArgs.appendVector(Vector<CString>({
764                 pathAndPermission.value == SandboxPermission::ReadOnly ? "--ro-bind-try": "--bind-try",
765                 pathAndPermission.key, pathAndPermission.key
766             }));
767         }
768
769         Vector<String> extraPaths = { "applicationCacheDirectory", "waylandSocket"};
770         for (const auto& path : extraPaths) {
771             String extraPath = launchOptions.extraInitializationData.get(path);
772             if (!extraPath.isEmpty())
773                 sandboxArgs.appendVector(Vector<CString>({ "--bind-try", extraPath.utf8(), extraPath.utf8() }));
774         }
775
776         bindDBusSession(sandboxArgs, proxy);
777         // FIXME: We should move to Pipewire as soon as viable, Pulse doesn't restrict clients atm.
778         bindPulse(sandboxArgs);
779         bindFonts(sandboxArgs);
780         bindGStreamerData(sandboxArgs);
781         bindOpenGL(sandboxArgs);
782         // FIXME: This is also fixed by Pipewire once in use.
783         bindV4l(sandboxArgs);
784 #if PLATFORM(GTK)
785         bindA11y(sandboxArgs);
786         bindGtkData(sandboxArgs);
787 #endif
788
789         if (!proxy.isRunning()) {
790             Vector<CString> permissions = {
791                 // GStreamers plugin install helper.
792                 "--call=org.freedesktop.PackageKit=org.freedesktop.PackageKit.Modify2.InstallGStreamerResources@/org/freedesktop/PackageKit"
793             };
794             if (flatpakInfoFd != -1) {
795                 // xdg-desktop-portal used by GTK and us.
796                 permissions.append("--talk=org.freedesktop.portal.Desktop");
797             }
798             proxy.setPermissions(WTFMove(permissions));
799             proxy.launch();
800         }
801     } else {
802         // Only X11 users need this for XShm which is only the Web process.
803         sandboxArgs.append("--unshare-ipc");
804     }
805
806 #if ENABLE(DEVELOPER_MODE)
807     const char* execDirectory = g_getenv("WEBKIT_EXEC_PATH");
808     if (execDirectory) {
809         String parentDir = FileSystem::directoryName(FileSystem::stringFromFileSystemRepresentation(execDirectory));
810         bindIfExists(sandboxArgs, parentDir.utf8().data());
811     }
812
813     CString executablePath = getCurrentExecutablePath();
814     if (!executablePath.isNull()) {
815         // Our executable is `/foo/bar/bin/Process`, we want `/foo/bar` as a usable prefix
816         String parentDir = FileSystem::directoryName(FileSystem::directoryName(FileSystem::stringFromFileSystemRepresentation(executablePath.data())));
817         bindIfExists(sandboxArgs, parentDir.utf8().data());
818     }
819 #endif
820
821     int seccompFd = setupSeccomp();
822     GUniquePtr<char> fdStr(g_strdup_printf("%d", seccompFd));
823     g_subprocess_launcher_take_fd(launcher, seccompFd, seccompFd);
824     sandboxArgs.appendVector(Vector<CString>({ "--seccomp", fdStr.get() }));
825
826     int bwrapFd = argsToFd(sandboxArgs, "bwrap");
827     GUniquePtr<char> bwrapFdStr(g_strdup_printf("%d", bwrapFd));
828     g_subprocess_launcher_take_fd(launcher, bwrapFd, bwrapFd);
829
830     Vector<CString> bwrapArgs = {
831         BWRAP_EXECUTABLE,
832         "--args",
833         bwrapFdStr.get(),
834         "--",
835     };
836
837     char** newArgv = g_newa(char*, g_strv_length(argv) + bwrapArgs.size() + 1);
838     size_t i = 0;
839
840     for (auto& arg : bwrapArgs)
841         newArgv[i++] = const_cast<char*>(arg.data());
842     for (size_t x = 0; argv[x]; x++)
843         newArgv[i++] = argv[x];
844     newArgv[i++] = nullptr;
845
846     return adoptGRef(g_subprocess_launcher_spawnv(launcher, newArgv, error));
847 }
848
849 };
850
851 #endif // ENABLE(BUBBLEWRAP_SANDBOX)