mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

The code currently tried to avoid joining the userns from conmon directly and rather joined to only read the pid file and then send this back to use so we could join the userns. From the comment this was done because we could not read the pid file. However this is no longer true as of commit 49eb5af301 and file is no always owned by the real user. This means we can just remove this special logic and join the namespace directly there. A test has been added to check the rejoin logic with a custom uidmapping. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
206 lines
5.1 KiB
Go
206 lines
5.1 KiB
Go
package rootless
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/containers/storage/pkg/fileutils"
|
|
"github.com/containers/storage/pkg/lockfile"
|
|
"github.com/moby/sys/user"
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
// TryJoinPauseProcess attempts to join the namespaces of the pause PID via
|
|
// TryJoinFromFilePaths. If joining fails, it attempts to delete the specified
|
|
// file.
|
|
func TryJoinPauseProcess(pausePidPath string) (bool, int, error) {
|
|
if err := fileutils.Exists(pausePidPath); err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return false, -1, nil
|
|
}
|
|
return false, -1, err
|
|
}
|
|
|
|
became, ret, err := TryJoinFromFilePaths("", []string{pausePidPath})
|
|
if err == nil {
|
|
return became, ret, nil
|
|
}
|
|
|
|
// It could not join the pause process, let's lock the file before trying to delete it.
|
|
pidFileLock, err := lockfile.GetLockFile(pausePidPath)
|
|
if err != nil {
|
|
// The file was deleted by another process.
|
|
if os.IsNotExist(err) {
|
|
return false, -1, nil
|
|
}
|
|
return false, -1, fmt.Errorf("acquiring lock on %s: %w", pausePidPath, err)
|
|
}
|
|
|
|
pidFileLock.Lock()
|
|
defer func() {
|
|
pidFileLock.Unlock()
|
|
}()
|
|
|
|
// Now the pause PID file is locked. Try to join once again in case it changed while it was not locked.
|
|
became, ret, err = TryJoinFromFilePaths("", []string{pausePidPath})
|
|
if err != nil {
|
|
// It is still failing. We can safely remove it.
|
|
os.Remove(pausePidPath)
|
|
return false, -1, nil //nolint: nilerr
|
|
}
|
|
return became, ret, err
|
|
}
|
|
|
|
var (
|
|
uidMap []user.IDMap
|
|
uidMapError error
|
|
uidMapOnce sync.Once
|
|
|
|
gidMap []user.IDMap
|
|
gidMapError error
|
|
gidMapOnce sync.Once
|
|
)
|
|
|
|
// GetAvailableUIDMap returns the UID mappings in the
|
|
// current user namespace.
|
|
func GetAvailableUIDMap() ([]user.IDMap, error) {
|
|
uidMapOnce.Do(func() {
|
|
var err error
|
|
uidMap, err = user.ParseIDMapFile("/proc/self/uid_map")
|
|
if err != nil {
|
|
uidMapError = err
|
|
return
|
|
}
|
|
})
|
|
return uidMap, uidMapError
|
|
}
|
|
|
|
// GetAvailableGIDMap returns the GID mappings in the
|
|
// current user namespace.
|
|
func GetAvailableGIDMap() ([]user.IDMap, error) {
|
|
gidMapOnce.Do(func() {
|
|
var err error
|
|
gidMap, err = user.ParseIDMapFile("/proc/self/gid_map")
|
|
if err != nil {
|
|
gidMapError = err
|
|
return
|
|
}
|
|
})
|
|
return gidMap, gidMapError
|
|
}
|
|
|
|
// GetAvailableIDMaps returns the UID and GID mappings in the
|
|
// current user namespace.
|
|
func GetAvailableIDMaps() ([]user.IDMap, []user.IDMap, error) {
|
|
u, err := GetAvailableUIDMap()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
g, err := GetAvailableGIDMap()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return u, g, nil
|
|
}
|
|
|
|
func countAvailableIDs(mappings []user.IDMap) int64 {
|
|
availableUids := int64(0)
|
|
for _, r := range mappings {
|
|
availableUids += r.Count
|
|
}
|
|
return availableUids
|
|
}
|
|
|
|
// GetAvailableUids returns how many UIDs are available in the
|
|
// current user namespace.
|
|
func GetAvailableUids() (int64, error) {
|
|
uids, err := GetAvailableUIDMap()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
return countAvailableIDs(uids), nil
|
|
}
|
|
|
|
// GetAvailableGids returns how many GIDs are available in the
|
|
// current user namespace.
|
|
func GetAvailableGids() (int64, error) {
|
|
gids, err := GetAvailableGIDMap()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
return countAvailableIDs(gids), nil
|
|
}
|
|
|
|
// findIDInMappings find the mapping that contains the specified ID.
|
|
// It assumes availableMappings is sorted by ID.
|
|
func findIDInMappings(id int64, availableMappings []user.IDMap) *user.IDMap {
|
|
i := sort.Search(len(availableMappings), func(i int) bool {
|
|
return availableMappings[i].ID <= id
|
|
})
|
|
if i < 0 || i >= len(availableMappings) {
|
|
return nil
|
|
}
|
|
r := &availableMappings[i]
|
|
if id >= r.ID && id < r.ID+r.Count {
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MaybeSplitMappings checks whether the specified OCI mappings are possible
|
|
// in the current user namespace or the specified ranges must be split.
|
|
func MaybeSplitMappings(mappings []spec.LinuxIDMapping, availableMappings []user.IDMap) []spec.LinuxIDMapping {
|
|
var ret []spec.LinuxIDMapping
|
|
var overflow spec.LinuxIDMapping
|
|
overflow.Size = 0
|
|
consumed := 0
|
|
sort.Slice(availableMappings, func(i, j int) bool {
|
|
return availableMappings[i].ID > availableMappings[j].ID
|
|
})
|
|
for {
|
|
cur := overflow
|
|
// if there is no overflow left from the previous request, get the next one
|
|
if cur.Size == 0 {
|
|
if consumed == len(mappings) {
|
|
// all done
|
|
return ret
|
|
}
|
|
cur = mappings[consumed]
|
|
consumed++
|
|
}
|
|
|
|
// Find the range where the first specified ID is present
|
|
r := findIDInMappings(int64(cur.HostID), availableMappings)
|
|
if r == nil {
|
|
// The requested range is not available. Just return the original request
|
|
// and let other layers deal with it.
|
|
return mappings
|
|
}
|
|
|
|
offsetInRange := cur.HostID - uint32(r.ID)
|
|
|
|
usableIDs := uint32(r.Count) - offsetInRange
|
|
|
|
// the current range can satisfy the whole request
|
|
if usableIDs >= cur.Size {
|
|
// reset the overflow
|
|
overflow.Size = 0
|
|
} else {
|
|
// the current range can satisfy the request partially
|
|
// so move the rest to overflow
|
|
overflow.Size = cur.Size - usableIDs
|
|
overflow.ContainerID = cur.ContainerID + usableIDs
|
|
overflow.HostID = cur.HostID + usableIDs
|
|
|
|
// and cap to the usableIDs count
|
|
cur.Size = usableIDs
|
|
}
|
|
ret = append(ret, cur)
|
|
}
|
|
}
|