diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index 5713460e486..6fb349f7d6a 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -1141,19 +1141,21 @@ func parseMountOptions(options []string) *configs.Mount { ) initMaps() for _, o := range options { - // If the option does not exist in the mountFlags table, - // or the flag is not supported on the platform, - // then it is a data value for a specific fs type. - if f, exists := mountFlags[o]; exists && f.flag != 0 { + // Options absent from the mountFlags table are data for a specific fs type. + // A recognized option is consumed even when its flag is zero: "defaults" + // is a no-op and must not be forwarded as filesystem data (for example to tmpfs). + if f, exists := mountFlags[o]; exists { // FIXME: The *atime flags are special (they are more of an enum // with quite hairy semantics) and thus arguably setting some of // them should clear unrelated flags. - if f.clear { - m.Flags &= ^f.flag - m.ClearedFlags |= f.flag - } else { - m.Flags |= f.flag - m.ClearedFlags &= ^f.flag + if f.flag != 0 { + if f.clear { + m.Flags &= ^f.flag + m.ClearedFlags |= f.flag + } else { + m.Flags |= f.flag + m.ClearedFlags &= ^f.flag + } } } else if f, exists := mountPropagationMapping[o]; exists && f != 0 { m.PropagationFlags = append(m.PropagationFlags, f) diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go index 4578f01158e..be8e8167848 100644 --- a/libcontainer/specconv/spec_linux_test.go +++ b/libcontainer/specconv/spec_linux_test.go @@ -1026,3 +1026,39 @@ func TestCreateNetDevices(t *testing.T) { }) } } + +func TestParseMountOptionsDefaultsNotInData(t *testing.T) { + m := parseMountOptions([]string{"defaults", "size=1m", "mode=777"}) + if strings.Contains(m.Data, "defaults") { + t.Errorf("defaults must not be passed as mount data, got %q", m.Data) + } + if !strings.Contains(m.Data, "size=1m") || !strings.Contains(m.Data, "mode=777") { + t.Errorf("expected size and mode in mount data, got %q", m.Data) + } + if m.Flags != 0 || m.ClearedFlags != 0 { + t.Errorf("defaults must not change mount flags, flags=%#x cleared=%#x", m.Flags, m.ClearedFlags) + } + + unknown := parseMountOptions([]string{"defaults", "notarealoption", "size=1m"}) + if strings.Contains(unknown.Data, "defaults") { + t.Errorf("defaults must not be passed as mount data, got %q", unknown.Data) + } + if !strings.Contains(unknown.Data, "notarealoption") { + t.Errorf("unknown option must be preserved in mount data, got %q", unknown.Data) + } + + // Nonzero flags keep set/clear behavior and are not treated as data. + flagged := parseMountOptions([]string{"ro", "nodev", "rw"}) + if flagged.Flags&unix.MS_RDONLY != 0 { + t.Errorf("rw should clear MS_RDONLY, flags=%#x cleared=%#x", flagged.Flags, flagged.ClearedFlags) + } + if flagged.ClearedFlags&unix.MS_RDONLY == 0 { + t.Errorf("rw should record MS_RDONLY as cleared, cleared=%#x", flagged.ClearedFlags) + } + if flagged.Flags&unix.MS_NODEV == 0 { + t.Errorf("nodev should set MS_NODEV, flags=%#x", flagged.Flags) + } + if flagged.Data != "" { + t.Errorf("known flags must not be passed as data, got %q", flagged.Data) + } +}