Skip to content

Commit a31262c

Browse files
authored
Merge pull request #4134 from cyphar/ns-path-regression
specconv: temporarily allow userns path and mapping if they match
2 parents 99f7fa1 + 482e563 commit a31262c

File tree

6 files changed

+63
-19
lines changed

6 files changed

+63
-19
lines changed

libcontainer/configs/config.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ type Rlimit struct {
2222

2323
// IDMap represents UID/GID Mappings for User Namespaces.
2424
type IDMap struct {
25-
ContainerID int `json:"container_id"`
26-
HostID int `json:"host_id"`
27-
Size int `json:"size"`
25+
ContainerID int64 `json:"container_id"`
26+
HostID int64 `json:"host_id"`
27+
Size int64 `json:"size"`
2828
}
2929

3030
// Seccomp represents syscall restrictions

libcontainer/configs/config_linux.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package configs
33
import (
44
"errors"
55
"fmt"
6+
"math"
67
)
78

89
var (
@@ -30,11 +31,18 @@ func (c Config) HostUID(containerId int) (int, error) {
3031
if len(c.UIDMappings) == 0 {
3132
return -1, errNoUIDMap
3233
}
33-
id, found := c.hostIDFromMapping(containerId, c.UIDMappings)
34+
id, found := c.hostIDFromMapping(int64(containerId), c.UIDMappings)
3435
if !found {
3536
return -1, fmt.Errorf("user namespaces enabled, but no mapping found for uid %d", containerId)
3637
}
37-
return id, nil
38+
// If we are a 32-bit binary running on a 64-bit system, it's possible
39+
// the mapped user is too large to store in an int, which means we
40+
// cannot do the mapping. We can't just return an int64, because
41+
// os.Setuid() takes an int.
42+
if id > math.MaxInt {
43+
return -1, fmt.Errorf("mapping for uid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt)
44+
}
45+
return int(id), nil
3846
}
3947
// Return unchanged id.
4048
return containerId, nil
@@ -53,11 +61,18 @@ func (c Config) HostGID(containerId int) (int, error) {
5361
if len(c.GIDMappings) == 0 {
5462
return -1, errNoGIDMap
5563
}
56-
id, found := c.hostIDFromMapping(containerId, c.GIDMappings)
64+
id, found := c.hostIDFromMapping(int64(containerId), c.GIDMappings)
5765
if !found {
5866
return -1, fmt.Errorf("user namespaces enabled, but no mapping found for gid %d", containerId)
5967
}
60-
return id, nil
68+
// If we are a 32-bit binary running on a 64-bit system, it's possible
69+
// the mapped user is too large to store in an int, which means we
70+
// cannot do the mapping. We can't just return an int64, because
71+
// os.Setgid() takes an int.
72+
if id > math.MaxInt {
73+
return -1, fmt.Errorf("mapping for gid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt)
74+
}
75+
return int(id), nil
6176
}
6277
// Return unchanged id.
6378
return containerId, nil
@@ -71,7 +86,7 @@ func (c Config) HostRootGID() (int, error) {
7186

7287
// Utility function that gets a host ID for a container ID from user namespace map
7388
// if that ID is present in the map.
74-
func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) {
89+
func (c Config) hostIDFromMapping(containerID int64, uMap []IDMap) (int64, bool) {
7590
for _, m := range uMap {
7691
if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) {
7792
hostID := m.HostID + (containerID - m.ContainerID)

libcontainer/container_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1351,7 +1351,7 @@ func ignoreTerminateErrors(err error) error {
13511351

13521352
func requiresRootOrMappingTool(c *configs.Config) bool {
13531353
gidMap := []configs.IDMap{
1354-
{ContainerID: 0, HostID: os.Getegid(), Size: 1},
1354+
{ContainerID: 0, HostID: int64(os.Getegid()), Size: 1},
13551355
}
13561356
return !reflect.DeepEqual(c.GIDMappings, gidMap)
13571357
}

libcontainer/specconv/spec_linux.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -522,9 +522,9 @@ func toConfigIDMap(specMaps []specs.LinuxIDMapping) []configs.IDMap {
522522
idmaps := make([]configs.IDMap, len(specMaps))
523523
for i, id := range specMaps {
524524
idmaps[i] = configs.IDMap{
525-
ContainerID: int(id.ContainerID),
526-
HostID: int(id.HostID),
527-
Size: int(id.Size),
525+
ContainerID: int64(id.ContainerID),
526+
HostID: int64(id.HostID),
527+
Size: int64(id.Size),
528528
}
529529
}
530530
return idmaps
@@ -973,18 +973,32 @@ func setupUserNamespace(spec *specs.Spec, config *configs.Config) error {
973973
config.GIDMappings = toConfigIDMap(spec.Linux.GIDMappings)
974974
}
975975
if path := config.Namespaces.PathOf(configs.NEWUSER); path != "" {
976-
// We cannot allow uid or gid mappings to be set if we are also asked
977-
// to join a userns.
978-
if config.UIDMappings != nil || config.GIDMappings != nil {
979-
return errors.New("user namespaces enabled, but both namespace path and mapping specified -- you may only provide one")
980-
}
981976
// Cache the current userns mappings in our configuration, so that we
982977
// can calculate uid and gid mappings within runc. These mappings are
983978
// never used for configuring the container if the path is set.
984979
uidMap, gidMap, err := userns.GetUserNamespaceMappings(path)
985980
if err != nil {
986981
return fmt.Errorf("failed to cache mappings for userns: %w", err)
987982
}
983+
// We cannot allow uid or gid mappings to be set if we are also asked
984+
// to join a userns.
985+
if config.UIDMappings != nil || config.GIDMappings != nil {
986+
// FIXME: It turns out that containerd and CRIO pass both a userns
987+
// path and the mappings of the namespace in the same config.json.
988+
// Such a configuration is technically not valid, but we used to
989+
// require mappings be specified, and thus users worked around our
990+
// bug -- so we can't regress it at the moment. But we also don't
991+
// want to produce broken behaviour if the mapping doesn't match
992+
// the userns. So (for now) we output a warning if the actual
993+
// userns mappings match the configuration, otherwise we return an
994+
// error.
995+
if !userns.IsSameMapping(uidMap, config.UIDMappings) ||
996+
!userns.IsSameMapping(gidMap, config.GIDMappings) {
997+
return errors.New("user namespaces enabled, but both namespace path and non-matching mapping specified -- you may only provide one")
998+
}
999+
logrus.Warnf("config.json has both a userns path to join and a matching userns mapping specified -- you may only provide one. Future versions of runc may return an error with this configuration, please report a bug on <https://github.com/opencontainers/runc> if you see this warning and cannot update your configuration.")
1000+
}
1001+
9881002
config.UIDMappings = uidMap
9891003
config.GIDMappings = gidMap
9901004
logrus.WithFields(logrus.Fields{

libcontainer/specconv/spec_linux_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,8 @@ func TestUserNamespaceMappingAndPath(t *testing.T) {
637637
Spec: spec,
638638
})
639639

640-
if !strings.Contains(err.Error(), "user namespaces enabled, but both namespace path and mapping specified") {
641-
t.Errorf("user namespace with mapping and namespace path should be forbidden")
640+
if !strings.Contains(err.Error(), "both namespace path and non-matching mapping specified") {
641+
t.Errorf("user namespace with path and non-matching mapping should be forbidden, got error %v", err)
642642
}
643643
}
644644

libcontainer/userns/userns_maps_linux.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,18 @@ func GetUserNamespaceMappings(nsPath string) (uidMap, gidMap []configs.IDMap, er
169169

170170
return uidMap, gidMap, nil
171171
}
172+
173+
// IsSameMapping returns whether or not the two id mappings are the same. Note
174+
// that if the order of the mappings is different, or a mapping has been split,
175+
// the mappings will be considered different.
176+
func IsSameMapping(a, b []configs.IDMap) bool {
177+
if len(a) != len(b) {
178+
return false
179+
}
180+
for idx := range a {
181+
if a[idx] != b[idx] {
182+
return false
183+
}
184+
}
185+
return true
186+
}

0 commit comments

Comments
 (0)