mirror of
https://github.com/containers/podman.git
synced 2025-08-16 04:15:29 +08:00

Bumps [github.com/containers/storage](https://github.com/containers/storage) from 1.19.2 to 1.20.1. - [Release notes](https://github.com/containers/storage/releases) - [Changelog](https://github.com/containers/storage/blob/master/docs/containers-storage-changes.md) - [Commits](https://github.com/containers/storage/compare/v1.19.2...v1.20.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
458 lines
13 KiB
Go
458 lines
13 KiB
Go
package storage
|
|
|
|
import (
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
drivers "github.com/containers/storage/drivers"
|
|
"github.com/containers/storage/pkg/idtools"
|
|
"github.com/containers/storage/pkg/unshare"
|
|
libcontainerUser "github.com/opencontainers/runc/libcontainer/user"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// getAdditionalSubIDs looks up the additional IDs configured for
|
|
// the specified user.
|
|
// The argument USERNAME is ignored for rootless users, as it is not
|
|
// possible to use an arbitrary entry in /etc/sub*id.
|
|
// Differently, if the username is not specified for root users, a
|
|
// default name is used.
|
|
func getAdditionalSubIDs(username string) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
var uids, gids []idtools.IDMap
|
|
|
|
if unshare.IsRootless() {
|
|
username = os.Getenv("USER")
|
|
if username == "" {
|
|
var id string
|
|
if os.Geteuid() == 0 {
|
|
id = strconv.Itoa(unshare.GetRootlessUID())
|
|
} else {
|
|
id = strconv.Itoa(os.Geteuid())
|
|
}
|
|
userID, err := user.LookupId(id)
|
|
if err == nil {
|
|
username = userID.Username
|
|
}
|
|
}
|
|
} else if username == "" {
|
|
username = RootAutoUserNsUser
|
|
}
|
|
mappings, err := idtools.NewIDMappings(username, username)
|
|
if err != nil {
|
|
logrus.Errorf("cannot find mappings for user %q: %v", username, err)
|
|
} else {
|
|
uids = mappings.UIDs()
|
|
gids = mappings.GIDs()
|
|
}
|
|
return uids, gids, nil
|
|
}
|
|
|
|
// getAvailableMappings returns the list of ranges that are usable by the current user.
|
|
// When running as root, it looks up the additional IDs assigned to the specified user.
|
|
// When running as rootless, the mappings assigned to the unprivileged user are converted
|
|
// to the IDs inside of the initial rootless user namespace.
|
|
func (s *store) getAvailableMappings() ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
if s.autoUIDMap == nil {
|
|
uids, gids, err := getAdditionalSubIDs(s.autoUsernsUser)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// Store the result so we don't need to look it up again next time
|
|
s.autoUIDMap, s.autoGIDMap = uids, gids
|
|
}
|
|
|
|
uids := s.autoUIDMap
|
|
gids := s.autoGIDMap
|
|
|
|
if !unshare.IsRootless() {
|
|
// No mapping to inner namespace needed
|
|
return copyIDMap(uids), copyIDMap(gids), nil
|
|
}
|
|
|
|
// We are already inside of the rootless user namespace.
|
|
// We need to remap the configured mappings to what is available
|
|
// inside of the rootless userns.
|
|
totaluid := 0
|
|
totalgid := 0
|
|
for _, u := range uids {
|
|
totaluid += u.Size
|
|
}
|
|
for _, g := range gids {
|
|
totalgid += g.Size
|
|
}
|
|
|
|
u := []idtools.IDMap{{ContainerID: 0, HostID: 1, Size: totaluid}}
|
|
g := []idtools.IDMap{{ContainerID: 0, HostID: 1, Size: totalgid}}
|
|
return u, g, nil
|
|
}
|
|
|
|
// parseMountedFiles returns the maximum UID and GID found in the /etc/passwd and
|
|
// /etc/group files.
|
|
func parseMountedFiles(containerMount, passwdFile, groupFile string) uint32 {
|
|
if passwdFile == "" {
|
|
passwdFile = filepath.Join(containerMount, "etc/passwd")
|
|
}
|
|
if groupFile == "" {
|
|
groupFile = filepath.Join(groupFile, "etc/group")
|
|
}
|
|
|
|
size := 0
|
|
|
|
users, err := libcontainerUser.ParsePasswdFile(passwdFile)
|
|
if err == nil {
|
|
for _, u := range users {
|
|
// Skip the "nobody" user otherwise we end up with 65536
|
|
// ids with most images
|
|
if u.Name == "nobody" {
|
|
continue
|
|
}
|
|
if u.Uid > size {
|
|
size = u.Uid
|
|
}
|
|
if u.Gid > size {
|
|
size = u.Gid
|
|
}
|
|
}
|
|
}
|
|
|
|
groups, err := libcontainerUser.ParseGroupFile(groupFile)
|
|
if err == nil {
|
|
for _, g := range groups {
|
|
if g.Name == "nobody" {
|
|
continue
|
|
}
|
|
if g.Gid > size {
|
|
size = g.Gid
|
|
}
|
|
}
|
|
}
|
|
|
|
return uint32(size)
|
|
}
|
|
|
|
// getMaxSizeFromImage returns the maximum ID used by the specified image.
|
|
// The layer stores must be already locked.
|
|
func (s *store) getMaxSizeFromImage(id string, image *Image, passwdFile, groupFile string) (uint32, error) {
|
|
lstore, err := s.LayerStore()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
lstores, err := s.ROLayerStores()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
size := uint32(0)
|
|
|
|
var topLayer *Layer
|
|
layerName := image.TopLayer
|
|
outer:
|
|
for {
|
|
for _, ls := range append([]ROLayerStore{lstore}, lstores...) {
|
|
layer, err := ls.Get(layerName)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if image.TopLayer == layerName {
|
|
topLayer = layer
|
|
}
|
|
for _, uid := range layer.UIDs {
|
|
if uid >= size {
|
|
size = uid + 1
|
|
}
|
|
}
|
|
for _, gid := range layer.GIDs {
|
|
if gid >= size {
|
|
size = gid + 1
|
|
}
|
|
}
|
|
layerName = layer.Parent
|
|
if layerName == "" {
|
|
break outer
|
|
}
|
|
continue outer
|
|
}
|
|
return 0, errors.Errorf("cannot find layer %q", layerName)
|
|
}
|
|
|
|
rlstore, err := s.LayerStore()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
layerOptions := &LayerOptions{
|
|
IDMappingOptions: IDMappingOptions{
|
|
HostUIDMapping: true,
|
|
HostGIDMapping: true,
|
|
UIDMap: nil,
|
|
GIDMap: nil,
|
|
},
|
|
}
|
|
|
|
// We need to create a temporary layer so we can mount it and lookup the
|
|
// maximum IDs used.
|
|
clayer, err := rlstore.Create(id, topLayer, nil, "", nil, layerOptions, false)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer rlstore.Delete(clayer.ID)
|
|
|
|
mountOptions := drivers.MountOpts{
|
|
MountLabel: "",
|
|
UidMaps: nil,
|
|
GidMaps: nil,
|
|
Options: nil,
|
|
}
|
|
|
|
mountpoint, err := rlstore.Mount(clayer.ID, mountOptions)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer rlstore.Unmount(clayer.ID, true)
|
|
|
|
userFilesSize := parseMountedFiles(mountpoint, passwdFile, groupFile)
|
|
if userFilesSize > size {
|
|
size = userFilesSize
|
|
}
|
|
|
|
return size, nil
|
|
}
|
|
|
|
// subtractHostIDs return the subtraction of the range USED from AVAIL. The range is specified
|
|
// by [HostID, HostID+Size).
|
|
// ContainerID is ignored.
|
|
func subtractHostIDs(avail idtools.IDMap, used idtools.IDMap) []idtools.IDMap {
|
|
switch {
|
|
case used.HostID <= avail.HostID && used.HostID+used.Size >= avail.HostID+avail.Size:
|
|
return nil
|
|
case used.HostID <= avail.HostID && used.HostID+used.Size > avail.HostID && used.HostID+used.Size < avail.HostID+avail.Size:
|
|
newContainerID := used.HostID + used.Size
|
|
newHostID := used.HostID + used.Size
|
|
r := idtools.IDMap{
|
|
ContainerID: newContainerID,
|
|
HostID: newHostID,
|
|
Size: avail.Size + avail.HostID - newHostID,
|
|
}
|
|
return []idtools.IDMap{r}
|
|
case used.HostID > avail.HostID && used.HostID < avail.HostID+avail.Size && used.HostID+used.Size >= avail.HostID+avail.Size:
|
|
r := idtools.IDMap{
|
|
ContainerID: avail.ContainerID,
|
|
HostID: avail.HostID,
|
|
Size: used.HostID - avail.HostID,
|
|
}
|
|
return []idtools.IDMap{r}
|
|
case used.HostID > avail.HostID && used.HostID < avail.HostID+avail.Size && used.HostID+used.Size < avail.HostID+avail.Size:
|
|
r1 := idtools.IDMap{
|
|
ContainerID: avail.ContainerID,
|
|
HostID: avail.HostID,
|
|
Size: used.HostID - avail.HostID,
|
|
}
|
|
r2 := idtools.IDMap{
|
|
ContainerID: used.ContainerID + used.Size,
|
|
HostID: used.HostID + used.Size,
|
|
Size: avail.HostID + avail.Size - used.HostID - used.Size,
|
|
}
|
|
return []idtools.IDMap{r1, r2}
|
|
default:
|
|
r := idtools.IDMap{
|
|
ContainerID: 0,
|
|
HostID: avail.HostID,
|
|
Size: avail.Size,
|
|
}
|
|
return []idtools.IDMap{r}
|
|
}
|
|
}
|
|
|
|
// subtractContainerIDs return the subtraction of the range USED from AVAIL. The range is specified
|
|
// by [ContainerID, ContainerID+Size).
|
|
// HostID is ignored.
|
|
func subtractContainerIDs(avail idtools.IDMap, used idtools.IDMap) []idtools.IDMap {
|
|
switch {
|
|
case used.ContainerID <= avail.ContainerID && used.ContainerID+used.Size >= avail.ContainerID+avail.Size:
|
|
return nil
|
|
case used.ContainerID <= avail.ContainerID && used.ContainerID+used.Size > avail.ContainerID && used.ContainerID+used.Size < avail.ContainerID+avail.Size:
|
|
newContainerID := used.ContainerID + used.Size
|
|
newHostID := used.HostID + used.Size
|
|
r := idtools.IDMap{
|
|
ContainerID: newContainerID,
|
|
HostID: newHostID,
|
|
Size: avail.Size + avail.ContainerID - newContainerID,
|
|
}
|
|
return []idtools.IDMap{r}
|
|
case used.ContainerID > avail.ContainerID && used.ContainerID < avail.ContainerID+avail.Size && used.ContainerID+used.Size >= avail.ContainerID+avail.Size:
|
|
r := idtools.IDMap{
|
|
ContainerID: avail.ContainerID,
|
|
HostID: avail.HostID,
|
|
Size: used.ContainerID - avail.ContainerID,
|
|
}
|
|
return []idtools.IDMap{r}
|
|
case used.ContainerID > avail.ContainerID && used.ContainerID < avail.ContainerID+avail.Size && used.ContainerID+used.Size < avail.ContainerID+avail.Size:
|
|
r1 := idtools.IDMap{
|
|
ContainerID: avail.ContainerID,
|
|
HostID: avail.HostID,
|
|
Size: used.ContainerID - avail.ContainerID,
|
|
}
|
|
r2 := idtools.IDMap{
|
|
ContainerID: used.ContainerID + used.Size,
|
|
HostID: used.HostID + used.Size,
|
|
Size: avail.ContainerID + avail.Size - used.ContainerID - used.Size,
|
|
}
|
|
return []idtools.IDMap{r1, r2}
|
|
default:
|
|
r := idtools.IDMap{
|
|
ContainerID: avail.ContainerID,
|
|
HostID: avail.HostID,
|
|
Size: avail.Size,
|
|
}
|
|
return []idtools.IDMap{r}
|
|
}
|
|
}
|
|
|
|
// subtractAll subtracts all usedIDs from the available IDs.
|
|
func subtractAll(availableIDs, usedIDs []idtools.IDMap, host bool) []idtools.IDMap {
|
|
for _, u := range usedIDs {
|
|
for i := 0; i < len(availableIDs); {
|
|
var prev []idtools.IDMap
|
|
if i > 0 {
|
|
prev = availableIDs[:i-1]
|
|
}
|
|
next := availableIDs[i+1:]
|
|
cur := availableIDs[i]
|
|
var newRanges []idtools.IDMap
|
|
if host {
|
|
newRanges = subtractHostIDs(cur, u)
|
|
} else {
|
|
newRanges = subtractContainerIDs(cur, u)
|
|
}
|
|
availableIDs = append(append(prev, newRanges...), next...)
|
|
i += len(newRanges)
|
|
}
|
|
}
|
|
return availableIDs
|
|
}
|
|
|
|
// findAvailableIDRange returns the list of IDs that are not used by existing containers.
|
|
// This function is used to lookup both UIDs and GIDs.
|
|
func findAvailableIDRange(size uint32, availableIDs, usedIDs []idtools.IDMap) ([]idtools.IDMap, error) {
|
|
var avail []idtools.IDMap
|
|
|
|
// ContainerID will be adjusted later.
|
|
for _, i := range availableIDs {
|
|
n := idtools.IDMap{
|
|
ContainerID: 0,
|
|
HostID: i.HostID,
|
|
Size: i.Size,
|
|
}
|
|
avail = append(avail, n)
|
|
}
|
|
avail = subtractAll(avail, usedIDs, true)
|
|
|
|
currentID := 0
|
|
remaining := size
|
|
// We know the size for each intervals, let's adjust the ContainerID for each
|
|
// of them.
|
|
for i := 0; i < len(avail); i++ {
|
|
avail[i].ContainerID = currentID
|
|
if uint32(avail[i].Size) >= remaining {
|
|
avail[i].Size = int(remaining)
|
|
return avail[:i+1], nil
|
|
}
|
|
remaining -= uint32(avail[i].Size)
|
|
}
|
|
|
|
return nil, errors.New("could not find enough available IDs")
|
|
}
|
|
|
|
// findAvailableRange returns both the list of UIDs and GIDs ranges that are not
|
|
// currently used by other containers.
|
|
// It is a wrapper for findAvailableIDRange.
|
|
func findAvailableRange(sizeUID, sizeGID uint32, availableUIDs, availableGIDs, usedUIDs, usedGIDs []idtools.IDMap) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
UIDMap, err := findAvailableIDRange(sizeUID, availableUIDs, usedUIDs)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
GIDMap, err := findAvailableIDRange(sizeGID, availableGIDs, usedGIDs)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return UIDMap, GIDMap, nil
|
|
}
|
|
|
|
// getAutoUserNS creates an automatic user namespace
|
|
func (s *store) getAutoUserNS(id string, options *AutoUserNsOptions, image *Image) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
requestedSize := uint32(0)
|
|
initialSize := uint32(1)
|
|
if options.Size > 0 {
|
|
requestedSize = options.Size
|
|
}
|
|
if options.InitialSize > 0 {
|
|
initialSize = options.InitialSize
|
|
}
|
|
|
|
availableUIDs, availableGIDs, err := s.getAvailableMappings()
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "cannot read mappings")
|
|
}
|
|
|
|
// Look every container that is using a user namespace and store
|
|
// the intervals that are already used.
|
|
containers, err := s.Containers()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var usedUIDs, usedGIDs []idtools.IDMap
|
|
for _, c := range containers {
|
|
usedUIDs = append(usedUIDs, c.UIDMap...)
|
|
usedGIDs = append(usedGIDs, c.GIDMap...)
|
|
}
|
|
|
|
size := requestedSize
|
|
|
|
// If there is no requestedSize, lookup the maximum used IDs in the layers
|
|
// metadata. Make sure the size is at least s.autoNsMinSize and it is not
|
|
// bigger than s.autoNsMaxSize.
|
|
// This is a best effort heuristic.
|
|
if requestedSize == 0 {
|
|
size = initialSize
|
|
if s.autoNsMinSize > size {
|
|
size = s.autoNsMinSize
|
|
}
|
|
if image != nil {
|
|
sizeFromImage, err := s.getMaxSizeFromImage(id, image, options.PasswdFile, options.GroupFile)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if sizeFromImage > size {
|
|
size = sizeFromImage
|
|
}
|
|
}
|
|
if s.autoNsMaxSize > 0 && size > s.autoNsMaxSize {
|
|
return nil, nil, errors.Errorf("the container needs a user namespace with size %q that is bigger than the maximum value allowed with userns=auto %q", size, s.autoNsMaxSize)
|
|
}
|
|
}
|
|
// Make sure the specified additional IDs are not used as part of the automatic
|
|
// mapping
|
|
usedUIDs = append(usedUIDs, options.AdditionalUIDMappings...)
|
|
usedGIDs = append(usedGIDs, options.AdditionalGIDMappings...)
|
|
availableUIDs, availableGIDs, err = findAvailableRange(size, size, availableUIDs, availableGIDs, usedUIDs, usedGIDs)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// We need to make sure the specified container IDs are also dropped from the automatic
|
|
// namespaces we have found.
|
|
if len(options.AdditionalUIDMappings) > 0 {
|
|
availableUIDs = subtractAll(availableUIDs, options.AdditionalUIDMappings, false)
|
|
}
|
|
if len(options.AdditionalGIDMappings) > 0 {
|
|
availableGIDs = subtractAll(availableGIDs, options.AdditionalGIDMappings, false)
|
|
}
|
|
|
|
return append(availableUIDs, options.AdditionalUIDMappings...), append(availableGIDs, options.AdditionalGIDMappings...), nil
|
|
}
|