Merge pull request #23498 from lelemka0/fix/quadlets/userLevelFilter

Quadlet: Fix `userLevelFilter` when `UnitDirAdmin` is a symlink
This commit is contained in:
openshift-merge-bot[bot] 2024-08-11 13:43:34 +00:00 committed by GitHub
commit 277e061878
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 145 additions and 58 deletions

View File

@ -34,10 +34,6 @@ var (
versionFlag bool // True if -version is used versionFlag bool // True if -version is used
) )
const (
SystemUserDirLevel = 5
)
var ( var (
// data saved between logToKmsg calls // data saved between logToKmsg calls
noKmsg = false noKmsg = false
@ -59,6 +55,12 @@ var (
} }
) )
var (
unitDirAdminUser string
resolvedUnitDirAdminUser string
systemUserDirLevel int
)
// We log directly to /dev/kmsg, because that is the only way to get information out // We log directly to /dev/kmsg, because that is the only way to get information out
// of the generator into the system logs. // of the generator into the system logs.
func logToKmsg(s string) bool { func logToKmsg(s string) bool {
@ -115,6 +117,14 @@ func getUnitDirs(rootless bool) []string {
unitDirsEnv := os.Getenv("QUADLET_UNIT_DIRS") unitDirsEnv := os.Getenv("QUADLET_UNIT_DIRS")
dirs := make([]string, 0) dirs := make([]string, 0)
unitDirAdminUser = filepath.Join(quadlet.UnitDirAdmin, "users")
var err error
if resolvedUnitDirAdminUser, err = filepath.EvalSymlinks(unitDirAdminUser); err != nil {
Debugf("Error occurred resolving path %q: %s", unitDirAdminUser, err)
resolvedUnitDirAdminUser = unitDirAdminUser
}
systemUserDirLevel = len(strings.Split(resolvedUnitDirAdminUser, string(os.PathSeparator)))
if len(unitDirsEnv) > 0 { if len(unitDirsEnv) > 0 {
for _, eachUnitDir := range strings.Split(unitDirsEnv, ":") { for _, eachUnitDir := range strings.Split(unitDirsEnv, ":") {
if !filepath.IsAbs(eachUnitDir) { if !filepath.IsAbs(eachUnitDir) {
@ -185,10 +195,10 @@ func appendSubPaths(dirs []string, path string, isUserFlag bool, filterPtr func(
func nonNumericFilter(_path string, isUserFlag bool) bool { func nonNumericFilter(_path string, isUserFlag bool) bool {
// when running in rootless, recursive walk directories that are non numeric // when running in rootless, recursive walk directories that are non numeric
// ignore sub dirs under the `users` directory which correspond to a user id // ignore sub dirs under the `users` directory which correspond to a user id
if strings.Contains(_path, filepath.Join(quadlet.UnitDirAdmin, "users")) { if strings.HasPrefix(_path, resolvedUnitDirAdminUser) {
listDirUserPathLevels := strings.Split(_path, string(os.PathSeparator)) listDirUserPathLevels := strings.Split(_path, string(os.PathSeparator))
if len(listDirUserPathLevels) > SystemUserDirLevel { if len(listDirUserPathLevels) > systemUserDirLevel {
if !(regexp.MustCompile(`^[0-9]*$`).MatchString(listDirUserPathLevels[SystemUserDirLevel])) { if !(regexp.MustCompile(`^[0-9]*$`).MatchString(listDirUserPathLevels[systemUserDirLevel])) {
return true return true
} }
} }
@ -201,7 +211,7 @@ func nonNumericFilter(_path string, isUserFlag bool) bool {
func userLevelFilter(_path string, isUserFlag bool) bool { func userLevelFilter(_path string, isUserFlag bool) bool {
// if quadlet generator is run rootless, do not recurse other user sub dirs // if quadlet generator is run rootless, do not recurse other user sub dirs
// if quadlet generator is run as root, ignore users sub dirs // if quadlet generator is run as root, ignore users sub dirs
if strings.Contains(_path, filepath.Join(quadlet.UnitDirAdmin, "users")) { if strings.HasPrefix(_path, resolvedUnitDirAdminUser) {
if isUserFlag { if isUserFlag {
return true return true
} }

View File

@ -1,10 +1,16 @@
//go:build linux
package main package main
import ( import (
"fmt"
"os" "os"
"os/exec"
"os/user" "os/user"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"syscall"
"testing" "testing"
"github.com/containers/podman/v5/pkg/systemd/quadlet" "github.com/containers/podman/v5/pkg/systemd/quadlet"
@ -47,17 +53,21 @@ func TestIsUnambiguousName(t *testing.T) {
} }
func TestUnitDirs(t *testing.T) { func TestUnitDirs(t *testing.T) {
u, err := user.Current()
assert.Nil(t, err)
uidInt, err := strconv.Atoi(u.Uid)
assert.Nil(t, err)
if os.Getenv("_UNSHARED") != "true" {
unitDirs := getUnitDirs(false)
rootDirs := []string{} rootDirs := []string{}
rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirTemp, false, userLevelFilter) rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirTemp, false, userLevelFilter)
rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirAdmin, false, userLevelFilter) rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirAdmin, false, userLevelFilter)
rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirDistro, false, userLevelFilter) rootDirs = appendSubPaths(rootDirs, quadlet.UnitDirDistro, false, userLevelFilter)
unitDirs := getUnitDirs(false)
assert.Equal(t, unitDirs, rootDirs, "rootful unit dirs should match") assert.Equal(t, unitDirs, rootDirs, "rootful unit dirs should match")
configDir, err := os.UserConfigDir() configDir, err := os.UserConfigDir()
assert.Nil(t, err) assert.Nil(t, err)
u, err := user.Current()
assert.Nil(t, err)
rootlessDirs := []string{} rootlessDirs := []string{}
@ -99,7 +109,74 @@ func TestUnitDirs(t *testing.T) {
symlink := filepath.Join(symLinkTestBaseDir, "symlink") symlink := filepath.Join(symLinkTestBaseDir, "symlink")
err = os.Symlink(actualDir, symlink) err = os.Symlink(actualDir, symlink)
assert.Nil(t, err) assert.Nil(t, err)
t.Setenv("QUADLET_UNIT_DIRS", actualDir) t.Setenv("QUADLET_UNIT_DIRS", symlink)
unitDirs = getUnitDirs(true) unitDirs = getUnitDirs(true)
assert.Equal(t, unitDirs, []string{actualDir, innerDir}, "directory resolution should follow symlink") assert.Equal(t, unitDirs, []string{actualDir, innerDir}, "directory resolution should follow symlink")
// because chroot is only available for root,
// unshare the namespace and map user to root
c := exec.Command("/proc/self/exe", os.Args[1:]...)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: uidInt,
Size: 1,
},
},
}
c.Env = append(os.Environ(), "_UNSHARED=true")
err = c.Run()
assert.Nil(t, err)
} else {
fmt.Println(os.Args)
symLinkTestBaseDir, err := os.MkdirTemp("", "podman-symlinktest2")
assert.Nil(t, err)
defer os.RemoveAll(symLinkTestBaseDir)
rootF, err := os.Open("/")
assert.Nil(t, err)
defer rootF.Close()
defer func() {
err := rootF.Chdir()
assert.Nil(t, err)
err = syscall.Chroot(".")
assert.Nil(t, err)
}()
err = syscall.Chroot(symLinkTestBaseDir)
assert.Nil(t, err)
err = os.MkdirAll(quadlet.UnitDirAdmin, 0755)
assert.Nil(t, err)
err = os.RemoveAll(quadlet.UnitDirAdmin)
assert.Nil(t, err)
systemdDir := filepath.Join("/", "systemd")
userDir := filepath.Join("/", "users")
err = os.Mkdir(systemdDir, 0755)
assert.Nil(t, err)
err = os.Mkdir(userDir, 0755)
assert.Nil(t, err)
err = os.Symlink(userDir, filepath.Join(systemdDir, "users"))
assert.Nil(t, err)
err = os.Symlink(systemdDir, quadlet.UnitDirAdmin)
assert.Nil(t, err)
uidDir := filepath.Join(userDir, u.Uid)
err = os.Mkdir(uidDir, 0755)
assert.Nil(t, err)
uidDir2 := filepath.Join(userDir, strconv.Itoa(uidInt+1))
err = os.Mkdir(uidDir2, 0755)
assert.Nil(t, err)
t.Setenv("QUADLET_UNIT_DIRS", "")
unitDirs := getUnitDirs(false)
assert.NotContains(t, unitDirs, userDir, "rootful should not contain rootless")
unitDirs = getUnitDirs(true)
assert.NotContains(t, unitDirs, uidDir2, "rootless should not contain other users'")
}
} }