aboutsummaryrefslogtreecommitdiffhomepage
path: root/hst
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-14 06:37:24 +0900
committerOphestra <cat@gensokyo.uk>2025-10-14 06:39:00 +0900
commit4c647add0db06cc7a28571aee7502737f0d66b27 (patch)
tree7446453d4380ed21c3ad3c3f8026c708fc53e729 /hst
parenta3414669428d4de37b68aff3f2db14e5768e1f59 (diff)
hst/container: pack boolean options
The memory saving is relatively insignificant, however this increases serialisation efficiency. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'hst')
-rw-r--r--hst/container.go151
-rw-r--r--hst/container_test.go76
-rw-r--r--hst/hst.go17
-rw-r--r--hst/hst_test.go51
4 files changed, 244 insertions, 51 deletions
diff --git a/hst/container.go b/hst/container.go
index ca75c17b..69909389 100644
--- a/hst/container.go
+++ b/hst/container.go
@@ -1,6 +1,8 @@
package hst
import (
+ "encoding/json"
+ "syscall"
"time"
"hakurei.app/container/check"
@@ -29,6 +31,37 @@ const (
ShimExitOrphan = 3
)
+const (
+ // FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
+ FMultiarch uintptr = 1 << iota
+
+ // FSeccompCompat causes emitted seccomp filter programs to be identical to Flatpak.
+ FSeccompCompat
+ // FDevel unblocks ptrace and friends.
+ FDevel
+ // FUserns unblocks userns creation and container setup syscalls.
+ FUserns
+ // FHostNet skips net namespace creation.
+ FHostNet
+ // FHostAbstract skips setting up abstract unix socket scope.
+ FHostAbstract
+ // FTty unblocks dangerous terminal I/O (faking input).
+ FTty
+
+ // FMapRealUID maps the target user uid to the privileged user uid in the container user namespace.
+ // Some programs fail to connect to dbus session running as a different uid,
+ // this option works around it by mapping priv-side caller uid in container.
+ FMapRealUID
+
+ // FDevice mount /dev/ from the init mount namespace as-is in the container mount namespace.
+ FDevice
+
+ fMax
+
+ // FAll is [ContainerConfig.Flags] with all currently defined bits set.
+ FAll = fMax - 1
+)
+
// ContainerConfig describes the container configuration to be applied to an underlying [container].
type ContainerConfig struct {
// Container UTS namespace hostname.
@@ -39,33 +72,9 @@ type ContainerConfig struct {
// Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
WaitDelay time.Duration `json:"wait_delay,omitempty"`
- // Emit Flatpak-compatible seccomp filter programs.
- SeccompCompat bool `json:"seccomp_compat,omitempty"`
- // Allow ptrace and friends.
- Devel bool `json:"devel,omitempty"`
- // Allow userns creation and container setup syscalls.
- Userns bool `json:"userns,omitempty"`
- // Share host net namespace.
- HostNet bool `json:"host_net,omitempty"`
- // Share abstract unix socket scope.
- HostAbstract bool `json:"host_abstract,omitempty"`
- // Allow dangerous terminal I/O (faking input).
- Tty bool `json:"tty,omitempty"`
- // Allow multiarch.
- Multiarch bool `json:"multiarch,omitempty"`
-
// Initial process environment variables.
Env map[string]string `json:"env"`
- /* Map target user uid to privileged user uid in the container user namespace.
-
- Some programs fail to connect to dbus session running as a different uid,
- this option works around it by mapping priv-side caller uid in container. */
- MapRealUID bool `json:"map_real_uid"`
-
- // Mount /dev/ from the init mount namespace as-is in the container mount namespace.
- Device bool `json:"device,omitempty"`
-
/* Container mount points.
If the first element targets /, it is inserted early and excluded from path hiding. */
@@ -83,4 +92,98 @@ type ContainerConfig struct {
Path *check.Absolute `json:"path,omitempty"`
// Final args passed to the initial program.
Args []string `json:"args"`
+
+ // Flags holds boolean options of [ContainerConfig].
+ Flags uintptr `json:"-"`
+}
+
+// ContainerConfigF is [ContainerConfig] stripped of its methods.
+// The [ContainerConfig.Flags] field does not survive a [json] round trip.
+type ContainerConfigF ContainerConfig
+
+// containerConfigJSON is the [json] representation of [ContainerConfig].
+type containerConfigJSON = struct {
+ *ContainerConfigF
+
+ // Corresponds to [FSeccompCompat].
+ SeccompCompat bool `json:"seccomp_compat,omitempty"`
+ // Corresponds to [FDevel].
+ Devel bool `json:"devel,omitempty"`
+ // Corresponds to [FUserns].
+ Userns bool `json:"userns,omitempty"`
+ // Corresponds to [FHostNet].
+ HostNet bool `json:"host_net,omitempty"`
+ // Corresponds to [FHostAbstract].
+ HostAbstract bool `json:"host_abstract,omitempty"`
+ // Corresponds to [FTty].
+ Tty bool `json:"tty,omitempty"`
+
+ // Corresponds to [FMultiarch].
+ Multiarch bool `json:"multiarch,omitempty"`
+
+ // Corresponds to [FMapRealUID].
+ MapRealUID bool `json:"map_real_uid"`
+
+ // Corresponds to [FDevice].
+ Device bool `json:"device,omitempty"`
+}
+
+func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
+ if c == nil {
+ return nil, syscall.EINVAL
+ }
+ return json.Marshal(&containerConfigJSON{
+ ContainerConfigF: (*ContainerConfigF)(c),
+
+ SeccompCompat: c.Flags&FSeccompCompat != 0,
+ Devel: c.Flags&FDevel != 0,
+ Userns: c.Flags&FUserns != 0,
+ HostNet: c.Flags&FHostNet != 0,
+ HostAbstract: c.Flags&FHostAbstract != 0,
+ Tty: c.Flags&FTty != 0,
+ Multiarch: c.Flags&FMultiarch != 0,
+ MapRealUID: c.Flags&FMapRealUID != 0,
+ Device: c.Flags&FDevice != 0,
+ })
+}
+
+func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
+ if c == nil {
+ return syscall.EINVAL
+ }
+
+ v := new(containerConfigJSON)
+ if err := json.Unmarshal(data, &v); err != nil {
+ return err
+ }
+
+ *c = *(*ContainerConfig)(v.ContainerConfigF)
+ if v.SeccompCompat {
+ c.Flags |= FSeccompCompat
+ }
+ if v.Devel {
+ c.Flags |= FDevel
+ }
+ if v.Userns {
+ c.Flags |= FUserns
+ }
+ if v.HostNet {
+ c.Flags |= FHostNet
+ }
+ if v.HostAbstract {
+ c.Flags |= FHostAbstract
+ }
+ if v.Tty {
+ c.Flags |= FTty
+ }
+ if v.Multiarch {
+ c.Flags |= FMultiarch
+ }
+ if v.MapRealUID {
+ c.Flags |= FMapRealUID
+ }
+ if v.Device {
+ c.Flags |= FDevice
+ }
+ return nil
}
diff --git a/hst/container_test.go b/hst/container_test.go
new file mode 100644
index 00000000..049d3540
--- /dev/null
+++ b/hst/container_test.go
@@ -0,0 +1,76 @@
+package hst_test
+
+import (
+ "encoding/json"
+ "errors"
+ "reflect"
+ "syscall"
+ "testing"
+
+ "hakurei.app/hst"
+)
+
+func TestContainerConfig(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ c *hst.ContainerConfig
+ data string
+ }{
+ {"nil", nil, "null"},
+ {"zero", new(hst.ContainerConfig),
+ `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"map_real_uid":false}`},
+ {"seccomp compat", &hst.ContainerConfig{Flags: hst.FSeccompCompat},
+ `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"map_real_uid":false}`},
+ {"hostnet hostabstract", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract},
+ `{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":false}`},
+ {"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}`},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ t.Run("marshal", func(t *testing.T) {
+ t.Parallel()
+ if got, err := json.Marshal(tc.c); err != nil {
+ t.Fatalf("Marshal: error = %v", err)
+ } else if string(got) != tc.data {
+ t.Errorf("Marshal:\n%s, want\n%s", string(got), tc.data)
+ }
+ })
+
+ t.Run("unmarshal", func(t *testing.T) {
+ t.Parallel()
+
+ {
+ got := new(hst.ContainerConfig)
+ if err := json.Unmarshal([]byte(tc.data), &got); err != nil {
+ t.Fatalf("Unmarshal: error = %v", err)
+ }
+ if !reflect.DeepEqual(got, tc.c) {
+ t.Errorf("Unmarshal: %v, want %v", got, tc.c)
+ }
+ }
+ })
+ })
+ }
+
+ t.Run("passthrough", func(t *testing.T) {
+ t.Parallel()
+
+ if _, err := (*hst.ContainerConfig)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
+ t.Errorf("MarshalJSON: error = %v", err)
+ }
+ if err := (*hst.ContainerConfig)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
+ t.Errorf("UnmarshalJSON: error = %v", err)
+ }
+ if err := new(hst.ContainerConfig).UnmarshalJSON([]byte{}); err == nil {
+ t.Errorf("UnmarshalJSON: error = %v", err)
+ }
+ })
+}
diff --git a/hst/hst.go b/hst/hst.go
index 11e91e7b..11b2b279 100644
--- a/hst/hst.go
+++ b/hst/hst.go
@@ -3,6 +3,7 @@ package hst
import (
"errors"
+ "math"
"net"
"os"
@@ -96,17 +97,8 @@ func Template() *Config {
Groups: []string{"video", "dialout", "plugdev"},
Container: &ContainerConfig{
- Hostname: "localhost",
- Devel: true,
- Userns: true,
- HostNet: true,
- HostAbstract: true,
- Device: true,
- WaitDelay: -1,
- SeccompCompat: true,
- Tty: true,
- Multiarch: true,
- MapRealUID: true,
+ Hostname: "localhost",
+ WaitDelay: -1,
// example API credentials pulled from Google Chrome
// DO NOT USE THESE IN A REAL BROWSER
Env: map[string]string{
@@ -143,6 +135,9 @@ func Template() *Config {
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
},
+
+ // Set all bits here so new flags trip the template test.
+ Flags: math.MaxUint,
},
}
}
diff --git a/hst/hst_test.go b/hst/hst_test.go
index 8bca8d6e..0dd74886 100644
--- a/hst/hst_test.go
+++ b/hst/hst_test.go
@@ -5,6 +5,7 @@ import (
"errors"
"net"
"os"
+ "reflect"
"syscall"
"testing"
@@ -164,20 +165,11 @@ func TestTemplate(t *testing.T) {
"container": {
"hostname": "localhost",
"wait_delay": -1,
- "seccomp_compat": true,
- "devel": true,
- "userns": true,
- "host_net": true,
- "host_abstract": true,
- "tty": true,
- "multiarch": true,
"env": {
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
},
- "map_real_uid": true,
- "device": true,
"filesystem": [
{
"type": "bind",
@@ -243,14 +235,41 @@ func TestTemplate(t *testing.T) {
"--disable-smooth-scrolling",
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland"
- ]
+ ],
+ "seccomp_compat": true,
+ "devel": true,
+ "userns": true,
+ "host_net": true,
+ "host_abstract": true,
+ "tty": true,
+ "multiarch": true,
+ "map_real_uid": true,
+ "device": true
}
}`
- if p, err := json.MarshalIndent(hst.Template(), "", "\t"); err != nil {
- t.Fatalf("cannot marshal: %v", err)
- } else if s := string(p); s != want {
- t.Fatalf("Template:\n%s\nwant:\n%s",
- s, want)
- }
+ t.Run("marshal", func(t *testing.T) {
+ t.Parallel()
+ if p, err := json.MarshalIndent(hst.Template(), "", "\t"); err != nil {
+ t.Fatalf("cannot marshal: %v", err)
+ } else if s := string(p); s != want {
+ t.Fatalf("Template:\n%s\nwant:\n%s",
+ s, want)
+ }
+ })
+
+ t.Run("unmarshal", func(t *testing.T) {
+ t.Parallel()
+
+ var got *hst.Config
+ if err := json.Unmarshal([]byte(want), &got); err != nil {
+ t.Fatalf("Unmarshal: error = %v", err)
+ }
+
+ wantVal := hst.Template()
+ wantVal.Container.Flags = hst.FAll
+ if !reflect.DeepEqual(got, wantVal) {
+ t.Fatalf("Unmarshal: %#v, want %#v", got, wantVal)
+ }
+ })
}