diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-25 05:26:37 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-25 05:26:37 +0900 |
| commit | 61dbfeffe77df9c1a11413b3cb8954933e79b45f (patch) | |
| tree | 7f7f6bde9a8662848ad9392621316cbbc454a9f0 /sandbox | |
| parent | 532feb4bfaa84a57dc8de97f36b13a89610a0584 (diff) | |
sandbox/wl: move into sandbox
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'sandbox')
| -rw-r--r-- | sandbox/container.go | 1 | ||||
| -rw-r--r-- | sandbox/wl/conn.go | 121 | ||||
| -rw-r--r-- | sandbox/wl/consts.go | 15 | ||||
| -rw-r--r-- | sandbox/wl/wayland-bind.c | 88 | ||||
| -rw-r--r-- | sandbox/wl/wayland-bind.h | 3 | ||||
| -rw-r--r-- | sandbox/wl/wl.go | 36 |
6 files changed, 264 insertions, 0 deletions
diff --git a/sandbox/container.go b/sandbox/container.go index 31da929b..ee4f632e 100644 --- a/sandbox/container.go +++ b/sandbox/container.go @@ -1,3 +1,4 @@ +// Package sandbox implements unprivileged Linux container with hardening options useful for creating application sandboxes. package sandbox import ( diff --git a/sandbox/wl/conn.go b/sandbox/wl/conn.go new file mode 100644 index 00000000..9baa5abe --- /dev/null +++ b/sandbox/wl/conn.go @@ -0,0 +1,121 @@ +// Package wl implements Wayland security_context_v1 protocol. +package wl + +import ( + "errors" + "net" + "os" + "runtime" + "sync" + "syscall" +) + +type Conn struct { + conn *net.UnixConn + + done chan struct{} + doneOnce sync.Once + + mu sync.Mutex +} + +// Attach connects Conn to a wayland socket. +func (c *Conn) Attach(p string) (err error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn != nil { + return errors.New("attached") + } + + c.conn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: p, Net: "unix"}) + return +} + +// Close releases resources and closes the connection to the wayland compositor. +func (c *Conn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.done == nil { + return errors.New("no socket bound") + } + + c.doneOnce.Do(func() { + c.done <- struct{}{} + <-c.done + }) + + // closed by wayland + runtime.SetFinalizer(c.conn, nil) + return nil +} + +func (c *Conn) Bind(p, appID, instanceID string) (*os.File, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn == nil { + return nil, errors.New("not attached") + } + if c.done != nil { + return nil, errors.New("bound") + } + + if rc, err := c.conn.SyscallConn(); err != nil { + // unreachable + return nil, err + } else { + c.done = make(chan struct{}) + return bindRawConn(c.done, rc, p, appID, instanceID) + } +} + +func bindRawConn(done chan struct{}, rc syscall.RawConn, p, appID, instanceID string) (*os.File, error) { + var syncPipe [2]*os.File + + if r, w, err := os.Pipe(); err != nil { + return nil, err + } else { + syncPipe[0] = r + syncPipe[1] = w + } + + setupDone := make(chan error, 1) // does not block with c.done + + go func() { + if err := rc.Control(func(fd uintptr) { + // prevent runtime from closing the read end of sync fd + runtime.SetFinalizer(syncPipe[0], nil) + + // allow the Bind method to return after setup + setupDone <- bind(fd, p, appID, instanceID, syncPipe[0].Fd()) + close(setupDone) + + // keep socket alive until done is requested + <-done + runtime.KeepAlive(syncPipe[1]) + }); err != nil { + setupDone <- err + } + + // notify Close that rc.Control has returned + close(done) + }() + + // return write end of the pipe + return syncPipe[1], <-setupDone +} + +func bind(fd uintptr, p, appID, instanceID string, syncFd uintptr) error { + // ensure p is available + if f, err := os.Create(p); err != nil { + return err + } else if err = f.Close(); err != nil { + return err + } else if err = os.Remove(p); err != nil { + return err + } + + return bindWaylandFd(p, fd, appID, instanceID, syncFd) +} diff --git a/sandbox/wl/consts.go b/sandbox/wl/consts.go new file mode 100644 index 00000000..2ad4028c --- /dev/null +++ b/sandbox/wl/consts.go @@ -0,0 +1,15 @@ +package wl + +const ( + // WaylandDisplay contains the name of the server socket + // (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147) + // which is concatenated with XDG_RUNTIME_DIR + // (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1171) + // or used as-is if absolute + // (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1176). + WaylandDisplay = "WAYLAND_DISPLAY" + + // FallbackName is used as the wayland socket name if WAYLAND_DISPLAY is unset + // (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1149). + FallbackName = "wayland-0" +) diff --git a/sandbox/wl/wayland-bind.c b/sandbox/wl/wayland-bind.c new file mode 100644 index 00000000..01e8b882 --- /dev/null +++ b/sandbox/wl/wayland-bind.c @@ -0,0 +1,88 @@ +#include "wayland-bind.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <wayland-client.h> +#include "security-context-v1-protocol.h" + +static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + struct wp_security_context_manager_v1 **out = data; + + if (strcmp(interface, wp_security_context_manager_v1_interface.name) == 0) + *out = wl_registry_bind(registry, name, &wp_security_context_manager_v1_interface, 1); +} + +static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } // no-op + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, +}; + +int32_t f_bind_wayland_fd(char *socket_path, int fd, const char *app_id, const char *instance_id, int sync_fd) { + int32_t res = 0; // refer to resErr for meaning + + struct wl_display *display; + display = wl_display_connect_to_fd(fd); + if (!display) { + res = 1; + goto out; + }; + + struct wl_registry *registry; + registry = wl_display_get_registry(display); + + struct wp_security_context_manager_v1 *security_context_manager = NULL; + wl_registry_add_listener(registry, ®istry_listener, &security_context_manager); + int ret; + ret = wl_display_roundtrip(display); + wl_registry_destroy(registry); + if (ret < 0) + goto out; + + if (!security_context_manager) { + res = 2; + goto out; + } + + int listen_fd = -1; + listen_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (listen_fd < 0) + goto out; + + struct sockaddr_un sockaddr = {0}; + sockaddr.sun_family = AF_UNIX; + snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socket_path); + if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) + goto out; + + if (listen(listen_fd, 0) != 0) + goto out; + + struct wp_security_context_v1 *security_context; + security_context = wp_security_context_manager_v1_create_listener(security_context_manager, listen_fd, sync_fd); + wp_security_context_v1_set_sandbox_engine(security_context, "uk.gensokyo.fortify"); + wp_security_context_v1_set_app_id(security_context, app_id); + wp_security_context_v1_set_instance_id(security_context, instance_id); + wp_security_context_v1_commit(security_context); + wp_security_context_v1_destroy(security_context); + if (wl_display_roundtrip(display) < 0) + goto out; + +out: + if (listen_fd >= 0) + close(listen_fd); + if (security_context_manager) + wp_security_context_manager_v1_destroy(security_context_manager); + if (display) + wl_display_disconnect(display); + + free((void *)socket_path); + free((void *)app_id); + free((void *)instance_id); + return res; +} diff --git a/sandbox/wl/wayland-bind.h b/sandbox/wl/wayland-bind.h new file mode 100644 index 00000000..720dfe4e --- /dev/null +++ b/sandbox/wl/wayland-bind.h @@ -0,0 +1,3 @@ +#include <stdint.h> + +int32_t f_bind_wayland_fd(char *socket_path, int fd, const char *app_id, const char *instance_id, int sync_fd);
\ No newline at end of file diff --git a/sandbox/wl/wl.go b/sandbox/wl/wl.go new file mode 100644 index 00000000..f75f063d --- /dev/null +++ b/sandbox/wl/wl.go @@ -0,0 +1,36 @@ +package wl + +//go:generate sh -c "wayland-scanner client-header `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.h" +//go:generate sh -c "wayland-scanner private-code `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.c" + +/* +#cgo linux pkg-config: --static wayland-client +#cgo freebsd openbsd LDFLAGS: -lwayland-client + +#include "wayland-bind.h" +*/ +import "C" +import ( + "errors" + "strings" +) + +var ( + ErrContainsNull = errors.New("string contains null character") +) + +var resErr = [...]error{ + 0: nil, + 1: errors.New("wl_display_connect_to_fd() failed"), + 2: errors.New("wp_security_context_v1 not available"), +} + +func bindWaylandFd(socketPath string, fd uintptr, appID, instanceID string, syncFd uintptr) error { + if hasNull(appID) || hasNull(instanceID) { + return ErrContainsNull + } + res := C.f_bind_wayland_fd(C.CString(socketPath), C.int(fd), C.CString(appID), C.CString(instanceID), C.int(syncFd)) + return resErr[int32(res)] +} + +func hasNull(s string) bool { return strings.IndexByte(s, '\x00') > -1 } |
