aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-08-18 02:24:56 +0900
committerOphestra <cat@gensokyo.uk>2025-08-18 11:30:58 +0900
commit83a1c75f1ac4d2d611b3a96474ed07df3cb557b6 (patch)
treeca9b0aba65d1453c24c419781cc8ed7377b77d74
parent0ac6e998188e41d90ebc2b479a7e440361a3eecc (diff)
app: set up acl on X11 socket
The socket is typically owned by the priv-user, and inaccessible by the target user, so just allowing access to the directory is not enough. This change fixes this oversight and add checks that will also be useful for merging https://git.gensokyo.uk/security/hakurei/pulls/1. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--internal/app/seal_linux.go28
-rw-r--r--test/sandbox/assert.go46
-rw-r--r--test/sandbox/case/default.nix3
-rw-r--r--test/sandbox/case/device.nix11
-rw-r--r--test/sandbox/case/mapuid.nix5
-rw-r--r--test/sandbox/case/pdlike.nix5
-rw-r--r--test/sandbox/case/preset.nix5
-rw-r--r--test/sandbox/case/tty.nix11
8 files changed, 111 insertions, 3 deletions
diff --git a/internal/app/seal_linux.go b/internal/app/seal_linux.go
index 49ef78f7..c7293720 100644
--- a/internal/app/seal_linux.go
+++ b/internal/app/seal_linux.go
@@ -12,6 +12,7 @@ import (
"path"
"regexp"
"slices"
+ "strconv"
"strings"
"sync/atomic"
"syscall"
@@ -386,9 +387,34 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
return hlog.WrapErr(ErrXDisplay,
"DISPLAY is not set")
} else {
+ socketDir := container.AbsFHSTmp.Append(".X11-unix")
+
+ // the socket file at `/tmp/.X11-unix/X%d` is typically owned by the priv user
+ // and not accessible by the target user
+ var socketPath *container.Absolute
+ if len(d) > 1 && d[0] == ':' { // `:%d`
+ if n, err := strconv.Atoi(d[1:]); err == nil && n >= 0 {
+ socketPath = socketDir.Append("X" + strconv.Itoa(n))
+ }
+ } else if len(d) > 5 && strings.HasPrefix(d, "unix:") { // `unix:%s`
+ if a, err := container.NewAbs(d[5:]); err == nil {
+ socketPath = a
+ }
+ }
+ if socketPath != nil {
+ if _, err := sys.Stat(socketPath.String()); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return hlog.WrapErrSuffix(err,
+ fmt.Sprintf("cannot access X11 socket %q:", socketPath))
+ }
+ } else {
+ seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
+ d = "unix:" + socketPath.String()
+ }
+ }
+
seal.sys.ChangeHosts("#" + seal.user.uid.String())
seal.env[display] = d
- socketDir := container.AbsFHSTmp.Append(".X11-unix")
seal.container.Bind(socketDir, socketDir, 0)
}
}
diff --git a/test/sandbox/assert.go b/test/sandbox/assert.go
index 57d17039..2c2c9f20 100644
--- a/test/sandbox/assert.go
+++ b/test/sandbox/assert.go
@@ -15,6 +15,7 @@ import (
"errors"
"io/fs"
"log"
+ "net"
"os"
"syscall"
)
@@ -33,6 +34,10 @@ type TestCase struct {
FS *FS `json:"fs"`
Mount []*MountinfoEntry `json:"mount"`
Seccomp bool `json:"seccomp"`
+
+ TrySocket string `json:"try_socket,omitempty"`
+ SocketAbstract bool `json:"socket_abstract,omitempty"`
+ SocketPathname bool `json:"socket_pathname,omitempty"`
}
type T struct {
@@ -125,6 +130,47 @@ func (t *T) MustCheck(want *TestCase) {
} else {
printf("[SKIP] skipping seccomp check")
}
+
+ if want.TrySocket != "" {
+ abstractConn, abstractErr := net.Dial("unix", "@"+want.TrySocket)
+ pathnameConn, pathnameErr := net.Dial("unix", want.TrySocket)
+ ok := true
+
+ if abstractErr == nil {
+ if err := abstractConn.Close(); err != nil {
+ ok = false
+ log.Printf("Close: %v", err)
+ }
+ }
+ if pathnameErr == nil {
+ if err := pathnameConn.Close(); err != nil {
+ ok = false
+ log.Printf("Close: %v", err)
+ }
+ }
+
+ abstractWantErr := error(syscall.EPERM)
+ pathnameWantErr := error(syscall.ENOENT)
+ if want.SocketAbstract {
+ abstractWantErr = nil
+ }
+ if want.SocketPathname {
+ pathnameWantErr = nil
+ }
+
+ if !errors.Is(abstractErr, abstractWantErr) {
+ ok = false
+ log.Printf("abstractErr: %v, want %v", abstractErr, abstractWantErr)
+ }
+ if !errors.Is(pathnameErr, pathnameWantErr) {
+ ok = false
+ log.Printf("pathnameErr: %v, want %v", pathnameErr, pathnameWantErr)
+ }
+
+ if !ok {
+ os.Exit(1)
+ }
+ }
}
func MustCheckFilter(pid int, want string) {
diff --git a/test/sandbox/case/default.nix b/test/sandbox/case/default.nix
index 6db1ff45..ec814aab 100644
--- a/test/sandbox/case/default.nix
+++ b/test/sandbox/case/default.nix
@@ -50,6 +50,9 @@ let
useCommonPaths
userns
;
+ enablements = {
+ inherit (tc) x11;
+ };
share = testProgram;
packages = [ ];
path = "${testProgram}/bin/hakurei-test";
diff --git a/test/sandbox/case/device.nix b/test/sandbox/case/device.nix
index fb2a54f2..e9cf724b 100644
--- a/test/sandbox/case/device.nix
+++ b/test/sandbox/case/device.nix
@@ -25,6 +25,7 @@ in
mapRealUid = false;
useCommonPaths = true;
userns = false;
+ x11 = true;
# 0, PresetStrict
expectedFilter = {
@@ -35,6 +36,7 @@ in
want = {
env = [
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
+ "DISPLAY=unix:/tmp/.X11-unix/X0"
"HOME=/var/lib/hakurei/u0/a4"
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
"SHELL=/run/current-system/sw/bin/bash"
@@ -161,7 +163,9 @@ in
} null;
devices = fs "800001ed" null null;
} null;
- tmp = fs "800001f8" { } null;
+ tmp = fs "800001f8" {
+ ".X11-unix" = fs "801001ff" { X0 = fs "10001fd" null null; } null;
+ } null;
usr = fs "800001c0" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null;
var = fs "800001c0" {
lib = fs "800001c0" {
@@ -231,10 +235,15 @@ in
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
+ (ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
];
seccomp = true;
+
+ try_socket = "/tmp/.X11-unix/X0";
+ socket_abstract = true;
+ socket_pathname = true;
};
}
diff --git a/test/sandbox/case/mapuid.nix b/test/sandbox/case/mapuid.nix
index 1069f9fa..18d82e77 100644
--- a/test/sandbox/case/mapuid.nix
+++ b/test/sandbox/case/mapuid.nix
@@ -34,6 +34,7 @@ in
mapRealUid = true;
useCommonPaths = true;
userns = false;
+ x11 = false;
# 0, PresetStrict
expectedFilter = {
@@ -266,5 +267,9 @@ in
];
seccomp = true;
+
+ try_socket = "/tmp/.X11-unix/X0";
+ socket_abstract = true;
+ socket_pathname = false;
};
}
diff --git a/test/sandbox/case/pdlike.nix b/test/sandbox/case/pdlike.nix
index 4f0e71c4..da6753f8 100644
--- a/test/sandbox/case/pdlike.nix
+++ b/test/sandbox/case/pdlike.nix
@@ -34,6 +34,7 @@ in
mapRealUid = false;
useCommonPaths = false;
userns = true;
+ x11 = false;
# 0, PresetExt | PresetDenyDevel
expectedFilter = {
@@ -261,5 +262,9 @@ in
];
seccomp = true;
+
+ try_socket = "/tmp/.X11-unix/X0";
+ socket_abstract = true;
+ socket_pathname = false;
};
}
diff --git a/test/sandbox/case/preset.nix b/test/sandbox/case/preset.nix
index 60257ad0..be376a17 100644
--- a/test/sandbox/case/preset.nix
+++ b/test/sandbox/case/preset.nix
@@ -34,6 +34,7 @@ in
mapRealUid = false;
useCommonPaths = false;
userns = false;
+ x11 = false;
# 0, PresetStrict
expectedFilter = {
@@ -259,5 +260,9 @@ in
];
seccomp = true;
+
+ try_socket = "/tmp/.X11-unix/X0";
+ socket_abstract = true;
+ socket_pathname = false;
};
}
diff --git a/test/sandbox/case/tty.nix b/test/sandbox/case/tty.nix
index 267ac661..ff7bf035 100644
--- a/test/sandbox/case/tty.nix
+++ b/test/sandbox/case/tty.nix
@@ -34,6 +34,7 @@ in
mapRealUid = false;
useCommonPaths = true;
userns = false;
+ x11 = true;
# 0, PresetExt | PresetDenyNS | PresetDenyDevel
expectedFilter = {
@@ -44,6 +45,7 @@ in
want = {
env = [
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
+ "DISPLAY=unix:/tmp/.X11-unix/X0"
"HOME=/var/lib/hakurei/u0/a2"
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
"SHELL=/run/current-system/sw/bin/bash"
@@ -188,7 +190,9 @@ in
} null;
devices = fs "800001ed" null null;
} null;
- tmp = fs "800001f8" { } null;
+ tmp = fs "800001f8" {
+ ".X11-unix" = fs "801001ff" { X0 = fs "10001fd" null null; } null;
+ } null;
usr = fs "800001c0" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null;
var = fs "800001c0" {
lib = fs "800001c0" {
@@ -263,10 +267,15 @@ in
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
+ (ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
];
seccomp = true;
+
+ try_socket = "/tmp/.X11-unix/X0";
+ socket_abstract = true;
+ socket_pathname = true;
};
}