diff --git a/idtools/idmap.go b/idtools/idmap.go new file mode 100644 index 000000000..be34a0f0e --- /dev/null +++ b/idtools/idmap.go @@ -0,0 +1,115 @@ +/* + Copyright The Soci Snapshotter Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The containerd Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Copied from https://github.com/containerd/containerd/blob/2ca3ff87255a4aa6b4244cb942033d45b6d44546/internal/userns/idmap.go +/* + This file is copied and customized based on + https://github.com/moby/moby/blob/master/pkg/idtools/idtools.go +*/ +package idtools + +import ( + "errors" + "fmt" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +const invalidID = 1<<32 - 1 + +var invalidUser = User{Uid: invalidID, Gid: invalidID} + +// User is a Uid and Gid pair of a user +// +//nolint:revive +type User struct { + Uid uint32 + Gid uint32 +} + +// IDMap contains the mappings of Uids and Gids. +// +//nolint:revive +type IDMap struct { + UidMap []specs.LinuxIDMapping `json:"UidMap"` + GidMap []specs.LinuxIDMapping `json:"GidMap"` +} + +// ToHost returns the host user ID pair for the container ID pair. +func (i IDMap) ToHost(pair User) (User, error) { + var ( + target User + err error + ) + target.Uid, err = toHost(pair.Uid, i.UidMap) + if err != nil { + return invalidUser, err + } + target.Gid, err = toHost(pair.Gid, i.GidMap) + if err != nil { + return invalidUser, err + } + return target, nil +} + +// Empty returns true if there are no id mappings +func (i IDMap) Empty() bool { + return len(i.UidMap) == 0 && len(i.GidMap) == 0 +} + +// toHost takes an id mapping and a remapped ID, and translates the +// ID to the mapped host ID. If no map is provided, then the translation +// assumes a 1-to-1 mapping and returns the passed in id # +func toHost(contID uint32, idMap []specs.LinuxIDMapping) (uint32, error) { + if idMap == nil { + return contID, nil + } + for _, m := range idMap { + high, err := safeSum(m.ContainerID, m.Size) + if err != nil { + break + } + if contID >= m.ContainerID && contID < high { + hostID, err := safeSum(m.HostID, contID-m.ContainerID) + if err != nil || hostID == invalidID { + break + } + return hostID, nil + } + } + return invalidID, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID) +} + +// safeSum returns the sum of x and y. or an error if the result overflows +func safeSum(x, y uint32) (uint32, error) { + z := x + y + if z < x || z < y { + return invalidID, errors.New("ID overflow") + } + return z, nil +} diff --git a/idtools/idmap_test.go b/idtools/idmap_test.go new file mode 100644 index 000000000..b79c3ebe8 --- /dev/null +++ b/idtools/idmap_test.go @@ -0,0 +1,264 @@ +/* + Copyright The Soci Snapshotter Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright The containerd Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +// Copied from https://github.com/containerd/containerd/blob/2ca3ff87255a4aa6b4244cb942033d45b6d44546/internal/userns/idmap_test.go +package idtools + +import ( + "testing" + + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/assert" +) + +func TestToHost(t *testing.T) { + idmap := IDMap{ + UidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1, + Size: 2, + }, + { + ContainerID: 2, + HostID: 4, + Size: 1000, + }, + }, + GidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 2, + Size: 4, + }, + { + ContainerID: 4, + HostID: 8, + Size: 1000, + }, + }, + } + for _, test := range []struct { + container User + host User + }{ + { + container: User{ + Uid: 0, + Gid: 0, + }, + host: User{ + Uid: 1, + Gid: 2, + }, + }, + { + container: User{ + Uid: 1, + Gid: 1, + }, + host: User{ + Uid: 2, + Gid: 3, + }, + }, + { + container: User{ + Uid: 2, + Gid: 4, + }, + host: User{ + Uid: 4, + Gid: 8, + }, + }, + { + container: User{ + Uid: 100, + Gid: 200, + }, + host: User{ + Uid: 102, + Gid: 204, + }, + }, + { + container: User{ + Uid: 1001, + Gid: 1003, + }, + host: User{ + Uid: 1003, + Gid: 1007, + }, + }, + { + container: User{ + Uid: 1004, + Gid: 1008, + }, + host: invalidUser, + }, + { + container: User{ + Uid: 2000, + Gid: 2000, + }, + host: invalidUser, + }, + } { + r, err := idmap.ToHost(test.container) + assert.Equal(t, test.host, r) + if r == invalidUser { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } +} +func TestToHostOverflow(t *testing.T) { + for _, test := range []struct { + idmap IDMap + user User + }{ + { + idmap: IDMap{ + UidMap: []specs.LinuxIDMapping{ + { + ContainerID: 1<<32 - 1000, + HostID: 1000, + Size: 10000, + }, + }, + GidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 10000, + }, + }, + }, + user: User{ + Uid: 1<<32 - 100, + Gid: 0, + }, + }, + { + idmap: IDMap{ + UidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 10000, + }, + }, + GidMap: []specs.LinuxIDMapping{ + { + ContainerID: 1<<32 - 1000, + HostID: 1000, + Size: 10000, + }, + }, + }, + user: User{ + Uid: 0, + Gid: 1<<32 - 100, + }, + }, + { + idmap: IDMap{ + UidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 1<<32 - 1, + }, + }, + GidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 1<<32 - 1, + }, + }, + }, + user: User{ + Uid: 1<<32 - 2, + Gid: 0, + }, + }, + { + idmap: IDMap{ + UidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 1<<32 - 1, + }, + }, + GidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 1<<32 - 1, + }, + }, + }, + user: User{ + Uid: 0, + Gid: 1<<32 - 2, + }, + }, + { + idmap: IDMap{ + UidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1, + Size: 1<<32 - 1, + }, + }, + GidMap: []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1, + Size: 1<<32 - 1, + }, + }, + }, + user: User{ + Uid: 1<<32 - 2, + Gid: 1<<32 - 2, + }, + }, + } { + r, err := test.idmap.ToHost(test.user) + assert.Error(t, err) + assert.Equal(t, r, invalidUser) + } +}