Compare commits
2 Commits
c1ff73b1b1
...
22d577ab49
Author | SHA1 | Date | |
---|---|---|---|
22d577ab49 | |||
83a1c75f1a |
@ -28,8 +28,6 @@ type appInfo struct {
|
|||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
Net bool `json:"net,omitempty"`
|
Net bool `json:"net,omitempty"`
|
||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
ScopeAbstract bool `json:"scope_abstract,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Device bool `json:"dev,omitempty"`
|
Device bool `json:"dev,omitempty"`
|
||||||
// passed through to [hst.Config]
|
// passed through to [hst.Config]
|
||||||
Tty bool `json:"tty,omitempty"`
|
Tty bool `json:"tty,omitempty"`
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -15,7 +14,6 @@ import (
|
|||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container/landlock"
|
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -94,8 +92,6 @@ type (
|
|||||||
RetainSession bool
|
RetainSession bool
|
||||||
// Do not [syscall.CLONE_NEWNET].
|
// Do not [syscall.CLONE_NEWNET].
|
||||||
HostNet bool
|
HostNet bool
|
||||||
// Scope abstract UNIX domain sockets using LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET.
|
|
||||||
ScopeAbstract bool
|
|
||||||
// Retain CAP_SYS_ADMIN.
|
// Retain CAP_SYS_ADMIN.
|
||||||
Privileged bool
|
Privileged bool
|
||||||
}
|
}
|
||||||
@ -183,12 +179,6 @@ func (p *Container) Start() error {
|
|||||||
p.wait = make(chan struct{})
|
p.wait = make(chan struct{})
|
||||||
|
|
||||||
done <- func() error { // setup depending on per-thread state must happen here
|
done <- func() error { // setup depending on per-thread state must happen here
|
||||||
if p.ScopeAbstract {
|
|
||||||
if err := landlock.ScopeAbstract(); err != nil {
|
|
||||||
log.Fatalf("could not scope abstract unix sockets: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Verbose("starting container init")
|
msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return msg.WrapErr(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
package landlock
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <linux/landlock.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LANDLOCK_CREATE_RULESET_VERSION = C.LANDLOCK_CREATE_RULESET_VERSION
|
|
||||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
|
||||||
|
|
||||||
SYS_LANDLOCK_CREATE_RULESET = C.SYS_landlock_create_ruleset
|
|
||||||
SYS_LANDLOCK_RESTRICT_SELF = C.SYS_landlock_restrict_self
|
|
||||||
)
|
|
||||||
|
|
||||||
type LandlockRulesetAttr = C.struct_landlock_ruleset_attr
|
|
||||||
|
|
||||||
// ScopeAbstract calls landlock_restrict_self and must be called from a goroutine wired to an m
|
|
||||||
// with the process starting from the same goroutine.
|
|
||||||
func ScopeAbstract() error {
|
|
||||||
abi, _, err := syscall.Syscall(SYS_LANDLOCK_CREATE_RULESET, 0, 0, LANDLOCK_CREATE_RULESET_VERSION)
|
|
||||||
|
|
||||||
if err != 0 {
|
|
||||||
return fmt.Errorf("could not fetch landlock ABI: errno %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if abi < 6 {
|
|
||||||
return fmt.Errorf("landlock ABI must be >= 6, got %d", abi)
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs := LandlockRulesetAttr{
|
|
||||||
scoped: LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, _, err := syscall.Syscall(SYS_LANDLOCK_CREATE_RULESET, uintptr(unsafe.Pointer(&attrs)), unsafe.Sizeof(attrs), 0)
|
|
||||||
|
|
||||||
if err != 0 {
|
|
||||||
return fmt.Errorf("could not create landlock ruleset: errno %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer syscall.Close(int(fd))
|
|
||||||
|
|
||||||
r, _, err := syscall.Syscall(SYS_LANDLOCK_RESTRICT_SELF, fd, 0, 0)
|
|
||||||
if r != 0 {
|
|
||||||
return fmt.Errorf("could not restrict self via landlock: errno %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -79,8 +79,6 @@ type (
|
|||||||
Userns bool `json:"userns,omitempty"`
|
Userns bool `json:"userns,omitempty"`
|
||||||
// share host net namespace
|
// share host net namespace
|
||||||
Net bool `json:"net,omitempty"`
|
Net bool `json:"net,omitempty"`
|
||||||
// disallow accessing abstract UNIX domain sockets created outside the container
|
|
||||||
ScopeAbstract bool `json:"scope_abstract,omitempty"`
|
|
||||||
// allow dangerous terminal I/O
|
// allow dangerous terminal I/O
|
||||||
Tty bool `json:"tty,omitempty"`
|
Tty bool `json:"tty,omitempty"`
|
||||||
// allow multiarch
|
// allow multiarch
|
||||||
|
@ -33,7 +33,6 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
|||||||
SeccompPresets: s.SeccompPresets,
|
SeccompPresets: s.SeccompPresets,
|
||||||
RetainSession: s.Tty,
|
RetainSession: s.Tty,
|
||||||
HostNet: s.Net,
|
HostNet: s.Net,
|
||||||
ScopeAbstract: s.ScopeAbstract,
|
|
||||||
|
|
||||||
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
||||||
// this behaviour is implemented in the shim
|
// this behaviour is implemented in the shim
|
||||||
|
@ -387,10 +387,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
return hlog.WrapErr(ErrXDisplay,
|
return hlog.WrapErr(ErrXDisplay,
|
||||||
"DISPLAY is not set")
|
"DISPLAY is not set")
|
||||||
} else {
|
} else {
|
||||||
seal.sys.ChangeHosts("#" + seal.user.uid.String())
|
|
||||||
seal.env[display] = d
|
|
||||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
||||||
seal.container.Bind(socketDir, socketDir, 0)
|
|
||||||
|
|
||||||
// the socket file at `/tmp/.X11-unix/X%d` is typically owned by the priv user
|
// the socket file at `/tmp/.X11-unix/X%d` is typically owned by the priv user
|
||||||
// and not accessible by the target user
|
// and not accessible by the target user
|
||||||
@ -405,8 +402,20 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if socketPath != nil {
|
if socketPath != nil {
|
||||||
seal.sys.UpdatePermTypeOptional(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
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
|
||||||
|
seal.container.Bind(socketDir, socketDir, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,6 @@ in
|
|||||||
multiarch
|
multiarch
|
||||||
env
|
env
|
||||||
;
|
;
|
||||||
scope_abstract = app.scopeAbstract;
|
|
||||||
map_real_uid = app.mapRealUid;
|
map_real_uid = app.mapRealUid;
|
||||||
|
|
||||||
filesystem =
|
filesystem =
|
||||||
|
22
options.md
22
options.md
@ -572,28 +572,6 @@ boolean
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
*Example:*
|
|
||||||
` true `
|
|
||||||
|
|
||||||
|
|
||||||
## environment\.hakurei\.apps\.\<name>\.scopeAbstract
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Whether to restrict abstract UNIX domain socket access\.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*Type:*
|
|
||||||
boolean
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*Default:*
|
|
||||||
` true `
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*Example:*
|
*Example:*
|
||||||
` true `
|
` true `
|
||||||
|
|
||||||
|
@ -182,9 +182,6 @@ in
|
|||||||
net = mkEnableOption "network access" // {
|
net = mkEnableOption "network access" // {
|
||||||
default = true;
|
default = true;
|
||||||
};
|
};
|
||||||
scopeAbstract = mkEnableOption "abstract unix domain socket access" // {
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
nix = mkEnableOption "nix daemon access";
|
nix = mkEnableOption "nix daemon access";
|
||||||
mapRealUid = mkEnableOption "mapping to priv-user uid";
|
mapRealUid = mkEnableOption "mapping to priv-user uid";
|
||||||
|
@ -21,17 +21,7 @@ func (sys *I) UpdatePermType(et Enablement, path string, perms ...acl.Perm) *I {
|
|||||||
sys.lock.Lock()
|
sys.lock.Lock()
|
||||||
defer sys.lock.Unlock()
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
sys.ops = append(sys.ops, &ACL{et, path, perms, false})
|
sys.ops = append(sys.ops, &ACL{et, path, perms})
|
||||||
|
|
||||||
return sys
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdatePermTypeOptional appends an acl update Op that silently continues if the target does not exist.
|
|
||||||
func (sys *I) UpdatePermTypeOptional(et Enablement, path string, perms ...acl.Perm) *I {
|
|
||||||
sys.lock.Lock()
|
|
||||||
defer sys.lock.Unlock()
|
|
||||||
|
|
||||||
sys.ops = append(sys.ops, &ACL{et, path, perms, true})
|
|
||||||
|
|
||||||
return sys
|
return sys
|
||||||
}
|
}
|
||||||
@ -40,24 +30,14 @@ type ACL struct {
|
|||||||
et Enablement
|
et Enablement
|
||||||
path string
|
path string
|
||||||
perms acl.Perms
|
perms acl.Perms
|
||||||
|
|
||||||
// since revert operations are cross-process, the success of apply must not affect the outcome of revert
|
|
||||||
skipNotExist bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) Type() Enablement { return a.et }
|
func (a *ACL) Type() Enablement { return a.et }
|
||||||
|
|
||||||
func (a *ACL) apply(sys *I) error {
|
func (a *ACL) apply(sys *I) error {
|
||||||
msg.Verbose("applying ACL", a)
|
msg.Verbose("applying ACL", a)
|
||||||
if err := acl.Update(a.path, sys.uid, a.perms...); err != nil {
|
return wrapErrSuffix(acl.Update(a.path, sys.uid, a.perms...),
|
||||||
if !a.skipNotExist || !os.IsNotExist(err) {
|
|
||||||
return wrapErrSuffix(err,
|
|
||||||
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
||||||
}
|
|
||||||
msg.Verbosef("path %q does not exist", a.path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
||||||
|
@ -20,7 +20,7 @@ func TestUpdatePerm(t *testing.T) {
|
|||||||
t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) {
|
t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) {
|
||||||
sys := New(150)
|
sys := New(150)
|
||||||
sys.UpdatePerm(tc.path, tc.perms...)
|
sys.UpdatePerm(tc.path, tc.perms...)
|
||||||
(&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms, false}}, "UpdatePerm")
|
(&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms}}, "UpdatePerm")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ func TestUpdatePermType(t *testing.T) {
|
|||||||
t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) {
|
t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) {
|
||||||
sys := New(150)
|
sys := New(150)
|
||||||
sys.UpdatePermType(tc.et, tc.path, tc.perms...)
|
sys.UpdatePermType(tc.et, tc.path, tc.perms...)
|
||||||
tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms, false}}, "UpdatePermType")
|
tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms}}, "UpdatePermType")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,6 @@ func (p *Proxy) Start() error {
|
|||||||
argF, func(z *container.Container) {
|
argF, func(z *container.Container) {
|
||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
z.SeccompPresets |= seccomp.PresetStrict
|
z.SeccompPresets |= seccomp.PresetStrict
|
||||||
|
|
||||||
// xdg-dbus-proxy requires host abstract UNIX domain socket access
|
|
||||||
z.ScopeAbstract = false
|
|
||||||
|
|
||||||
z.Hostname = "hakurei-dbus"
|
z.Hostname = "hakurei-dbus"
|
||||||
if p.output != nil {
|
if p.output != nil {
|
||||||
z.Stdout, z.Stderr = p.output, p.output
|
z.Stdout, z.Stderr = p.output, p.output
|
||||||
|
@ -36,7 +36,7 @@ in
|
|||||||
want = {
|
want = {
|
||||||
env = [
|
env = [
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
"DISPLAY=:0"
|
"DISPLAY=unix:/tmp/.X11-unix/X0"
|
||||||
"HOME=/var/lib/hakurei/u0/a4"
|
"HOME=/var/lib/hakurei/u0/a4"
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
@ -243,7 +243,7 @@ in
|
|||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|
||||||
try_socket = "/tmp/.X11-unix/X0";
|
try_socket = "/tmp/.X11-unix/X0";
|
||||||
socket_abstract = false;
|
socket_abstract = true;
|
||||||
socket_pathname = true;
|
socket_pathname = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ in
|
|||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|
||||||
try_socket = "/tmp/.X11-unix/X0";
|
try_socket = "/tmp/.X11-unix/X0";
|
||||||
socket_abstract = false;
|
socket_abstract = true;
|
||||||
socket_pathname = false;
|
socket_pathname = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ in
|
|||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|
||||||
try_socket = "/tmp/.X11-unix/X0";
|
try_socket = "/tmp/.X11-unix/X0";
|
||||||
socket_abstract = false;
|
socket_abstract = true;
|
||||||
socket_pathname = false;
|
socket_pathname = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ in
|
|||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|
||||||
try_socket = "/tmp/.X11-unix/X0";
|
try_socket = "/tmp/.X11-unix/X0";
|
||||||
socket_abstract = false;
|
socket_abstract = true;
|
||||||
socket_pathname = false;
|
socket_pathname = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ in
|
|||||||
want = {
|
want = {
|
||||||
env = [
|
env = [
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
"DISPLAY=:0"
|
"DISPLAY=unix:/tmp/.X11-unix/X0"
|
||||||
"HOME=/var/lib/hakurei/u0/a2"
|
"HOME=/var/lib/hakurei/u0/a2"
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
||||||
"SHELL=/run/current-system/sw/bin/bash"
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
@ -275,7 +275,7 @@ in
|
|||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|
||||||
try_socket = "/tmp/.X11-unix/X0";
|
try_socket = "/tmp/.X11-unix/X0";
|
||||||
socket_abstract = false;
|
socket_abstract = true;
|
||||||
socket_pathname = true;
|
socket_pathname = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ def swaymsg(command: str = "", succeed=True, type="command"):
|
|||||||
|
|
||||||
def check_filter(check_offset, name, pname):
|
def check_filter(check_offset, name, pname):
|
||||||
pid = int(machine.wait_until_succeeds(f"pgrep -U {1000000+check_offset} -x {pname}", timeout=15))
|
pid = int(machine.wait_until_succeeds(f"pgrep -U {1000000+check_offset} -x {pname}", timeout=15))
|
||||||
hash = machine.succeed(f"sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-1 check-sandbox-{name} hash 2>/dev/null")
|
hash = machine.succeed(f"sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-1 check-sandbox-{name} hash")
|
||||||
print(machine.succeed(f"hakurei-test -s {hash} filter {pid}"))
|
print(machine.succeed(f"hakurei-test -s {hash} filter {pid}"))
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user