Merge pull request #8816 from giuseppe/automatically-split-userns-mappings

rootless: automatically split userns ranges
This commit is contained in:
OpenShift Merge Robot
2021-01-07 09:35:01 -05:00
committed by GitHub
3 changed files with 253 additions and 15 deletions

View File

@ -529,6 +529,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
}
}
availableUIDs, availableGIDs, err := rootless.GetAvailableIDMaps()
if err != nil {
return nil, err
}
g.Config.Linux.UIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.UIDMappings, availableUIDs)
g.Config.Linux.GIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.GIDMappings, availableGIDs)
// Hostname handling:
// If we have a UTS namespace, set Hostname in the OCI spec.
// Set the HOSTNAME environment variable unless explicitly overridden by
@ -536,6 +543,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
// set it to the host's hostname instead.
hostname := c.Hostname()
foundUTS := false
for _, i := range c.config.Spec.Linux.Namespaces {
if i.Type == spec.UTSNamespace && i.Path == "" {
foundUTS = true

View File

@ -2,10 +2,12 @@ package rootless
import (
"os"
"sort"
"sync"
"github.com/containers/storage"
"github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@ -50,24 +52,151 @@ func TryJoinPauseProcess(pausePidPath string) (bool, int, error) {
}
var (
availableGids int64
availableGidsErr error
availableGidsOnce sync.Once
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) {
availableGidsOnce.Do(func() {
idMap, err := user.ParseIDMapFile("/proc/self/gid_map")
if err != nil {
availableGidsErr = err
return
}
availableGids = int64(0)
for _, r := range idMap {
availableGids += r.Count
}
})
return availableGids, availableGidsErr
gids, err := GetAvailableGidMap()
if err != nil {
return -1, err
}
return countAvailableIDs(gids), nil
}
// findIDInMappings find the 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)
}
}

View File

@ -0,0 +1,101 @@
package rootless
import (
"reflect"
"testing"
"github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
func TestMaybeSplitMappings(t *testing.T) {
mappings := []spec.LinuxIDMapping{
{
ContainerID: 0,
HostID: 0,
Size: 2,
},
}
desiredMappings := []spec.LinuxIDMapping{
{
ContainerID: 0,
HostID: 0,
Size: 1,
},
{
ContainerID: 1,
HostID: 1,
Size: 1,
},
}
availableMappings := []user.IDMap{
{
ID: 1,
ParentID: 1000000,
Count: 65536,
},
{
ID: 0,
ParentID: 1000,
Count: 1,
},
}
newMappings := MaybeSplitMappings(mappings, availableMappings)
if !reflect.DeepEqual(newMappings, desiredMappings) {
t.Fatal("wrong mappings generated")
}
mappings = []spec.LinuxIDMapping{
{
ContainerID: 0,
HostID: 0,
Size: 2,
},
}
desiredMappings = []spec.LinuxIDMapping{
{
ContainerID: 0,
HostID: 0,
Size: 2,
},
}
availableMappings = []user.IDMap{
{
ID: 0,
ParentID: 1000000,
Count: 65536,
},
}
newMappings = MaybeSplitMappings(mappings, availableMappings)
if !reflect.DeepEqual(newMappings, desiredMappings) {
t.Fatal("wrong mappings generated")
}
mappings = []spec.LinuxIDMapping{
{
ContainerID: 0,
HostID: 0,
Size: 1,
},
}
desiredMappings = []spec.LinuxIDMapping{
{
ContainerID: 0,
HostID: 0,
Size: 1,
},
}
availableMappings = []user.IDMap{
{
ID: 10000,
ParentID: 10000,
Count: 65536,
},
}
newMappings = MaybeSplitMappings(mappings, availableMappings)
if !reflect.DeepEqual(newMappings, desiredMappings) {
t.Fatal("wrong mappings generated")
}
}