From e2489059c103800ab62250351db895415e0cdfed Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sun, 5 Jan 2025 20:09:35 +0900 Subject: [PATCH] helper/bwrap: implement overlayfs builder Signed-off-by: Ophestra --- helper/bwrap/arg.static.awkward.go | 10 +++++ helper/bwrap/config.go | 70 ++++++++++++++++++++++++++++++ helper/bwrap/config.set.go | 25 +++++++++++ helper/bwrap/config_test.go | 19 ++++++++ 4 files changed, 124 insertions(+) diff --git a/helper/bwrap/arg.static.awkward.go b/helper/bwrap/arg.static.awkward.go index 0848d1f..91fde87 100644 --- a/helper/bwrap/arg.static.awkward.go +++ b/helper/bwrap/arg.static.awkward.go @@ -4,10 +4,20 @@ const ( Tmpfs = iota Dir Symlink + + OverlaySrc + Overlay + TmpOverlay + ROOverlay ) var awkwardArgs = [...]string{ Tmpfs: "--tmpfs", Dir: "--dir", Symlink: "--symlink", + + OverlaySrc: "--overlay-src", + Overlay: "--overlay", + TmpOverlay: "--tmp-overlay", + ROOverlay: "--ro-overlay", } diff --git a/helper/bwrap/config.go b/helper/bwrap/config.go index 5556f4c..6bac6ef 100644 --- a/helper/bwrap/config.go +++ b/helper/bwrap/config.go @@ -78,6 +78,8 @@ type Config struct { --userns FD Use this user namespace (cannot combine with --unshare-user) --userns2 FD After setup switch to this user namespace, only useful with --userns --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid) + --bind-fd FD DEST Bind open directory or path fd on DEST + --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST --exec-label LABEL Exec label for the sandbox --file-label LABEL File label for temporary sandbox content --file FD DEST Copy from FD to destination DEST @@ -178,6 +180,74 @@ func (t *TmpfsConfig) Append(args *[]string) { *args = append(*args, awkwardArgs[Tmpfs], t.Dir) } +type OverlayConfig struct { + /* + read files from SRC in the following overlay + (--overlay-src SRC) + */ + Src []string `json:"src,omitempty"` + + /* + mount overlayfs on DEST, with RWSRC as the host path for writes and + WORKDIR an empty directory on the same filesystem as RWSRC + (--overlay RWSRC WORKDIR DEST) + + if nil, mount overlayfs on DEST, with writes going to an invisible tmpfs + (--tmp-overlay DEST) + + if either strings are empty, mount overlayfs read-only on DEST + (--ro-overlay DEST) + */ + Persist *[2]string `json:"persist,omitempty"` + + /* + --overlay RWSRC WORKDIR DEST + + --tmp-overlay DEST + + --ro-overlay DEST + */ + Dest string `json:"dest"` +} + +func (o *OverlayConfig) Path() string { + return o.Dest +} + +func (o *OverlayConfig) Len() int { + // (--tmp-overlay DEST) or (--ro-overlay DEST) + p := 2 + // (--overlay RWSRC WORKDIR DEST) + if o.Persist != nil && o.Persist[0] != "" && o.Persist[1] != "" { + p = 4 + } + + return p + len(o.Src)*2 +} + +func (o *OverlayConfig) Append(args *[]string) { + // --overlay-src SRC + for _, src := range o.Src { + *args = append(*args, awkwardArgs[OverlaySrc], src) + } + + if o.Persist != nil { + if o.Persist[0] != "" && o.Persist[1] != "" { + // --overlay RWSRC WORKDIR + *args = append(*args, awkwardArgs[Overlay], o.Persist[0], o.Persist[1]) + } else { + // --ro-overlay + *args = append(*args, awkwardArgs[ROOverlay]) + } + } else { + // --tmp-overlay + *args = append(*args, awkwardArgs[TmpOverlay]) + } + + // DEST + *args = append(*args, o.Dest) +} + type SymlinkConfig [2]string func (s SymlinkConfig) Path() string { diff --git a/helper/bwrap/config.set.go b/helper/bwrap/config.set.go index ca2f855..8cbafda 100644 --- a/helper/bwrap/config.set.go +++ b/helper/bwrap/config.set.go @@ -96,6 +96,31 @@ func (c *Config) Tmpfs(dest string, size int, perm ...os.FileMode) *Config { return c } +// Overlay mount overlayfs on DEST, with writes going to an invisible tmpfs +// (--tmp-overlay DEST) +func (c *Config) Overlay(dest string, src ...string) *Config { + c.Filesystem = append(c.Filesystem, &OverlayConfig{Src: src, Dest: dest}) + return c +} + +// Join mount overlayfs read-only on DEST +// (--ro-overlay DEST) +func (c *Config) Join(dest string, src ...string) *Config { + c.Filesystem = append(c.Filesystem, &OverlayConfig{Src: src, Dest: dest, Persist: new([2]string)}) + return c +} + +// Persist mount overlayfs on DEST, with RWSRC as the host path for writes and +// WORKDIR an empty directory on the same filesystem as RWSRC +// (--overlay RWSRC WORKDIR DEST) +func (c *Config) Persist(dest, rwsrc, workdir string, src ...string) *Config { + if rwsrc == "" || workdir == "" { + panic("persist called without required paths") + } + c.Filesystem = append(c.Filesystem, &OverlayConfig{Src: src, Dest: dest, Persist: &[2]string{rwsrc, workdir}}) + return c +} + // Mqueue mount new mqueue in sandbox // (--mqueue DEST) func (c *Config) Mqueue(dest string) *Config { diff --git a/helper/bwrap/config_test.go b/helper/bwrap/config_test.go index 0170bcf..afbcede 100644 --- a/helper/bwrap/config_test.go +++ b/helper/bwrap/config_test.go @@ -13,6 +13,25 @@ func TestConfig_Args(t *testing.T) { conf *bwrap.Config want []string }{ + { + name: "overlayfs", + conf: (new(bwrap.Config)). + Overlay("/etc", "/etc"). + Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin"). + Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix"), + want: []string{ + "--unshare-all", "--unshare-user", + "--disable-userns", "--assert-userns-disabled", + // Overlay("/etc", "/etc") + "--overlay-src", "/etc", "--tmp-overlay", "/etc", + // Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin") + "--overlay-src", "/bin", "--overlay-src", "/usr/bin", + "--overlay-src", "/usr/local/bin", "--ro-overlay", "/.fortify/bin", + // Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix") + "--overlay-src", "/data/app/org.chromium.Chromium/nix", + "--overlay", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/nix", + }, + }, { name: "xdg-dbus-proxy constraint sample", conf: (&bwrap.Config{