Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions cmd/urunc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 16 additions & 9 deletions cmd/urunc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
62 changes: 62 additions & 0 deletions cmd/urunc/utils_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
20 changes: 1 addition & 19 deletions pkg/unikontainers/hypervisors/hvt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
40 changes: 40 additions & 0 deletions pkg/unikontainers/hypervisors/solo5.go
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 1 addition & 19 deletions pkg/unikontainers/hypervisors/spt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions pkg/unikontainers/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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
}
}
Expand Down