diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go index 232b323..7215253 100644 --- a/cmd/hakurei/command.go +++ b/cmd/hakurei/command.go @@ -81,6 +81,8 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr flagHomeDir string flagUserName string + flagPrivateRuntime, flagPrivateTmpdir bool + flagWayland, flagX11, flagDBus, flagPulse bool ) @@ -211,6 +213,13 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr } } + if !flagPrivateRuntime { + config.Container.Flags |= hst.FShareRuntime + } + if !flagPrivateTmpdir { + config.Container.Flags |= hst.FShareTmpdir + } + // parse D-Bus config file from flags if applicable if flagDBus { if flagDBusConfigSession == "builtin" { @@ -264,6 +273,10 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr "Container home directory"). Flag(&flagUserName, "u", command.StringFlag("chronos"), "Passwd user name within sandbox"). + Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false), + "Do not share XDG_RUNTIME_DIR between containers under the same identity"). + Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false), + "Do not share TMPDIR between containers under the same identity"). Flag(&flagWayland, "wayland", command.BoolFlag(false), "Enable connection to Wayland via security-context-v1"). Flag(&flagX11, "X", command.BoolFlag(false), diff --git a/cmd/hakurei/command_test.go b/cmd/hakurei/command_test.go index 66960d9..e489291 100644 --- a/cmd/hakurei/command_test.go +++ b/cmd/hakurei/command_test.go @@ -36,7 +36,7 @@ Commands: }, { "run", []string{"run", "-h"}, ` -Usage: hakurei run [-h | --help] [--dbus-config ] [--dbus-system ] [--mpris] [--dbus-log] [--id ] [-a ] [-g ] [-d ] [-u ] [--wayland] [-X] [--dbus] [--pulse] COMMAND [OPTIONS] +Usage: hakurei run [-h | --help] [--dbus-config ] [--dbus-system ] [--mpris] [--dbus-log] [--id ] [-a ] [-g ] [-d ] [-u ] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pulse] COMMAND [OPTIONS] Flags: -X Enable direct connection to X11 @@ -58,6 +58,10 @@ Flags: Reverse-DNS style Application identifier, leave empty to inherit instance identifier -mpris Allow owning MPRIS D-Bus path, has no effect if custom config is available + -private-runtime + Do not share XDG_RUNTIME_DIR between containers under the same identity + -private-tmpdir + Do not share TMPDIR between containers under the same identity -pulse Enable direct connection to PulseAudio -u string diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go index da2db0d..caea899 100644 --- a/cmd/hakurei/print_test.go +++ b/cmd/hakurei/print_test.go @@ -332,7 +332,9 @@ App "tty": true, "multiarch": true, "map_real_uid": true, - "device": true + "device": true, + "share_runtime": true, + "share_tmpdir": true } }, "time": "1970-01-01T00:00:00.000000009Z" @@ -482,7 +484,9 @@ App "tty": true, "multiarch": true, "map_real_uid": true, - "device": true + "device": true, + "share_runtime": true, + "share_tmpdir": true } } `, true}, @@ -692,7 +696,9 @@ func TestPrintPs(t *testing.T) { "tty": true, "multiarch": true, "map_real_uid": true, - "device": true + "device": true, + "share_runtime": true, + "share_tmpdir": true } }, "time": "1970-01-01T00:00:00.000000009Z" diff --git a/cmd/hpkg/app.go b/cmd/hpkg/app.go index 350eaaa..17179dc 100644 --- a/cmd/hpkg/app.go +++ b/cmd/hpkg/app.go @@ -130,6 +130,7 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv [] if app.Multiarch { config.Container.Flags |= hst.FMultiarch } + config.Container.Flags |= hst.FShareRuntime | hst.FShareTmpdir return config } diff --git a/dist/comp/_hakurei b/dist/comp/_hakurei index 7f3974b..d1c7d4f 100644 --- a/dist/comp/_hakurei +++ b/dist/comp/_hakurei @@ -12,6 +12,8 @@ _hakurei_run() { '-g[Groups inherited by all container processes]: :_groups' \ '-d[Container home directory]: :_files -/' \ '-u[Passwd user name within sandbox]: :_users' \ + '--private-runtime[Do not share XDG_RUNTIME_DIR between containers under the same identity]' \ + '--private-tmpdir[Do not share TMPDIR between containers under the same identity]' \ '--wayland[Enable connection to Wayland via security-context-v1]' \ '-X[Enable direct connection to X11]' \ '--dbus[Enable proxied connection to D-Bus]' \ diff --git a/hst/container.go b/hst/container.go index 94017bf..617d22d 100644 --- a/hst/container.go +++ b/hst/container.go @@ -63,6 +63,11 @@ const ( // FDevice mount /dev/ from the init mount namespace as-is in the container mount namespace. FDevice + // FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity. + FShareRuntime + // FShareTmpdir shares TMPDIR between containers under the same identity. + FShareTmpdir + fMax // FAll is [ContainerConfig.Flags] with all currently defined bits set. @@ -133,6 +138,11 @@ type containerConfigJSON = struct { // Corresponds to [FDevice]. Device bool `json:"device,omitempty"` + + // Corresponds to [FShareRuntime]. + ShareRuntime bool `json:"share_runtime,omitempty"` + // Corresponds to [FShareTmpdir] + ShareTmpdir bool `json:"share_tmpdir,omitempty"` } func (c *ContainerConfig) MarshalJSON() ([]byte, error) { @@ -151,6 +161,8 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) { Multiarch: c.Flags&FMultiarch != 0, MapRealUID: c.Flags&FMapRealUID != 0, Device: c.Flags&FDevice != 0, + ShareRuntime: c.Flags&FShareRuntime != 0, + ShareTmpdir: c.Flags&FShareTmpdir != 0, }) } @@ -192,5 +204,11 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error { if v.Device { c.Flags |= FDevice } + if v.ShareRuntime { + c.Flags |= FShareRuntime + } + if v.ShareTmpdir { + c.Flags |= FShareTmpdir + } return nil } diff --git a/hst/container_test.go b/hst/container_test.go index 049d354..cdeb502 100644 --- a/hst/container_test.go +++ b/hst/container_test.go @@ -28,7 +28,7 @@ func TestContainerConfig(t *testing.T) { {"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID}, `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`}, {"all", &hst.ContainerConfig{Flags: hst.FAll}, - `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true}`}, + `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"share_runtime":true,"share_tmpdir":true}`}, } for _, tc := range testCases { diff --git a/hst/hst_test.go b/hst/hst_test.go index 0dd7488..b487dd5 100644 --- a/hst/hst_test.go +++ b/hst/hst_test.go @@ -244,7 +244,9 @@ func TestTemplate(t *testing.T) { "tty": true, "multiarch": true, "map_real_uid": true, - "device": true + "device": true, + "share_runtime": true, + "share_tmpdir": true } }` diff --git a/internal/app/app_test.go b/internal/app/app_test.go index ab01798..6501091 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -70,7 +70,7 @@ func TestApp(t *testing.T) { Path: m("/run/current-system/sw/bin/zsh"), Args: []string{"/run/current-system/sw/bin/zsh"}, - Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty, + Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty | hst.FShareRuntime | hst.FShareTmpdir, }}, state.ID{ 0x4a, 0x45, 0x0b, 0x65, @@ -193,7 +193,7 @@ func TestApp(t *testing.T) { Path: m("/run/current-system/sw/bin/zsh"), Args: []string{"zsh", "-c", "exec chromium "}, - Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty, + Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty | hst.FShareRuntime | hst.FShareTmpdir, }, }, state.ID{ @@ -331,7 +331,7 @@ func TestApp(t *testing.T) { Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), - Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID, + Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FShareRuntime | hst.FShareTmpdir, }, SystemBus: &hst.BusConfig{ Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"}, diff --git a/internal/app/spruntime.go b/internal/app/spruntime.go index 35ebff0..d8c5af9 100644 --- a/internal/app/spruntime.go +++ b/internal/app/spruntime.go @@ -72,11 +72,13 @@ type spRuntimeOp struct { } func (s *spRuntimeOp) toSystem(state *outcomeStateSys) error { - runtimeDir, runtimeDirInst := s.commonPaths(state.outcomeState) - state.sys.Ensure(runtimeDir, 0700) - state.sys.UpdatePermType(system.User, runtimeDir, acl.Execute) - state.sys.Ensure(runtimeDirInst, 0700) - state.sys.UpdatePermType(system.User, runtimeDirInst, acl.Read, acl.Write, acl.Execute) + if state.Container.Flags&hst.FShareRuntime != 0 { + runtimeDir, runtimeDirInst := s.commonPaths(state.outcomeState) + state.sys.Ensure(runtimeDir, 0700) + state.sys.UpdatePermType(system.User, runtimeDir, acl.Execute) + state.sys.Ensure(runtimeDirInst, 0700) + state.sys.UpdatePermType(system.User, runtimeDirInst, acl.Read, acl.Write, acl.Execute) + } if state.et&hst.EWayland != 0 { s.SessionType = sessionTypeWayland @@ -106,10 +108,13 @@ func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error { } - _, runtimeDirInst := s.commonPaths(state.outcomeState) - state.params. - Tmpfs(fhs.AbsRunUser, 1<<12, 0755). - Bind(runtimeDirInst, state.runtimeDir, bits.BindWritable) + state.params.Tmpfs(fhs.AbsRunUser, 1<<12, 0755) + if state.Container.Flags&hst.FShareRuntime != 0 { + _, runtimeDirInst := s.commonPaths(state.outcomeState) + state.params.Bind(runtimeDirInst, state.runtimeDir, bits.BindWritable) + } else { + state.params.Mkdir(state.runtimeDir, 0700) + } return nil } diff --git a/internal/app/sptmpdir.go b/internal/app/sptmpdir.go index cb49f78..b926f46 100644 --- a/internal/app/sptmpdir.go +++ b/internal/app/sptmpdir.go @@ -6,6 +6,7 @@ import ( "hakurei.app/container/bits" "hakurei.app/container/check" "hakurei.app/container/fhs" + "hakurei.app/hst" "hakurei.app/system" "hakurei.app/system/acl" ) @@ -16,18 +17,23 @@ func init() { gob.Register(spTmpdirOp{}) } type spTmpdirOp struct{} func (s spTmpdirOp) toSystem(state *outcomeStateSys) error { - tmpdir, tmpdirInst := s.commonPaths(state.outcomeState) - state.sys.Ensure(tmpdir, 0700) - state.sys.UpdatePermType(system.User, tmpdir, acl.Execute) - state.sys.Ensure(tmpdirInst, 01700) - state.sys.UpdatePermType(system.User, tmpdirInst, acl.Read, acl.Write, acl.Execute) + if state.Container.Flags&hst.FShareTmpdir != 0 { + tmpdir, tmpdirInst := s.commonPaths(state.outcomeState) + state.sys.Ensure(tmpdir, 0700) + state.sys.UpdatePermType(system.User, tmpdir, acl.Execute) + state.sys.Ensure(tmpdirInst, 01700) + state.sys.UpdatePermType(system.User, tmpdirInst, acl.Read, acl.Write, acl.Execute) + } return nil } func (s spTmpdirOp) toContainer(state *outcomeStateParams) error { - // mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp - _, tmpdirInst := s.commonPaths(state.outcomeState) - state.params.Bind(tmpdirInst, fhs.AbsTmp, bits.BindWritable) + if state.Container.Flags&hst.FShareTmpdir != 0 { + _, tmpdirInst := s.commonPaths(state.outcomeState) + state.params.Bind(tmpdirInst, fhs.AbsTmp, bits.BindWritable) + } else { + state.params.Tmpfs(fhs.AbsTmp, 0, 01777) + } return nil } diff --git a/nixos.nix b/nixos.nix index 8135690..2451442 100644 --- a/nixos.nix +++ b/nixos.nix @@ -121,6 +121,8 @@ in map_real_uid = app.mapRealUid; host_net = app.hostNet; host_abstract = app.hostAbstract; + share_runtime = app.shareRuntime; + share_tmpdir = app.shareTmpdir; filesystem = let diff --git a/options.nix b/options.nix index 383b554..a1b76e0 100644 --- a/options.nix +++ b/options.nix @@ -136,6 +136,9 @@ in ''; }; + shareRuntime = mkEnableOption "sharing of XDG_RUNTIME_DIR between containers under the same identity"; + shareTmpdir = mkEnableOption "sharing of TMPDIR between containers under the same identity"; + dbus = { session = mkOption { type = nullOr (functionTo anything); diff --git a/test/sandbox/case/default.nix b/test/sandbox/case/default.nix index b6ce3a2..337f4bf 100644 --- a/test/sandbox/case/default.nix +++ b/test/sandbox/case/default.nix @@ -50,6 +50,8 @@ let useCommonPaths userns hostAbstract + shareRuntime + shareTmpdir ; enablements = { inherit (tc) x11; diff --git a/test/sandbox/case/device.nix b/test/sandbox/case/device.nix index da0c974..dec3e38 100644 --- a/test/sandbox/case/device.nix +++ b/test/sandbox/case/device.nix @@ -27,6 +27,8 @@ in userns = false; x11 = true; hostAbstract = false; + shareRuntime = false; + shareTmpdir = true; # 0, PresetStrict expectedFilter = { @@ -133,7 +135,7 @@ in current-system = fs "80001ff" null null; opengl-driver = fs "80001ff" null null; user = fs "800001ed" { - "65534" = fs "800001f8" { + "65534" = fs "800001c0" { bus = fs "10001fd" null null; pulse = fs "800001c0" { native = fs "10001b6" null null; } null; wayland-0 = fs "1000038" null null; @@ -219,7 +221,6 @@ in (ent "/" ignore ignore ignore ignore ignore) (ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000004,gid=1000004") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000004,gid=1000004") - (ent "/tmp/hakurei.0/runtime/4" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/tmp/hakurei.0/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (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") diff --git a/test/sandbox/case/mapuid.nix b/test/sandbox/case/mapuid.nix index b3caf9d..1e007f1 100644 --- a/test/sandbox/case/mapuid.nix +++ b/test/sandbox/case/mapuid.nix @@ -36,6 +36,8 @@ in userns = false; x11 = false; hostAbstract = false; + shareRuntime = true; + shareTmpdir = true; # 0, PresetStrict expectedFilter = { diff --git a/test/sandbox/case/pdlike.nix b/test/sandbox/case/pdlike.nix index c078fac..e5354be 100644 --- a/test/sandbox/case/pdlike.nix +++ b/test/sandbox/case/pdlike.nix @@ -36,6 +36,8 @@ in userns = true; x11 = false; hostAbstract = false; + shareRuntime = true; + shareTmpdir = true; # 0, PresetExt | PresetDenyDevel expectedFilter = { diff --git a/test/sandbox/case/preset.nix b/test/sandbox/case/preset.nix index 10b2634..7cb6ed0 100644 --- a/test/sandbox/case/preset.nix +++ b/test/sandbox/case/preset.nix @@ -36,6 +36,8 @@ in userns = false; x11 = false; hostAbstract = false; + shareRuntime = false; + shareTmpdir = false; # 0, PresetStrict expectedFilter = { @@ -155,7 +157,7 @@ in current-system = fs "80001ff" null null; opengl-driver = fs "80001ff" null null; user = fs "800001ed" { - "65534" = fs "800001f8" { + "65534" = fs "800001c0" { bus = fs "10001fd" null null; pulse = fs "800001c0" { native = fs "10001b6" null null; } null; wayland-0 = fs "1000038" null null; @@ -186,7 +188,7 @@ in } null; devices = fs "800001ed" null null; } null; - tmp = fs "800001f8" { } null; + tmp = fs "801001ff" { } null; usr = fs "800001c0" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null; var = fs "800001c0" { tmp = fs "801001ff" null null; @@ -242,8 +244,7 @@ in (ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw") (ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000001,gid=1000001") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000001,gid=1000001") - (ent "/tmp/hakurei.0/runtime/1" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") - (ent "/tmp/hakurei.0/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") + (ent "/" "/tmp" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000001,gid=1000001") (ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001") (ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001") (ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") diff --git a/test/sandbox/case/tty.nix b/test/sandbox/case/tty.nix index 02dc0cc..eca345a 100644 --- a/test/sandbox/case/tty.nix +++ b/test/sandbox/case/tty.nix @@ -36,6 +36,8 @@ in userns = false; x11 = true; hostAbstract = true; + shareRuntime = true; + shareTmpdir = false; # 0, PresetExt | PresetDenyNS | PresetDenyDevel expectedFilter = { @@ -191,7 +193,7 @@ in } null; devices = fs "800001ed" null null; } null; - tmp = fs "800001f8" { + tmp = fs "801001ff" { ".X11-unix" = fs "801001ff" { X0 = fs "10001fd" null null; } null; } null; usr = fs "800001c0" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null; @@ -252,7 +254,7 @@ in (ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000002,gid=1000002") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000002,gid=1000002") (ent "/tmp/hakurei.0/runtime/2" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") - (ent "/tmp/hakurei.0/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") + (ent "/" "/tmp" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000002,gid=1000002") (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")