Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion pkg/cdi/container-edits.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,15 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {

if len(e.Mounts) > 0 {
for _, m := range e.Mounts {
mnt := &Mount{m}

specgen.RemoveMount(m.ContainerPath)
specgen.AddMount((&Mount{m}).toOCI())

if !specHasUserNamespace(spec) {
specgen.AddMount(mnt.toOCI())
} else {
specgen.AddMount(mnt.toOCI(withIDMapForBindMount()))
}
}
sortMounts(&specgen)
}
Expand Down Expand Up @@ -465,3 +472,16 @@ func (m orderedMounts) Swap(i, j int) {
func (m orderedMounts) parts(i int) int {
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
}

// specHasUserNamespace returns true if the OCI Spec has a Linux UserNamespace.
func specHasUserNamespace(spec *oci.Spec) bool {
if spec == nil || spec.Linux == nil {
return false
}
for _, ns := range spec.Linux.Namespaces {
if ns.Type == oci.UserNamespace {
return true
}
}
return false
}
98 changes: 98 additions & 0 deletions pkg/cdi/container-edits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,104 @@ func TestApplyContainerEdits(t *testing.T) {
},
},
},
{
name: "mount added to container with Linux user namespace and uid/gid mappings",
spec: &oci.Spec{
Linux: &oci.Linux{
UIDMappings: []oci.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 999,
},
},
GIDMappings: []oci.LinuxIDMapping{
{
ContainerID: 0,
HostID: 2000,
Size: 777,
},
},
Namespaces: []oci.LinuxNamespace{
{
Type: oci.UserNamespace,
Path: "/foo/bar",
},
},
},
Mounts: []oci.Mount{
{
Source: "/some/host/path1",
Destination: "/dest/path/c",
},
{
Source: "/some/host/path2",
Destination: "/dest/path/b",
},
},
},
edits: &cdi.ContainerEdits{
Mounts: []*cdi.Mount{
{
HostPath: "/some/host/path3",
ContainerPath: "/dest/path/a",
Type: "bind",
},
{
HostPath: "/some/host/path4",
ContainerPath: "/dest/path/d",
Type: "bind",
Options: []string{"rbind"},
},
},
},
result: &oci.Spec{
Linux: &oci.Linux{
UIDMappings: []oci.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 999,
},
},
GIDMappings: []oci.LinuxIDMapping{
{
ContainerID: 0,
HostID: 2000,
Size: 777,
},
},
Namespaces: []oci.LinuxNamespace{
{
Type: oci.UserNamespace,
Path: "/foo/bar",
},
},
},
Mounts: []oci.Mount{
{
Source: "/some/host/path1",
Destination: "/dest/path/c",
},
{
Source: "/some/host/path2",
Destination: "/dest/path/b",
},
{
Source: "/some/host/path3",
Destination: "/dest/path/a",
Type: "bind",
Options: []string{"idmap"},
},
{
Source: "/some/host/path4",
Destination: "/dest/path/d",
Type: "bind",
Options: []string{"rbind", "ridmap"},
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
edits := ContainerEdits{tc.edits}
Expand Down
36 changes: 34 additions & 2 deletions pkg/cdi/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,46 @@ func (h *Hook) toOCI() spec.Hook {
}
}

// Additional OCI mount option to apply to injected mounts.
type ociMountOption func(*spec.Mount)

// withIDMapForBindMount adds any necessary ID mapping options for a bind mount.
func withIDMapForBindMount() ociMountOption {
return func(m *spec.Mount) {
option := ""
if m.Type == "bind" {
option = "idmap"
}

for _, o := range m.Options {
switch o {
case "idmap", "ridmap":
return
case "bind":
option = "idmap"
case "rbind":
option = "ridmap"
Comment on lines +49 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing here. The option that is applied depends on the ordering of the options. Should there be a difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. You shouldn't use both options, but neither runc nor crun will flag it an error if you do... if I read the code correctly. Also (again IIRTCC), they will behave differently. runc will interpret it as an rbind while crun will interpreting it according to whichever was specified last. So I think there is no obviously correct behavior for that corner case us here. If I can choose the less questionable one, I'd say I prefer a 'last option wins' approach.

}
}

if option != "" {
m.Options = append(m.Options, option)
}
}
}

// toOCI returns the opencontainers runtime Spec Mount for this Mount.
func (m *Mount) toOCI() spec.Mount {
return spec.Mount{
func (m *Mount) toOCI(options ...ociMountOption) spec.Mount {
om := spec.Mount{
Source: m.HostPath,
Destination: m.ContainerPath,
Options: m.Options,
Type: m.Type,
}
for _, o := range options {
o(&om)
}
return om
}

// toOCI returns the opencontainers runtime Spec LinuxDevice for this DeviceNode.
Expand Down