Skip to content

Commit 3b57e45

Browse files
committed
mount: add support for ridmap and idmap
ridmap indicates that the id mapping should be applied recursively (only really relevant for rbind mount entries), and idmap indicates that it should not be applied recursively (the default). If no mappings are specified for the mount, we use the userns configuration of the container. This matches the behaviour in the currently-unreleased runtime-spec. This includes a minor change to the state.json serialisation format, but because there has been no released version of runc with commit fbf183c ("Add uid and gid mappings to mounts"), we can safely make this change without affecting running containers. Doing it this way makes it much easier to handle m.IsIDMapped() and indicating that a mapping has been specified. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 7795ca4 commit 3b57e45

File tree

7 files changed

+588
-52
lines changed

7 files changed

+588
-52
lines changed

libcontainer/configs/mount.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ package configs
33
const (
44
// EXT_COPYUP is a directive to copy up the contents of a directory when
55
// a tmpfs is mounted over it.
6-
EXT_COPYUP = 1 << iota //nolint:golint // ignore "don't use ALL_CAPS" warning
6+
EXT_COPYUP = 1 << iota //nolint:golint,revive // ignore "don't use ALL_CAPS" warning
77
)

libcontainer/configs/mount_linux.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@ package configs
22

33
import "golang.org/x/sys/unix"
44

