Skip to content

Commit 42d90f4

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 <[email protected]>
1 parent 98a7d73 commit 42d90f4

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
@@ -122,9 +122,24 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
122122
}
123123

124124
if len(e.Mounts) > 0 {
125+
var (
126+
hasUserNamespace = specHasUserNamespace(spec)
127+
isBind bool
128+
bindKind string
129+
)
125130
for _, m := range e.Mounts {
131+
mnt := &Mount{m}
132+
if hasUserNamespace {
133+
isBind, bindKind = mnt.isBindMount()
134+
}
135+
126136
specgen.RemoveMount(m.ContainerPath)
127-
specgen.AddMount((&Mount{m}).toOCI())
137+
138+
if !hasUserNamespace || !isBind {
139+
specgen.AddMount(mnt.toOCI())
140+
} else {
141+
specgen.AddMount(mnt.toOCI(withMountIDMappingsFor(bindKind)))
142+
}
128143
}
129144
sortMounts(&specgen)
130145
}
@@ -398,6 +413,25 @@ func (m *Mount) Validate() error {
398413
return nil
399414
}
400415

416+
func (m *Mount) isBindMount() (bool, string) {
417+
kind := ""
418+
if m.Type == "bind" {
419+
kind = "bind"
420+
}
421+
for _, o := range m.Options {
422+
switch o {
423+
case "bind":
424+
if kind == "" {
425+
kind = "bind"
426+
}
427+
case "rbind":
428+
return true, "rbind"
429+
}
430+
}
431+
432+
return kind != "", kind
433+
}
434+
401435
// IntelRdt is a CDI IntelRdt wrapper.
402436
// This is used for validation and conversion to OCI specifications.
403437
type IntelRdt struct {
@@ -465,3 +499,16 @@ func (m orderedMounts) Swap(i, j int) {
465499
func (m orderedMounts) parts(i int) int {
466500
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
467501
}
502+
503+
// specHasUserNamespace returns true if the OCI Spec has a Linux UserNamespace.
504+
func specHasUserNamespace(spec *oci.Spec) bool {
505+
if spec == nil || spec.Linux == nil {
506+
return false
507+
}
508+
for _, ns := range spec.Linux.Namespaces {
509+
if ns.Type == oci.UserNamespace {
510+
return true
511+
}
512+
}
513+
return false
514+
}

pkg/cdi/container-edits_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,104 @@ func TestApplyContainerEdits(t *testing.T) {
800800
},
801801
},
802802
},
803+
{
804+
name: "mount added to container with Linux user namespace and uid/gid mappings",
805+
spec: &oci.Spec{
806+
Linux: &oci.Linux{
807+
UIDMappings: []oci.LinuxIDMapping{
808+
{
809+
ContainerID: 0,
810+
HostID: 1000,
811+
Size: 999,
812+
},
813+
},
814+
GIDMappings: []oci.LinuxIDMapping{
815+
{
816+
ContainerID: 0,
817+
HostID: 2000,
818+
Size: 777,
819+
},
820+
},
821+
Namespaces: []oci.LinuxNamespace{
822+
{
823+
Type: oci.UserNamespace,
824+
Path: "/foo/bar",
825+
},
826+
},
827+
},
828+
Mounts: []oci.Mount{
829+
{
830+
Source: "/some/host/path1",
831+
Destination: "/dest/path/c",
832+
},
833+
{
834+
Source: "/some/host/path2",
835+
Destination: "/dest/path/b",
836+
},
837+
},
838+
},
839+
edits: &cdi.ContainerEdits{
840+
Mounts: []*cdi.Mount{
841+
{
842+
HostPath: "/some/host/path3",
843+
ContainerPath: "/dest/path/a",
844+
Type: "bind",
845+
},
846+
{
847+
HostPath: "/some/host/path4",
848+
ContainerPath: "/dest/path/d",
849+
Type: "bind",
850+
Options: []string{"rbind"},
851+
},
852+
},
853+
},
854+
result: &oci.Spec{
855+
Linux: &oci.Linux{
856+
UIDMappings: []oci.LinuxIDMapping{
857+
{
858+
ContainerID: 0,
859+
HostID: 1000,
860+
Size: 999,
861+
},
862+
},
863+
GIDMappings: []oci.LinuxIDMapping{
864+
{
865+
ContainerID: 0,
866+
HostID: 2000,
867+
Size: 777,
868+
},
869+
},
870+
Namespaces: []oci.LinuxNamespace{
871+
{
872+
Type: oci.UserNamespace,
873+
Path: "/foo/bar",
874+
},
875+
},
876+
},
877+
Mounts: []oci.Mount{
878+
{
879+
Source: "/some/host/path1",
880+
Destination: "/dest/path/c",
881+
},
882+
{
883+
Source: "/some/host/path2",
884+
Destination: "/dest/path/b",
885+
},
886+
{
887+
Source: "/some/host/path3",
888+
Destination: "/dest/path/a",
889+
Type: "bind",
890+
Options: []string{"idmap"},
891+
},
892+
{
893+
Source: "/some/host/path4",
894+
Destination: "/dest/path/d",
895+
Type: "bind",
896+
Options: []string{"rbind", "ridmap"},
897+
},
898+
},
899+
},
900+
},
803901
} {
804902
t.Run(tc.name, func(t *testing.T) {
805903
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)