Skip to content

Commit 0bb0276

Browse files
committed
cdi: use [r]idmap for bind mounts if user NS is in use.
When injecting bind mounts to a container with user namespaces, add the idmap or ridmap option depending on whether we have a bind or rbind mount. This should cause the mount to happen with ID mapping set up for proper access. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
1 parent 16a1328 commit 0bb0276

File tree

3 files changed

+167
-3
lines changed

3 files changed

+167
-3
lines changed

pkg/cdi/container-edits.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,24 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
114114
}
115115

116116
if len(e.Mounts) > 0 {
117+
var (
118+
hasUserNamespace = specHasUserNamespace(spec)
119+
isBind bool
120+
bindKind string
121+
)
117122
for _, m := range e.Mounts {
123+
mnt := &Mount{m}
124+
if hasUserNamespace {
125+
isBind, bindKind = mnt.isBindMount()
126+
}
127+
118128
specgen.RemoveMount(m.ContainerPath)
119-
specgen.AddMount((&Mount{m}).toOCI())
129+
130+
if !hasUserNamespace || !isBind {
131+
specgen.AddMount(mnt.toOCI())
132+
} else {
133+
specgen.AddMount(mnt.toOCI(withMountIDMappingsFor(bindKind)))
134+
}
120135
}
121136
sortMounts(&specgen)
122137
}
@@ -322,6 +337,25 @@ func (m *Mount) Validate() error {
322337
return nil
323338
}
324339

340+
func (m *Mount) isBindMount() (bool, string) {
341+
kind := ""
342+
if m.Type == "bind" {
343+
kind = "bind"
344+
}
345+
for _, o := range m.Options {
346+
switch o {
347+
case "bind":
348+
if kind == "" {
349+
kind = "bind"
350+
}
351+
case "rbind":
352+
return true, "rbind"
353+
}
354+
}
355+
356+
return kind != "", kind
357+
}
358+
325359
// IntelRdt is a CDI IntelRdt wrapper.
326360
// This is used for validation and conversion to OCI specifications.
327361
type IntelRdt struct {
@@ -389,3 +423,16 @@ func (m orderedMounts) Swap(i, j int) {
389423
func (m orderedMounts) parts(i int) int {
390424
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
391425
}
426+
427+
// specHasUserNamespace returns true if the OCI Spec has a Linux UserNamespace.
428+
func specHasUserNamespace(spec *oci.Spec) bool {
429+
if spec == nil || spec.Linux == nil {
430+
return false
431+
}
432+
for _, ns := range spec.Linux.Namespaces {
433+
if ns.Type == oci.UserNamespace {
434+
return true
435+
}
436+
}
437+
return false
438+
}

pkg/cdi/container-edits_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,104 @@ func TestApplyContainerEdits(t *testing.T) {
701701
},
702702
},
703703
},
704+
{
705+
name: "mount added to container with Linux user namespace and uid/gid mappings",
706+
spec: &oci.Spec{
707+
Linux: &oci.Linux{
708+
UIDMappings: []oci.LinuxIDMapping{
709+
{
710+
ContainerID: 0,
711+
HostID: 1000,
712+
Size: 999,
713+
},
714+
},
715+
GIDMappings: []oci.LinuxIDMapping{
716+
{
717+
ContainerID: 0,
718+
HostID: 2000,
719+
Size: 777,
720+
},
721+
},
722+
Namespaces: []oci.LinuxNamespace{
723+
{
724+
Type: oci.UserNamespace,
725+
Path: "/foo/bar",
726+
},
727+
},
728+
},
729+
Mounts: []oci.Mount{
730+
{
731+
Source: "/some/host/path1",
732+
Destination: "/dest/path/c",
733+
},
734+
{
735+
Source: "/some/host/path2",
736+
Destination: "/dest/path/b",
737+
},
738+
},
739+
},
740+
edits: &cdi.ContainerEdits{
741+
Mounts: []*cdi.Mount{
742+
{
743+
HostPath: "/some/host/path3",
744+
ContainerPath: "/dest/path/a",
745+
Type: "bind",
746+
},
747+
{
748+
HostPath: "/some/host/path4",
749+
ContainerPath: "/dest/path/d",
750+
Type: "bind",
751+
Options: []string{"rbind"},
752+
},
753+
},
754+
},
755+
result: &oci.Spec{
756+
Linux: &oci.Linux{
757+
UIDMappings: []oci.LinuxIDMapping{
758+
{
759+
ContainerID: 0,
760+
HostID: 1000,
761+
Size: 999,
762+
},
763+
},
764+
GIDMappings: []oci.LinuxIDMapping{
765+
{
766+
ContainerID: 0,
767+
HostID: 2000,
768+
Size: 777,
769+
},
770+
},
771+
Namespaces: []oci.LinuxNamespace{
772+
{
773+
Type: oci.UserNamespace,
774+
Path: "/foo/bar",
775+
},
776+
},
777+
},
778+
Mounts: []oci.Mount{
779+
{
780+
Source: "/some/host/path1",
781+
Destination: "/dest/path/c",
782+
},
783+
{
784+
Source: "/some/host/path2",
785+
Destination: "/dest/path/b",
786+
},
787+
{
788+
Source: "/some/host/path3",
789+
Destination: "/dest/path/a",
790+
Type: "bind",
791+
Options: []string{"idmap"},
792+
},
793+
{
794+
Source: "/some/host/path4",
795+
Destination: "/dest/path/d",
796+
Type: "bind",
797+
Options: []string{"rbind", "ridmap"},
798+
},
799+
},
800+
},
801+
},
704802
} {
705803
t.Run(tc.name, func(t *testing.T) {
706804
edits := ContainerEdits{tc.edits}

pkg/cdi/oci.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,33 @@ func (h *Hook) toOCI() spec.Hook {
3030
}
3131
}
3232

33+
// Additional OCI mount option to apply to injected mounts.
34+
type ociMountOption func(*spec.Mount)
35+
36+
// withMountIDMappingsFor adds the necessary ID mapping options for a bind mount.
37+
func withMountIDMappingsFor(kind string) ociMountOption {
38+
return func(m *spec.Mount) {
39+
switch kind {
40+
case "rbind":
41+
m.Options = append(m.Options, "ridmap")
42+
case "bind":
43+
m.Options = append(m.Options, "idmap")
44+
}
45+
}
46+
}
47+
3348
// toOCI returns the opencontainers runtime Spec Mount for this Mount.
34-
func (m *Mount) toOCI() spec.Mount {
35-
return spec.Mount{
49+
func (m *Mount) toOCI(options ...ociMountOption) spec.Mount {
50+
om := spec.Mount{
3651
Source: m.HostPath,
3752
Destination: m.ContainerPath,
3853
Options: m.Options,
3954
Type: m.Type,
4055
}
56+
for _, o := range options {
57+
o(&om)
58+
}
59+
return om
4160
}
4261

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

0 commit comments

Comments
 (0)