Skip to content

Commit

Permalink
Merge pull request #3445 from dtrudg/pick-3444
Browse files Browse the repository at this point in the history
fix: CNI: avoid error with iptables setuid check (release-4.2)
  • Loading branch information
dtrudg authored Dec 20, 2024
2 parents 7a7e790 + 2b5f914 commit 6af15b6
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 32 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 11 additions & 6 deletions internal/pkg/runtime/engine/singularity/cleanup_linux.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
13 changes: 10 additions & 3 deletions internal/pkg/runtime/engine/singularity/container_linux.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
}
}()
}
}

Expand Down
40 changes: 23 additions & 17 deletions internal/pkg/util/priv/priv_linux.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
// 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.

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)
}
46 changes: 46 additions & 0 deletions internal/pkg/util/priv/priv_linux_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
16 changes: 10 additions & 6 deletions pkg/network/network_linux.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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}}

Expand Down

0 comments on commit 6af15b6

Please sign in to comment.