diff --git a/container/check/overlay.go b/container/check/overlay.go new file mode 100644 index 0000000..ab907fb --- /dev/null +++ b/container/check/overlay.go @@ -0,0 +1,29 @@ +package check + +import "strings" + +const ( + // SpecialOverlayEscape is the escape string for overlay mount options. + SpecialOverlayEscape = `\` + // SpecialOverlayOption is the separator string between overlay mount options. + SpecialOverlayOption = "," + // SpecialOverlayPath is the separator string between overlay paths. + SpecialOverlayPath = ":" +) + +// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call. +func EscapeOverlayDataSegment(s string) string { + if s == "" { + return "" + } + + if f := strings.SplitN(s, "\x00", 2); len(f) > 0 { + s = f[0] + } + + return strings.NewReplacer( + SpecialOverlayEscape, SpecialOverlayEscape+SpecialOverlayEscape, + SpecialOverlayOption, SpecialOverlayEscape+SpecialOverlayOption, + SpecialOverlayPath, SpecialOverlayEscape+SpecialOverlayPath, + ).Replace(s) +} diff --git a/container/check/overlay_test.go b/container/check/overlay_test.go new file mode 100644 index 0000000..bd90236 --- /dev/null +++ b/container/check/overlay_test.go @@ -0,0 +1,27 @@ +package check_test + +import ( + "testing" + + "hakurei.app/container/check" +) + +func TestEscapeOverlayDataSegment(t *testing.T) { + testCases := []struct { + name string + s string + want string + }{ + {"zero", "", ""}, + {"multi", `\\\:,:,\\\`, `\\\\\\\:\,\:\,\\\\\\`}, + {"bwrap", `/path :,\`, `/path \:\,\\`}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if got := check.EscapeOverlayDataSegment(tc.s); got != tc.want { + t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want) + } + }) + } +} diff --git a/container/initoverlay.go b/container/initoverlay.go index 74ae7d0..5692af4 100644 --- a/container/initoverlay.go +++ b/container/initoverlay.go @@ -139,7 +139,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error { if v, err := k.evalSymlinks(o.Upper.String()); err != nil { return err } else { - o.upper = EscapeOverlayDataSegment(toHost(v)) + o.upper = check.EscapeOverlayDataSegment(toHost(v)) } } @@ -147,7 +147,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error { if v, err := k.evalSymlinks(o.Work.String()); err != nil { return err } else { - o.work = EscapeOverlayDataSegment(toHost(v)) + o.work = check.EscapeOverlayDataSegment(toHost(v)) } } } @@ -157,7 +157,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error { if v, err := k.evalSymlinks(a.String()); err != nil { return err } else { - o.lower[i] = EscapeOverlayDataSegment(toHost(v)) + o.lower[i] = check.EscapeOverlayDataSegment(toHost(v)) } } return nil @@ -199,10 +199,10 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error { OptionOverlayWorkdir+"="+o.work) } options = append(options, - OptionOverlayLowerdir+"="+strings.Join(o.lower, SpecialOverlayPath), + OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath), OptionOverlayUserxattr) - return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption)) + return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption)) } func (o *MountOverlayOp) Is(op Op) bool { diff --git a/container/mount.go b/container/mount.go index 2749d0e..0c459c9 100644 --- a/container/mount.go +++ b/container/mount.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "strings" . "syscall" "hakurei.app/container/vfs" @@ -85,13 +84,6 @@ const ( // OptionOverlayUserxattr represents the userxattr option of the overlay pseudo-filesystem. // Use the "user.overlay." xattr namespace instead of "trusted.overlay.". OptionOverlayUserxattr = "userxattr" - - // SpecialOverlayEscape is the escape string for overlay mount options. - SpecialOverlayEscape = `\` - // SpecialOverlayOption is the separator string between overlay mount options. - SpecialOverlayOption = "," - // SpecialOverlayPath is the separator string between overlay paths. - SpecialOverlayPath = ":" ) // bindMount mounts source on target and recursively applies flags if MS_REC is set. @@ -206,20 +198,3 @@ func parentPerm(perm os.FileMode) os.FileMode { } return os.FileMode(pperm) } - -// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call. -func EscapeOverlayDataSegment(s string) string { - if s == zeroString { - return zeroString - } - - if f := strings.SplitN(s, "\x00", 2); len(f) > 0 { - s = f[0] - } - - return strings.NewReplacer( - SpecialOverlayEscape, SpecialOverlayEscape+SpecialOverlayEscape, - SpecialOverlayOption, SpecialOverlayEscape+SpecialOverlayOption, - SpecialOverlayPath, SpecialOverlayEscape+SpecialOverlayPath, - ).Replace(s) -} diff --git a/container/mount_test.go b/container/mount_test.go index 5391623..daa2287 100644 --- a/container/mount_test.go +++ b/container/mount_test.go @@ -281,23 +281,3 @@ func TestParentPerm(t *testing.T) { }) } } - -func TestEscapeOverlayDataSegment(t *testing.T) { - testCases := []struct { - name string - s string - want string - }{ - {"zero", zeroString, zeroString}, - {"multi", `\\\:,:,\\\`, `\\\\\\\:\,\:\,\\\\\\`}, - {"bwrap", `/path :,\`, `/path \:\,\\`}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if got := EscapeOverlayDataSegment(tc.s); got != tc.want { - t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want) - } - }) - } -} diff --git a/container/path_test.go b/container/path_test.go index 746e70f..5af4c82 100644 --- a/container/path_test.go +++ b/container/path_test.go @@ -10,6 +10,7 @@ import ( "testing" "unsafe" + "hakurei.app/container/check" "hakurei.app/container/vfs" ) @@ -49,8 +50,8 @@ func TestToHost(t *testing.T) { } } -// InternalToHostOvlEscape exports toHost passed to EscapeOverlayDataSegment. -func InternalToHostOvlEscape(s string) string { return EscapeOverlayDataSegment(toHost(s)) } +// InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment]. +func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) } func TestCreateFile(t *testing.T) { t.Run("nonexistent", func(t *testing.T) { diff --git a/hst/fsoverlay.go b/hst/fsoverlay.go index 2c2a195..689970d 100644 --- a/hst/fsoverlay.go +++ b/hst/fsoverlay.go @@ -4,7 +4,6 @@ import ( "encoding/gob" "strings" - "hakurei.app/container" "hakurei.app/container/check" ) @@ -82,18 +81,18 @@ func (o *FSOverlay) String() string { lower := make([]string, len(o.Lower)) for i, a := range o.Lower { - lower[i] = container.EscapeOverlayDataSegment(a.String()) + lower[i] = check.EscapeOverlayDataSegment(a.String()) } if o.Upper != nil && o.Work != nil { return "w*" + strings.Join(append([]string{ - container.EscapeOverlayDataSegment(o.Target.String()), - container.EscapeOverlayDataSegment(o.Upper.String()), - container.EscapeOverlayDataSegment(o.Work.String())}, - lower...), container.SpecialOverlayPath) + check.EscapeOverlayDataSegment(o.Target.String()), + check.EscapeOverlayDataSegment(o.Upper.String()), + check.EscapeOverlayDataSegment(o.Work.String())}, + lower...), check.SpecialOverlayPath) } else { return "*" + strings.Join(append([]string{ - container.EscapeOverlayDataSegment(o.Target.String())}, - lower...), container.SpecialOverlayPath) + check.EscapeOverlayDataSegment(o.Target.String())}, + lower...), check.SpecialOverlayPath) } }