From 948508ad78cf3f602869f5929f4aef799e1c207b Mon Sep 17 00:00:00 2001 From: Yuxiang Zhu Date: Sat, 20 Jun 2026 18:58:21 +0800 Subject: [PATCH] network: use rootless user check for config paths Signed-off-by: Yuxiang Zhu --- libnetwork/network/interface.go | 14 +++++++-- libnetwork/network/interface_cni.go | 3 +- libnetwork/network/interface_test.go | 45 ++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 libnetwork/network/interface_test.go diff --git a/libnetwork/network/interface.go b/libnetwork/network/interface.go index 51c1ae718..4b7c53494 100644 --- a/libnetwork/network/interface.go +++ b/libnetwork/network/interface.go @@ -65,10 +65,10 @@ func netavarkBackendFromConf(store storage.Store, conf *config.Config, syslog bo // We cannot use the runroot for rootful since the network namespace is shared for all // libpod instances they also have to share the same ipam db. - // For rootless we have our own network namespace per libpod instances, + // For rootless users we have our own network namespace per libpod instances, // so this is not a problem there. runDir := netavarkRunDir - if unshare.IsRootless() { + if isRootlessUser() { runDir = filepath.Join(store.RunRoot(), "networks") } @@ -141,8 +141,16 @@ func defaultNetworkBackend(store storage.Store, conf *config.Config) (backend ty // use "/etc/containers/networks" and for rootless "$graphroot/networks". We cannot // use the graphroot for rootful since the network namespace is shared for all // libpod instances. +func isRootlessUser() bool { + return rootlessModeForUser(unshare.IsRootless(), unshare.GetRootlessUID()) +} + +func rootlessModeForUser(isRootless bool, rootlessUID int) bool { + return isRootless && rootlessUID > 0 +} + func getDefaultNetavarkConfigDir(store storage.Store) string { - if !unshare.IsRootless() { + if !isRootlessUser() { return netavarkConfigDir } return filepath.Join(store.GraphRoot(), "networks") diff --git a/libnetwork/network/interface_cni.go b/libnetwork/network/interface_cni.go index 3de3a3045..f38681606 100644 --- a/libnetwork/network/interface_cni.go +++ b/libnetwork/network/interface_cni.go @@ -14,7 +14,6 @@ import ( "github.com/containers/common/pkg/machine" "github.com/containers/storage" "github.com/containers/storage/pkg/homedir" - "github.com/containers/storage/pkg/unshare" ) const ( @@ -42,7 +41,7 @@ func getCniInterface(conf *config.Config) (types.ContainerNetwork, error) { } func getDefaultCNIConfigDir() (string, error) { - if !unshare.IsRootless() { + if !isRootlessUser() { return cniConfigDir, nil } diff --git a/libnetwork/network/interface_test.go b/libnetwork/network/interface_test.go new file mode 100644 index 000000000..4fd85ebe4 --- /dev/null +++ b/libnetwork/network/interface_test.go @@ -0,0 +1,45 @@ +//go:build linux || freebsd + +package network + +import "testing" + +func TestRootlessModeForUser(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + isRootless bool + rootlessUID int + expect bool + }{ + { + name: "rootless user", + isRootless: true, + rootlessUID: 1000, + expect: true, + }, + { + name: "root in nested user namespace", + isRootless: true, + rootlessUID: 0, + expect: false, + }, + { + name: "rootful", + isRootless: false, + rootlessUID: 0, + expect: false, + }, + } + + for _, tcl := range testCases { + tc := tcl + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := rootlessModeForUser(tc.isRootless, tc.rootlessUID); got != tc.expect { + t.Fatalf("expected %v, got %v", tc.expect, got) + } + }) + } +}