mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-28 07:19:08 +01:00
[client] Drop SSH privileges through libc in PKCS#11 package builds (#8323)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//go:build linux && privileged
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const envDropPrivilegesChild = "NB_TEST_DROP_PRIVILEGES_CHILD"
|
||||
|
||||
const (
|
||||
dropTargetUID = 65534
|
||||
dropTargetGID = 65534
|
||||
dropExtraGID = 65533
|
||||
)
|
||||
|
||||
// TestDropPrivilegesAllThreads drops privileges in a child process, because the drop
|
||||
// cannot be undone, and checks that every OS thread of that process carries the target
|
||||
// credentials. A drop applied to the calling thread only would leave the others as root.
|
||||
func TestDropPrivilegesAllThreads(t *testing.T) {
|
||||
if os.Getenv(envDropPrivilegesChild) == "1" {
|
||||
dropPrivilegesChild(t)
|
||||
return
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("requires root")
|
||||
}
|
||||
|
||||
cmd := exec.Command(os.Args[0], "-test.run=^TestDropPrivilegesAllThreads$", "-test.v")
|
||||
cmd.Env = append(os.Environ(), envDropPrivilegesChild+"=1")
|
||||
out, err := cmd.CombinedOutput()
|
||||
t.Logf("child output:\n%s", out)
|
||||
require.NoError(t, err, "child must drop privileges on all threads")
|
||||
assert.Contains(t, string(out), "--- PASS: TestDropPrivilegesAllThreads", "child test must run and pass")
|
||||
}
|
||||
|
||||
func dropPrivilegesChild(t *testing.T) {
|
||||
// Park goroutines on their own OS threads so the process has threads other than
|
||||
// the one that performs the drop.
|
||||
release := make(chan struct{})
|
||||
defer close(release)
|
||||
ready := make(chan struct{})
|
||||
for range 4 {
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ready <- struct{}{}
|
||||
<-release
|
||||
}()
|
||||
<-ready
|
||||
}
|
||||
|
||||
pd := NewPrivilegeDropper()
|
||||
require.NoError(t, pd.DropPrivileges(dropTargetUID, dropTargetGID, []uint32{dropTargetGID, dropExtraGID}))
|
||||
|
||||
tasks, err := filepath.Glob("/proc/self/task/*/status")
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(tasks), 4, "process must have several threads for the check to mean anything")
|
||||
|
||||
want := map[string]string{
|
||||
"Uid": fmt.Sprintf("%d\t%d\t%d\t%d", dropTargetUID, dropTargetUID, dropTargetUID, dropTargetUID),
|
||||
"Gid": fmt.Sprintf("%d\t%d\t%d\t%d", dropTargetGID, dropTargetGID, dropTargetGID, dropTargetGID),
|
||||
"Groups": fmt.Sprintf("%d %d", dropExtraGID, dropTargetGID),
|
||||
}
|
||||
for _, task := range tasks {
|
||||
got := readStatusFields(t, task)
|
||||
for field, value := range want {
|
||||
assert.Equal(t, value, got[field], "%s of %s", field, task)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Error(t, seteuid(0), "euid 0 must not be restorable after the drop")
|
||||
assert.Error(t, setegid(0), "egid 0 must not be restorable after the drop")
|
||||
}
|
||||
|
||||
func readStatusFields(t *testing.T, path string) map[string]string {
|
||||
f, err := os.Open(path)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
fields := make(map[string]string)
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
key, value, ok := strings.Cut(scanner.Text(), ":")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fields[key] = strings.TrimSpace(value)
|
||||
}
|
||||
require.NoError(t, scanner.Err())
|
||||
return fields
|
||||
}
|
||||
Reference in New Issue
Block a user