5+
type MountIDMapping struct {
6+
// Recursive indicates if the mapping needs to be recursive.
7+
Recursive bool `json:"recursive"`
8+
9+
// UserNSPath is a path to a user namespace that indicates the necessary
10+
// id-mappings for MOUNT_ATTR_IDMAP. If set to non-"", UIDMappings and
11+
// GIDMappings must be set to nil.
12+
UserNSPath string `json:"userns_path,omitempty"`
13+
14+
// UIDMappings is the uid mapping set for this mount, to be used with
15+
// MOUNT_ATTR_IDMAP.
16+
UIDMappings []IDMap `json:"uid_mappings,omitempty"`
17+
18+
// GIDMappings is the gid mapping set for this mount, to be used with
19+
// MOUNT_ATTR_IDMAP.
20+
GIDMappings []IDMap `json:"gid_mappings,omitempty"`
21+
}
22+
523
type Mount struct {
624
// Source path for the mount.
725
Source string `json:"source"`
@@ -34,23 +52,15 @@ type Mount struct {
3452
// Extensions are additional flags that are specific to runc.
3553
Extensions int `json:"extensions"`
3654

37-
// UIDMappings is used to changing file user owners w/o calling chown.
38-
// Note that, the underlying filesystem should support this feature to be
39-
// used.
40-
// Every mount point could have its own mapping.
41-
UIDMappings []IDMap `json:"uid_mappings,omitempty"`
42-
43-
// GIDMappings is used to changing file group owners w/o calling chown.
44-
// Note that, the underlying filesystem should support this feature to be
45-
// used.
46-
// Every mount point could have its own mapping.
47-
GIDMappings []IDMap `json:"gid_mappings,omitempty"`
55+
// Mapping is the MOUNT_ATTR_IDMAP configuration for the mount. If non-nil,
56+
// the mount is configured to use MOUNT_ATTR_IDMAP-style id mappings.
57+
IDMapping *MountIDMapping `json:"id_mapping,omitempty"`
4858
}
4959

5060
func (m *Mount) IsBind() bool {
5161
return m.Flags&unix.MS_BIND != 0
5262
}
5363

5464
func (m *Mount) IsIDMapped() bool {
55-
return len(m.UIDMappings) > 0 || len(m.GIDMappings) > 0
65+
return m.IDMapping != nil
5666
}

libcontainer/configs/validate/validator.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,13 @@ func checkBindOptions(m *configs.Mount) error {
309309
}
310310

311311
func checkIDMapMounts(config *configs.Config, m *configs.Mount) error {
312+
// Make sure MOUNT_ATTR_IDMAP is not set on any of our mounts. This
313+
// attribute is handled differently to all other attributes (through
314+
// m.IDMapping), so make sure we never store it in the actual config. This
315+
// really shouldn't ever happen.
316+
if m.RecAttr != nil && (m.RecAttr.Attr_set|m.RecAttr.Attr_clr)&unix.MOUNT_ATTR_IDMAP != 0 {
317+
return errors.New("mount configuration cannot contain recAttr for MOUNT_ATTR_IDMAP")
318+
}
312319
if !m.IsIDMapped() {
313320
return nil
314321
}
@@ -318,6 +325,16 @@ func checkIDMapMounts(config *configs.Config, m *configs.Mount) error {
318325
if config.RootlessEUID {
319326
return errors.New("id-mapped mounts are not supported for rootless containers")
320327
}
328+
if m.IDMapping.UserNSPath == "" {
329+
if len(m.IDMapping.UIDMappings) == 0 || len(m.IDMapping.GIDMappings) == 0 {
330+
return errors.New("id-mapped mounts must have both uid and gid mappings specified")
331+
}
332+
} else {
333+
if m.IDMapping.UIDMappings != nil || m.IDMapping.GIDMappings != nil {
334+
// should never happen
335+
return errors.New("[internal error] id-mapped mounts cannot have both userns_path and uid and gid mappings specified")
336+
}
337+
}
321338
return nil
322339
}
323340

libcontainer/configs/validate/validator_test.go

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -504,17 +504,82 @@ func TestValidateIDMapMounts(t *testing.T) {
504504
config *configs.Config
505505
}{
506506
{
507-
name: "idmap mount without bind opt specified",
507+
name: "idmap non-bind mount",
508508
isErr: true,
509509
config: &configs.Config{
510510
UIDMappings: mapping,
511511
GIDMappings: mapping,
512+
Mounts: []*configs.Mount{
513+
{
514+
Source: "/dev/sda1",
515+
Destination: "/abs/path/",
516+
Device: "ext4",
517+
IDMapping: &configs.MountIDMapping{
518+
UIDMappings: mapping,
519+
GIDMappings: mapping,
520+
},
521+
},
522+
},
523+
},
524+
},
525+
{
526+
name: "idmap option non-bind mount",
527+
isErr: true,
528+
config: &configs.Config{
529+
Mounts: []*configs.Mount{
530+
{
531+
Source: "/dev/sda1",
532+
Destination: "/abs/path/",
533+
Device: "ext4",
534+
IDMapping: &configs.MountIDMapping{},
535+
},
536+
},
537+
},
538+
},
539+
{
540+
name: "ridmap option non-bind mount",
541+
isErr: true,
542+
config: &configs.Config{
543+
Mounts: []*configs.Mount{
544+
{
545+
Source: "/dev/sda1",
546+
Destination: "/abs/path/",
547+
Device: "ext4",
548+
IDMapping: &configs.MountIDMapping{
549+
Recursive: true,
550+
},
551+
},
552+
},
553+
},
554+
},
555+
{
556+
name: "idmap mount no uid mapping",
557+
isErr: true,
558+
config: &configs.Config{
559+
Mounts: []*configs.Mount{
560+
{
561+
Source: "/abs/path/",
562+
Destination: "/abs/path/",
563+
Flags: unix.MS_BIND,
564+
IDMapping: &configs.MountIDMapping{
565+
GIDMappings: mapping,
566+
},
567+
},
568+
},
569+
},
570+
},
571+
{
572+
name: "idmap mount no gid mapping",
573+
isErr: true,
574+
config: &configs.Config{
512575
Mounts: []*configs.Mount{
513576
{
514577
Source: "/abs/path/",
515578
Destination: "/abs/path/",
516-
UIDMappings: mapping,
517-
GIDMappings: mapping,
579+
Flags: unix.MS_BIND,
580+
IDMapping: &configs.MountIDMapping{
581+
UIDMappings: mapping,
582+
},
518583
},
519584
},
520585
},
@@ -531,8 +596,10 @@ func TestValidateIDMapMounts(t *testing.T) {
531596
Source: "/abs/path/",
532597
Destination: "/abs/path/",
533598
Flags: unix.MS_BIND,
534-
UIDMappings: mapping,
535-
GIDMappings: mapping,
599+
IDMapping: &configs.MountIDMapping{
600+
UIDMappings: mapping,
601+
GIDMappings: mapping,
602+
},
536603
},
537604
},
538605
},
@@ -547,8 +614,10 @@ func TestValidateIDMapMounts(t *testing.T) {
547614
Source: "./rel/path/",
548615
Destination: "/abs/path/",
549616
Flags: unix.MS_BIND,
550-
UIDMappings: mapping,
551-
GIDMappings: mapping,
617+
IDMapping: &configs.MountIDMapping{
618+
UIDMappings: mapping,
619+
GIDMappings: mapping,
620+
},
552621
},
553622
},
554623
},
@@ -563,8 +632,10 @@ func TestValidateIDMapMounts(t *testing.T) {
563632
Source: "/abs/path/",
564633
Destination: "./rel/path/",
565634
Flags: unix.MS_BIND,
566-
UIDMappings: mapping,
567-
GIDMappings: mapping,
635+
IDMapping: &configs.MountIDMapping{
636+
UIDMappings: mapping,
637+
GIDMappings: mapping,
638+
},
568639
},
569640
},
570641
},
@@ -579,8 +650,10 @@ func TestValidateIDMapMounts(t *testing.T) {
579650
Source: "/another-abs/path/",
580651
Destination: "/abs/path/",
581652
Flags: unix.MS_BIND,
582-
UIDMappings: mapping,
583-
GIDMappings: mapping,
653+
IDMapping: &configs.MountIDMapping{
654+
UIDMappings: mapping,
655+
GIDMappings: mapping,
656+
},
584657
},
585658
},
586659
},
@@ -595,8 +668,10 @@ func TestValidateIDMapMounts(t *testing.T) {
595668
Source: "/another-abs/path/",
596669
Destination: "/abs/path/",
597670
Flags: unix.MS_BIND | unix.MS_RDONLY,
598-
UIDMappings: mapping,
599-
GIDMappings: mapping,
671+
IDMapping: &configs.MountIDMapping{
672+
UIDMappings: mapping,
673+
GIDMappings: mapping,
674+
},
600675
},
601676
},
602677
},
@@ -609,8 +684,10 @@ func TestValidateIDMapMounts(t *testing.T) {
609684
Source: "/abs/path/",
610685
Destination: "/abs/path/",
611686
Flags: unix.MS_BIND,
612-
UIDMappings: mapping,
613-
GIDMappings: mapping,
687+
IDMapping: &configs.MountIDMapping{
688+
UIDMappings: mapping,
689+
GIDMappings: mapping,
690+
},
614691
},
615692
},
616693
},
@@ -625,14 +702,16 @@ func TestValidateIDMapMounts(t *testing.T) {
625702
Source: "/abs/path/",
626703
Destination: "/abs/path/",
627704
Flags: unix.MS_BIND,
628-
UIDMappings: []configs.IDMap{
629-
{
630-
ContainerID: 10,
631-
HostID: 10,
632-
Size: 1,
705+
IDMapping: &configs.MountIDMapping{
706+
UIDMappings: []configs.IDMap{
707+
{
708+
ContainerID: 10,
709+
HostID: 10,
710+
Size: 1,
711+
},
633712
},
713+
GIDMappings: mapping,
634714
},
635-
GIDMappings: mapping,
636715
},
637716
},
638717
},
@@ -647,18 +726,50 @@ func TestValidateIDMapMounts(t *testing.T) {
647726
Source: "/abs/path/",
648727
Destination: "/abs/path/",
649728
Flags: unix.MS_BIND,
650-
UIDMappings: mapping,
651-
GIDMappings: []configs.IDMap{
652-
{
653-
ContainerID: 10,
654-
HostID: 10,
655-
Size: 1,
729+
IDMapping: &configs.MountIDMapping{
730+
UIDMappings: mapping,
731+
GIDMappings: []configs.IDMap{
732+
{
733+
ContainerID: 10,
734+
HostID: 10,
735+
Size: 1,
736+
},
656737
},
657738
},
658739
},
659740
},
660741
},
661742
},
743+
{
744+
name: "mount with 'idmap' option but no mappings",
745+
isErr: true,
746+
config: &configs.Config{
747+
Mounts: []*configs.Mount{
748+
{
749+
Source: "/abs/path/",
750+
Destination: "/abs/path/",
751+
Flags: unix.MS_BIND,
752+
IDMapping: &configs.MountIDMapping{},
753+
},
754+
},
755+
},
756+
},
757+
{
758+
name: "mount with 'ridmap' option but no mappings",
759+
isErr: true,
760+
config: &configs.Config{
761+
Mounts: []*configs.Mount{
762+
{
763+
Source: "/abs/path/",
764+
Destination: "/abs/path/",
765+
Flags: unix.MS_BIND,
766+
IDMapping: &configs.MountIDMapping{
767+
Recursive: true,
768+
},
769+
},
770+
},
771+
},
772+
},
662773
}
663774

