diff --git a/Makefile b/Makefile index afe535844..5dc264ace 100644 --- a/Makefile +++ b/Makefile @@ -87,8 +87,8 @@ CNTR_OPTS ?= run --rm -it # Linking variables LINT_CNTR_OPTS ?= $(CNTR_OPTS) -v $(CURDIR):/app -w /app -#? LINT_CNTR_IMG The linter image to use (default: golangci/golangci-lint:v1.53.3) -LINT_CNTR_IMG ?= golangci/golangci-lint:v2.9 +#? LINT_CNTR_IMG The linter image to use (default: golangci/golangci-lint:v2.15) +LINT_CNTR_IMG ?= golangci/golangci-lint:v2.15 LINT_CNTR_CMD ?= golangci-lint run -v --timeout=5m #? DOCS_CNTR_IMG The mkdocs image to use (default: harbor.nbfc.io/nubificus/urunc/mkdocs:test) diff --git a/cmd/urunc/main.go b/cmd/urunc/main.go index c2136f249..b68a4a67d 100644 --- a/cmd/urunc/main.go +++ b/cmd/urunc/main.go @@ -128,8 +128,8 @@ func main() { if !cmd.IsSet("root") { xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR") if xdgRuntimeDir != "" && ShouldHonorXDGRuntimeDir() { - root := xdgRuntimeDir + "/urunc" - if err := prepareXDGRuntimeDir(root); err != nil { + root, err := prepareXDGRuntimeDir(xdgRuntimeDir) + if err != nil { return nil, err } if err := cmd.Set("root", root); err != nil { diff --git a/cmd/urunc/utils.go b/cmd/urunc/utils.go index 9bbaa7c9a..e99dcdedf 100644 --- a/cmd/urunc/utils.go +++ b/cmd/urunc/utils.go @@ -100,7 +100,7 @@ func runcExec() error { return err } args[0] = binPath - return syscall.Exec(binPath, args, os.Environ()) + return syscall.Exec(binPath, args, os.Environ()) //nolint:gosec // runc gets urunc's own arguments } // newSockPair returns a new SOCK_STREAM unix socket pair. @@ -151,16 +151,23 @@ func ShouldHonorXDGRuntimeDir() bool { return !ok || u != "root" } -// prepareXDGRuntimeDir prepares the XDG_RUNTIME_DIR directory according to the XDG specification. -// It creates the directory with proper permissions and sets the sticky bit to prevent auto-pruning. -func prepareXDGRuntimeDir(root string) error { - if err := os.MkdirAll(root, 0o700); err != nil { - return fmt.Errorf("the path in $XDG_RUNTIME_DIR must be writable by the user: %w", err) +// prepareXDGRuntimeDir creates urunc's directory in $XDG_RUNTIME_DIR according to +// the XDG specification, with the sticky bit set to prevent auto-pruning, and +// returns its path. os.Root keeps both steps inside $XDG_RUNTIME_DIR. +func prepareXDGRuntimeDir(xdgRuntimeDir string) (string, error) { + const name = "urunc" + dir, err := os.OpenRoot(xdgRuntimeDir) + if err != nil { + return "", fmt.Errorf("the path in $XDG_RUNTIME_DIR must be writable by the user: %w", err) } - if err := os.Chmod(root, os.FileMode(0o700)|os.ModeSticky); err != nil { - return fmt.Errorf("you should check permission of the path in $XDG_RUNTIME_DIR: %w", err) + defer dir.Close() + if err := dir.MkdirAll(name, 0o700); err != nil { + return "", fmt.Errorf("the path in $XDG_RUNTIME_DIR must be writable by the user: %w", err) } - return nil + if err := dir.Chmod(name, os.FileMode(0o700)|os.ModeSticky); err != nil { + return "", fmt.Errorf("you should check permission of the path in $XDG_RUNTIME_DIR: %w", err) + } + return filepath.Join(xdgRuntimeDir, name), nil } // validateID validates the given ID string against the allowed characters. diff --git a/cmd/urunc/utils_test.go b/cmd/urunc/utils_test.go new file mode 100644 index 000000000..e289b6ce6 --- /dev/null +++ b/cmd/urunc/utils_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPrepareXDGRuntimeDir(t *testing.T) { + t.Parallel() + xdg := t.TempDir() + + root, err := prepareXDGRuntimeDir(xdg) + require.NoError(t, err) + assert.Equal(t, filepath.Join(xdg, "urunc"), root) + + fi, err := os.Stat(root) + require.NoError(t, err) + assert.True(t, fi.IsDir()) + assert.Equal(t, os.FileMode(0o700)|os.ModeSticky, fi.Mode()&(os.ModePerm|os.ModeSticky)) + + _, err = prepareXDGRuntimeDir(xdg) + require.NoError(t, err, "an existing directory is reused") +} + +func TestPrepareXDGRuntimeDirMissing(t *testing.T) { + t.Parallel() + _, err := prepareXDGRuntimeDir(filepath.Join(t.TempDir(), "missing")) + assert.Error(t, err) +} + +func TestPrepareXDGRuntimeDirStaysInside(t *testing.T) { + t.Parallel() + xdg := t.TempDir() + outside := t.TempDir() + require.NoError(t, os.Chmod(outside, 0o755)) + require.NoError(t, os.Symlink(outside, filepath.Join(xdg, "urunc"))) + + _, err := prepareXDGRuntimeDir(xdg) + require.Error(t, err) + + fi, err := os.Stat(outside) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o755), fi.Mode()&(os.ModePerm|os.ModeSticky), "the directory outside is not changed") +} diff --git a/pkg/unikontainers/hypervisors/hvt.go b/pkg/unikontainers/hypervisors/hvt.go index 3e1932b56..83d8a9719 100644 --- a/pkg/unikontainers/hypervisors/hvt.go +++ b/pkg/unikontainers/hypervisors/hvt.go @@ -76,25 +76,7 @@ func (h *HVT) Ok() error { } func (h *HVT) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) { - hvtMem := BytesToStringMB(args.MemSizeB) - exArgs := []string{h.binaryPath, "--mem=" + hvtMem} - if args.Net.TapDev != "" { - netCli := ukernel.MonitorNetCli(args.Net.TapDev, args.Net.MAC) - exArgs = append(exArgs, netCli...) - } - bArgs := ukernel.MonitorBlockCli() - for _, blockArg := range bArgs { - if blockArg.Path != "" { - exArgs = append(exArgs, "--block:"+blockArg.ID+"="+blockArg.Path) - } - } - extraMonArgs := ukernel.MonitorCli() - exArgs = append(exArgs, extraMonArgs.OtherArgs...) - exArgs = append(exArgs, solo5ArgsSeparator, args.UnikernelPath) - if args.Command != "" { - exArgs = append(exArgs, args.Command) - } - return exArgs, nil + return solo5ExecCmd(h.binaryPath, args, ukernel), nil } // PreExec performs pre-execution setup for HVT. diff --git a/pkg/unikontainers/hypervisors/solo5.go b/pkg/unikontainers/hypervisors/solo5.go new file mode 100644 index 000000000..87a2c3601 --- /dev/null +++ b/pkg/unikontainers/hypervisors/solo5.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hypervisors + +import "github.com/urunc-dev/urunc/pkg/unikontainers/types" + +// solo5ExecCmd builds the command line of a solo5 tender (hvt or spt). Both +// share the cli grammar "[ OPTIONS ] [ -- ] KERNEL [ ARGS ]". +func solo5ExecCmd(binaryPath string, args types.ExecArgs, ukernel types.Unikernel) []string { + exArgs := []string{binaryPath, "--mem=" + BytesToStringMB(args.MemSizeB)} + if args.Net.TapDev != "" { + netCli := ukernel.MonitorNetCli(args.Net.TapDev, args.Net.MAC) + exArgs = append(exArgs, netCli...) + } + bArgs := ukernel.MonitorBlockCli() + for _, blockArg := range bArgs { + if blockArg.Path != "" { + exArgs = append(exArgs, "--block:"+blockArg.ID+"="+blockArg.Path) + } + } + extraMonArgs := ukernel.MonitorCli() + exArgs = append(exArgs, extraMonArgs.OtherArgs...) + exArgs = append(exArgs, solo5ArgsSeparator, args.UnikernelPath) + if args.Command != "" { + exArgs = append(exArgs, args.Command) + } + return exArgs +} diff --git a/pkg/unikontainers/hypervisors/spt.go b/pkg/unikontainers/hypervisors/spt.go index 80b622e78..7844b0d3b 100644 --- a/pkg/unikontainers/hypervisors/spt.go +++ b/pkg/unikontainers/hypervisors/spt.go @@ -74,25 +74,7 @@ func (s *SPT) Ok() error { } func (s *SPT) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel) ([]string, error) { - sptMem := BytesToStringMB(args.MemSizeB) - exArgs := []string{s.binaryPath, "--mem=" + sptMem} - if args.Net.TapDev != "" { - netCli := ukernel.MonitorNetCli(args.Net.TapDev, args.Net.MAC) - exArgs = append(exArgs, netCli...) - } - bArgs := ukernel.MonitorBlockCli() - for _, blockArg := range bArgs { - if blockArg.Path != "" { - exArgs = append(exArgs, "--block:"+blockArg.ID+"="+blockArg.Path) - } - } - extraMonArgs := ukernel.MonitorCli() - exArgs = append(exArgs, extraMonArgs.OtherArgs...) - exArgs = append(exArgs, solo5ArgsSeparator, args.UnikernelPath) - if args.Command != "" { - exArgs = append(exArgs, args.Command) - } - return exArgs, nil + return solo5ExecCmd(s.binaryPath, args, ukernel), nil } // PreExec performs pre-execution setup. SPT has no special pre-exec requirements. diff --git a/pkg/unikontainers/mount.go b/pkg/unikontainers/mount.go index b0a8f036a..769d7f745 100644 --- a/pkg/unikontainers/mount.go +++ b/pkg/unikontainers/mount.go @@ -276,7 +276,8 @@ func getUnprivilegedMountFlags(path string) (uintptr, error) { // so map each ST_* bit that statfs reports to the MS_* flag used for the // remount explicitly. lockedFlags := []struct { - st, ms uintptr + st int64 + ms uintptr }{ {unix.ST_RDONLY, unix.MS_RDONLY}, {unix.ST_NODEV, unix.MS_NODEV}, @@ -289,7 +290,7 @@ func getUnprivilegedMountFlags(path string) (uintptr, error) { var flags uintptr for _, f := range lockedFlags { - if uintptr(st.Flags)&f.st == f.st { + if st.Flags&f.st == f.st { flags |= f.ms } }