Skip to content

Commit

Permalink
configs: make id mappings int64 to better handle 32-bit
Browse files Browse the repository at this point in the history
(This is a cherry-pick of 482e563.)

Using ints for all of our mapping structures means that a 32-bit binary
errors out when trying to parse /proc/self/*id_map:

  failed to cache mappings for userns: failed to parse uid_map of userns /proc/1/ns/user:
  parsing id map failed: invalid format in line "         0          0 4294967295": integer overflow on token 4294967295

This issue was unearthed by commit 1912d59 ("*: actually support
joining a userns with a new container") but the underlying issue has
been present since the docker/libcontainer days.

In theory, switching to uint32 (to match the spec) instead of int64
would also work, but keeping everything signed seems much less
error-prone. It's also important to note that a mapping might be too
large for an int on 32-bit, so we detect this during the mapping.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
  • Loading branch information
cyphar committed Dec 14, 2023
1 parent e65d4ca commit 617db78
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 13 deletions.
6 changes: 3 additions & 3 deletions libcontainer/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ type Rlimit struct {

// IDMap represents UID/GID Mappings for User Namespaces.
type IDMap struct {
ContainerID int `json:"container_id"`
HostID int `json:"host_id"`
Size int `json:"size"`
ContainerID int64 `json:"container_id"`
HostID int64 `json:"host_id"`
Size int64 `json:"size"`
}

// Seccomp represents syscall restrictions
Expand Down
30 changes: 24 additions & 6 deletions libcontainer/configs/config_linux.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package configs

import "errors"
import (
"errors"
"fmt"
"math"
)

var (
errNoUIDMap = errors.New("User namespaces enabled, but no uid mappings found.")
Expand All @@ -16,11 +20,18 @@ func (c Config) HostUID(containerId int) (int, error) {
if c.UidMappings == nil {
return -1, errNoUIDMap
}
id, found := c.hostIDFromMapping(containerId, c.UidMappings)
id, found := c.hostIDFromMapping(int64(containerId), c.UidMappings)
if !found {
return -1, errNoUserMap
}
return id, nil
// If we are a 32-bit binary running on a 64-bit system, it's possible
// the mapped user is too large to store in an int, which means we
// cannot do the mapping. We can't just return an int64, because
// os.Setuid() takes an int.
if id > math.MaxInt {
return -1, fmt.Errorf("mapping for uid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt)
}
return int(id), nil
}
// Return unchanged id.
return containerId, nil
Expand All @@ -39,11 +50,18 @@ func (c Config) HostGID(containerId int) (int, error) {
if c.GidMappings == nil {
return -1, errNoGIDMap
}
id, found := c.hostIDFromMapping(containerId, c.GidMappings)
id, found := c.hostIDFromMapping(int64(containerId), c.GidMappings)
if !found {
return -1, errNoGroupMap
}
return id, nil
// If we are a 32-bit binary running on a 64-bit system, it's possible
// the mapped user is too large to store in an int, which means we
// cannot do the mapping. We can't just return an int64, because
// os.Setgid() takes an int.
if id > math.MaxInt {
return -1, fmt.Errorf("mapping for gid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt)
}
return int(id), nil
}
// Return unchanged id.
return containerId, nil
Expand All @@ -57,7 +75,7 @@ func (c Config) HostRootGID() (int, error) {

// Utility function that gets a host ID for a container ID from user namespace map
// if that ID is present in the map.
func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) {
func (c Config) hostIDFromMapping(containerID int64, uMap []IDMap) (int64, bool) {
for _, m := range uMap {
if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) {
hostID := m.HostID + (containerID - m.ContainerID)
Expand Down
2 changes: 1 addition & 1 deletion libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2268,7 +2268,7 @@ func ignoreTerminateErrors(err error) error {

func requiresRootOrMappingTool(c *configs.Config) bool {
gidMap := []configs.IDMap{
{ContainerID: 0, HostID: os.Getegid(), Size: 1},
{ContainerID: 0, HostID: int64(os.Getegid()), Size: 1},
}
return !reflect.DeepEqual(c.GidMappings, gidMap)
}
6 changes: 3 additions & 3 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,9 +927,9 @@ next:
func setupUserNamespace(spec *specs.Spec, config *configs.Config) error {
create := func(m specs.LinuxIDMapping) configs.IDMap {
return configs.IDMap{
HostID: int(m.HostID),
ContainerID: int(m.ContainerID),
Size: int(m.Size),
HostID: int64(m.HostID),
ContainerID: int64(m.ContainerID),
Size: int64(m.Size),
}
}
if spec.Linux != nil {
Expand Down

0 comments on commit 617db78

Please sign in to comment.