664775
for _, tc := range testCases {

libcontainer/mount_linux.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -229,16 +229,32 @@ func mountFd(nsHandles *userns.Handles, m *configs.Mount) (*mountSource, error)
229229
sourceType = mountSourceOpenTree
230230

231231
// Configure the id mapping.
232-
usernsFile, err := nsHandles.Get(userns.Mapping{
233-
UIDMappings: m.UIDMappings,
234-
GIDMappings: m.GIDMappings,
235-
})
236-
if err != nil {
237-
return nil, fmt.Errorf("failed to create userns for %s id-mapping: %w", m.Source, err)
232+
var usernsFile *os.File
233+
if m.IDMapping.UserNSPath == "" {
234+
usernsFile, err = nsHandles.Get(userns.Mapping{
235+
UIDMappings: m.IDMapping.UIDMappings,
236+
GIDMappings: m.IDMapping.GIDMappings,
237+
})
238+
if err != nil {
239+
return nil, fmt.Errorf("failed to create userns for %s id-mapping: %w", m.Source, err)
240+
}
241+
} else {
242+
usernsFile, err = os.Open(m.IDMapping.UserNSPath)
243+
if err != nil {
244+
return nil, fmt.Errorf("failed to open existing userns for %s id-mapping: %w", m.Source, err)
245+
}
238246
}
239247
defer usernsFile.Close()
240248

241-
if err := unix.MountSetattr(int(mountFile.Fd()), "", unix.AT_EMPTY_PATH, &unix.MountAttr{
249+
setAttrFlags := uint(unix.AT_EMPTY_PATH)
250+
// If the mount has "ridmap" set, we apply the configuration
251+
// recursively. This allows you to create "rbind" mounts where only
252+
// the top-level mount has an idmapping. I'm not sure why you'd
253+
// want that, but still...
254+
if m.IDMapping.Recursive {
255+
setAttrFlags |= unix.AT_RECURSIVE
256+
}
257+
if err := unix.MountSetattr(int(mountFile.Fd()), "", setAttrFlags, &unix.MountAttr{
242258
Attr_set: unix.MOUNT_ATTR_IDMAP,
243259
Userns_fd: uint64(usernsFile.Fd()),
244260
}); err != nil {

0 commit comments

Comments
 (0)