diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ac012ba0..85d7ca0b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ `~/.singularity` is on a filesystem that does not fully support overlay. - Add more intuitive error message for rootless `build --oci` when required `XDG_RUNTIME_DIR` env var is not set. +- Avoid error in CNI network setup with newer versions of iptables that include + a setuid caller check. ### New Features & Functionality diff --git a/internal/pkg/runtime/engine/singularity/cleanup_linux.go b/internal/pkg/runtime/engine/singularity/cleanup_linux.go index baf888546c..499b2e763c 100644 --- a/internal/pkg/runtime/engine/singularity/cleanup_linux.go +++ b/internal/pkg/runtime/engine/singularity/cleanup_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE.md file distributed with the sources of this project regarding your // rights to use or distribute this software. @@ -74,18 +74,23 @@ func (e *EngineOperations) CleanupContainer(ctx context.Context, _ error, _ sysc if networkSetup != nil { net := e.EngineConfig.GetNetwork() - privileged := false + var dropPrivs priv.DropPrivsFunc // If a CNI configuration was allowed as non-root (or fakeroot) if net != "none" && os.Geteuid() != 0 { - priv.Escalate() - privileged = true + var err error + dropPrivs, err = priv.EscalateRealEffective() + if err != nil { + return err + } } sylog.Debugf("Cleaning up CNI network config %s", net) if err := networkSetup.DelNetworks(ctx); err != nil { sylog.Errorf("could not delete networks: %v", err) } - if privileged { - priv.Drop() + if dropPrivs != nil { + if err := dropPrivs(); err != nil { + sylog.Fatalf("while dropping privilege: %v", err) + } } } diff --git a/internal/pkg/runtime/engine/singularity/container_linux.go b/internal/pkg/runtime/engine/singularity/container_linux.go index e09e227e13..a6d7e15f6f 100644 --- a/internal/pkg/runtime/engine/singularity/container_linux.go +++ b/internal/pkg/runtime/engine/singularity/container_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. // Copyright (c) Contributors to the Apptainer project, established as // Apptainer a Series of LF Projects LLC. // This software is licensed under a 3-clause BSD license. Please consult the @@ -2398,8 +2398,15 @@ func (c *container) prepareNetworkSetup(system *mount.System, pid int) (func(con } } if euid != 0 { - priv.Escalate() - defer priv.Drop() + dropPrivs, err := priv.EscalateRealEffective() + if err != nil { + return err + } + defer func() { + if err := dropPrivs(); err != nil { + sylog.Fatalf("while dropping privilege: %v", err) + } + }() } } diff --git a/internal/pkg/util/priv/priv_linux.go b/internal/pkg/util/priv/priv_linux.go index 59ba5baa93..193942e957 100644 --- a/internal/pkg/util/priv/priv_linux.go +++ b/internal/pkg/util/priv/priv_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE.md file distributed with the sources of this project regarding your // rights to use or distribute this software. @@ -6,25 +6,31 @@ package priv import ( - "os" "runtime" - "syscall" + + "github.com/sylabs/singularity/v4/pkg/sylog" + "golang.org/x/sys/unix" ) -// Escalate escalates privileges of the thread or process. -// Since Go 1.16 syscall.Setresuid is an all-thread operation. -// A runtime.LockOSThread operation remains for older versions of Go. -func Escalate() error { +type DropPrivsFunc func() error + +// EscalateRealEffective locks the current goroutine to execute on the current +// OS thread, and then escalates the real and effective uid of the current OS +// thread to root (uid 0). The previous real uid is set as the saved +// set-user-ID. A dropPrivsFunc is returned, which must be called to drop +// privileges and unlock the goroutine at the earliest suitable point. +func EscalateRealEffective() (DropPrivsFunc, error) { runtime.LockOSThread() - uid := os.Getuid() - return syscall.Setresuid(uid, 0, uid) -} + uid, _, _ := unix.Getresuid() + + dropPrivsFunc := func() error { + defer runtime.UnlockOSThread() + sylog.Debugf("Drop r/e/s: %d/%d/%d", uid, uid, 0) + return unix.Setresuid(uid, uid, 0) + } -// Drop drops privileges of the thread or process. -// Since Go 1.16 syscall.Setresuid is an all-thread operation. -// A runtime.LockOSThread operation remains for older versions of Go. -func Drop() error { - defer runtime.UnlockOSThread() - uid := os.Getuid() - return syscall.Setresuid(uid, uid, 0) + sylog.Debugf("Escalate r/e/s: %d/%d/%d", 0, 0, uid) + // Note - unix.Setresuid makes a direct syscall which performs a single + // thread escalation. Since Go 1.16, syscall.Setresuid is all-thread. + return dropPrivsFunc, unix.Setresuid(0, 0, uid) } diff --git a/internal/pkg/util/priv/priv_linux_test.go b/internal/pkg/util/priv/priv_linux_test.go new file mode 100644 index 0000000000..b40c622903 --- /dev/null +++ b/internal/pkg/util/priv/priv_linux_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2024, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE.md file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package priv + +import ( + "testing" + + "github.com/sylabs/singularity/v4/internal/pkg/test" + "golang.org/x/sys/unix" +) + +func TestEscalateRealEffective(t *testing.T) { + test.EnsurePrivilege(t) + test.DropPrivilege(t) + defer test.ResetPrivilege(t) + + r, e, s := unix.Getresuid() + if r == 0 || e == 0 { + t.Fatalf("real / effective ID must be non-zero before escalation. Got r/e/s %d/%d/%d", r, e, s) + } + unprivUID := r + + drop, err := EscalateRealEffective() + if err != nil { + t.Fatal(err) + } + + r, e, s = unix.Getresuid() + t.Logf("Escalated r/e/s: %d/%d/%d", r, e, s) + if r != 0 || e != 0 || s != unprivUID { + t.Fatalf("Expected escalated r/e/s %d/%d/%d, Got r/e/s %d/%d/%d", 0, 0, unprivUID, r, e, s) + } + + if err := drop(); err != nil { + t.Fatal(err) + } + + r, e, s = unix.Getresuid() + t.Logf("Dropped r/e/s: %d/%d/%d", r, e, s) + if r != unprivUID || e != unprivUID || s != 0 { + t.Fatalf("Expected dropped r/e/s %d/%d/%d, Got r/e/s %d/%d/%d", unprivUID, unprivUID, 0, r, e, s) + } +} diff --git a/pkg/network/network_linux.go b/pkg/network/network_linux.go index b9a74134e1..5b214b79ad 100644 --- a/pkg/network/network_linux.go +++ b/pkg/network/network_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2019, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE.md file distributed with the sources of this project regarding your // rights to use or distribute this software. @@ -22,6 +22,7 @@ import ( cnitypes "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" "github.com/sylabs/singularity/v4/internal/pkg/util/env" + "github.com/sylabs/singularity/v4/pkg/sylog" ) type netError string @@ -451,12 +452,15 @@ func (m *Setup) DelNetworks(ctx context.Context) error { } func (m *Setup) command(ctx context.Context, command string) error { - if m.envPath != "" { - backupEnv := os.Environ() - os.Clearenv() - os.Setenv("PATH", m.envPath) - defer env.SetFromList(backupEnv) + if m.envPath == "" { + sylog.Debugf("Network envPath is unset. Setting PATH to a safe default.") + m.envPath = "/bin:/sbin:/usr/bin:/usr/sbin" } + sylog.Debugf("Network envPath: %s", m.envPath) + backupEnv := os.Environ() + os.Clearenv() + os.Setenv("PATH", m.envPath) + defer env.SetFromList(backupEnv) config := &libcni.CNIConfig{Path: []string{m.cniPath.Plugin}}