diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index 957011b4519..8de1a7f5acd 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -1161,6 +1161,16 @@ func parseMountOptions(options []string) *configs.Mount { if f.clear { recAttrClr |= f.flag recAttrSet &= ^f.flag + // The kernel's mount_setattr(2) requires that if any bit of the + // MOUNT_ATTR__ATIME mask is changed, the full mask must be present in + // attr_clr. Partial masks result in EINVAL. + // + // We intentionally use unix.MOUNT_ATTR__ATIME (which should be 0x70) + // instead of constructing it from flags, because strict adherence + // to the kernel's definition of the mask is required. + if f.flag&unix.MOUNT_ATTR__ATIME == f.flag { + recAttrClr |= unix.MOUNT_ATTR__ATIME + } } else { recAttrSet |= f.flag recAttrClr &= ^f.flag diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go index b96893c7f75..34a16c8877c 100644 --- a/libcontainer/specconv/spec_linux_test.go +++ b/libcontainer/specconv/spec_linux_test.go @@ -1017,3 +1017,37 @@ func TestCreateNetDevices(t *testing.T) { }) } } + +func TestParseMountOptionsRecursiveAtime(t *testing.T) { + // These options correspond to clears in recAttrFlags, triggering the 'clear' path. + // We expect recAttrClr to contain the full MOUNT_ATTR__ATIME mask. + const mountAttrAtime = unix.MOUNT_ATTR__ATIME + + testCases := []struct { + opt string + mask uint64 + }{ + {"ratime", mountAttrAtime}, + {"rnostrictatime", mountAttrAtime}, + {"rnorelatime", mountAttrAtime}, + } + + for _, tc := range testCases { + m := parseMountOptions([]string{tc.opt}) + if m.RecAttr == nil { + t.Errorf("option %s: expected RecAttr to be set", tc.opt) + continue + } + + // Check if the attr_clr has the full mask set + if m.RecAttr.Attr_clr&tc.mask != tc.mask { + t.Errorf("option %s: expected attr_clr (0x%x) to contain full ATIME mask (0x%x)", + tc.opt, m.RecAttr.Attr_clr, tc.mask) + } + + // Sanity check: Attr_set should be 0 for these clear-only options + if m.RecAttr.Attr_set != 0 { + t.Errorf("option %s: expected attr_set to be 0, got 0x%x", tc.opt, m.RecAttr.Attr_set) + } + } +}