Skip to content

Commit b60a77a

Browse files
authored
Merge pull request #4734 from cyphar/mount-errors
rootfs: improve mount-related errors
2 parents eeae96b + 58c3ab7 commit b60a77a

File tree

3 files changed

+127
-6
lines changed

3 files changed

+127
-6
lines changed

libcontainer/mount_linux.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io/fs"
77
"os"
88
"strconv"
9+
"strings"
910

1011
"github.com/sirupsen/logrus"
1112
"golang.org/x/sys/unix"
@@ -45,6 +46,64 @@ type mountError struct {
4546
err error
4647
}
4748

49+
// int32plus is a collection of int types with >=32 bits.
50+
type int32plus interface {
51+
int | uint | int32 | uint32 | int64 | uint64 | uintptr
52+
}
53+
54+
// stringifyMountFlags converts mount(2) flags to a string that you can use in
55+
// error messages.
56+
func stringifyMountFlags[Int int32plus](flags Int) string {
57+
flagNames := []struct {
58+
name string
59+
bits Int
60+
}{
61+
{"MS_RDONLY", unix.MS_RDONLY},
62+
{"MS_NOSUID", unix.MS_NOSUID},
63+
{"MS_NODEV", unix.MS_NODEV},
64+
{"MS_NOEXEC", unix.MS_NOEXEC},
65+
{"MS_SYNCHRONOUS", unix.MS_SYNCHRONOUS},
66+
{"MS_REMOUNT", unix.MS_REMOUNT},
67+
{"MS_MANDLOCK", unix.MS_MANDLOCK},
68+
{"MS_DIRSYNC", unix.MS_DIRSYNC},
69+
{"MS_NOSYMFOLLOW", unix.MS_NOSYMFOLLOW},
70+
// No (1 << 9) flag.
71+
{"MS_NOATIME", unix.MS_NOATIME},
72+
{"MS_NODIRATIME", unix.MS_NODIRATIME},
73+
{"MS_BIND", unix.MS_BIND},
74+
{"MS_MOVE", unix.MS_MOVE},
75+
{"MS_REC", unix.MS_REC},
76+
// MS_VERBOSE was deprecated and swapped to MS_SILENT.
77+
{"MS_SILENT", unix.MS_SILENT},
78+
{"MS_POSIXACL", unix.MS_POSIXACL},
79+
{"MS_UNBINDABLE", unix.MS_UNBINDABLE},
80+
{"MS_PRIVATE", unix.MS_PRIVATE},
81+
{"MS_SLAVE", unix.MS_SLAVE},
82+
{"MS_SHARED", unix.MS_SHARED},
83+
{"MS_RELATIME", unix.MS_RELATIME},
84+
// MS_KERNMOUNT (1 << 22) is internal to the kernel.
85+
{"MS_I_VERSION", unix.MS_I_VERSION},
86+
{"MS_STRICTATIME", unix.MS_STRICTATIME},
87+
{"MS_LAZYTIME", unix.MS_LAZYTIME},
88+
}
89+
var (
90+
flagSet []string
91+
seenBits Int
92+
)
93+
for _, flag := range flagNames {
94+
if flags&flag.bits == flag.bits {
95+
seenBits |= flag.bits
96+
flagSet = append(flagSet, flag.name)
97+
}
98+
}
99+
// If there were any remaining flags specified we don't know the name of,
100+
// just add them in an 0x... format.
101+
if remaining := flags &^ seenBits; remaining != 0 {
102+
flagSet = append(flagSet, "0x"+strconv.FormatUint(uint64(remaining), 16))
103+
}
104+
return strings.Join(flagSet, "|")
105+
}
106+
48107
// Error provides a string error representation.
49108
func (e *mountError) Error() string {
50109
out := e.op + " "
@@ -62,7 +121,7 @@ func (e *mountError) Error() string {
62121
}
63122

64123
if e.flags != uintptr(0) {
65-
out += ", flags=0x" + strconv.FormatUint(uint64(e.flags), 16)
124+
out += ", flags=" + stringifyMountFlags(e.flags)
66125
}
67126
if e.data != "" {
68127
out += ", data=" + e.data

libcontainer/mount_linux_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package libcontainer
2+
3+
import (
4+
"testing"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
func TestStringifyMountFlags(t *testing.T) {
10+
for _, test := range []struct {
11+
name string
12+
flags uintptr
13+
expected string
14+
}{
15+
{"Empty", 0, ""},
16+
// Single valid flags.
17+
{"Single-MS_RDONLY", unix.MS_RDONLY, "MS_RDONLY"},
18+
{"Single-MS_NOSUID", unix.MS_NOSUID, "MS_NOSUID"},
19+
{"Single-MS_NODEV", unix.MS_NODEV, "MS_NODEV"},
20+
{"Single-MS_NOEXEC", unix.MS_NOEXEC, "MS_NOEXEC"},
21+
{"Single-MS_SYNCHRONOUS", unix.MS_SYNCHRONOUS, "MS_SYNCHRONOUS"},
22+
{"Single-MS_REMOUNT", unix.MS_REMOUNT, "MS_REMOUNT"},
23+
{"Single-MS_MANDLOCK", unix.MS_MANDLOCK, "MS_MANDLOCK"},
24+
{"Single-MS_DIRSYNC", unix.MS_DIRSYNC, "MS_DIRSYNC"},
25+
{"Single-MS_NOSYMFOLLOW", unix.MS_NOSYMFOLLOW, "MS_NOSYMFOLLOW"},
26+
{"Single-MS_NOATIME", unix.MS_NOATIME, "MS_NOATIME"},
27+
{"Single-MS_NODIRATIME", unix.MS_NODIRATIME, "MS_NODIRATIME"},
28+
{"Single-MS_BIND", unix.MS_BIND, "MS_BIND"},
29+
{"Single-MS_MOVE", unix.MS_MOVE, "MS_MOVE"},
30+
{"Single-MS_REC", unix.MS_REC, "MS_REC"},
31+
{"Single-MS_SILENT", unix.MS_SILENT, "MS_SILENT"},
32+
{"Single-MS_POSIXACL", unix.MS_POSIXACL, "MS_POSIXACL"},
33+
{"Single-MS_UNBINDABLE", unix.MS_UNBINDABLE, "MS_UNBINDABLE"},
34+
{"Single-MS_PRIVATE", unix.MS_PRIVATE, "MS_PRIVATE"},
35+
{"Single-MS_SLAVE", unix.MS_SLAVE, "MS_SLAVE"},
36+
{"Single-MS_SHARED", unix.MS_SHARED, "MS_SHARED"},
37+
{"Single-MS_RELATIME", unix.MS_RELATIME, "MS_RELATIME"},
38+
{"Single-MS_KERNMOUNT", unix.MS_KERNMOUNT, "0x400000"},
39+
{"Single-MS_I_VERSION", unix.MS_I_VERSION, "MS_I_VERSION"},
40+
{"Single-MS_STRICTATIME", unix.MS_STRICTATIME, "MS_STRICTATIME"},
41+
{"Single-MS_LAZYTIME", unix.MS_LAZYTIME, "MS_LAZYTIME"},
42+
// Invalid flag value.
43+
{"Unknown-512", 1 << 9, "0x200"},
44+
// Multiple flag values at the same time.
45+
{"Multiple-Valid1", unix.MS_RDONLY | unix.MS_REC | unix.MS_BIND, "MS_RDONLY|MS_BIND|MS_REC"},
46+
{"Multiple-Valid2", unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC | unix.MS_REC | unix.MS_NODIRATIME | unix.MS_I_VERSION, "MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_NODIRATIME|MS_REC|MS_I_VERSION"},
47+
{"Multiple-Mixed", unix.MS_REC | unix.MS_BIND | (1 << 9) | (1 << 31), "MS_BIND|MS_REC|0x80000200"},
48+
} {
49+
got := stringifyMountFlags(test.flags)
50+
if got != test.expected {
51+
t.Errorf("%s: stringifyMountFlags(0x%x) = %q, expected %q", test.name, test.flags, got, test.expected)
52+
}
53+
}
54+
}

libcontainer/rootfs_linux.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -666,11 +666,17 @@ func mountToRootfs(c *mountConfig, m mountEntry) error {
666666
return err
667667
}
668668
srcFlags := statfsToMountFlags(*st)
669+
670+
logrus.Debugf(
671+
"working around failure to set vfs flags on bind-mount %s: srcFlags=%s flagsSet=%s flagsClr=%s: %v",
672+
m.Destination, stringifyMountFlags(srcFlags),
673+
stringifyMountFlags(m.Flags), stringifyMountFlags(m.ClearedFlags), mountErr)
674+
669675
// If the user explicitly request one of the locked flags *not*
670676
// be set, we need to return an error to avoid producing mounts
671677
// that don't match the user's request.
672-
if srcFlags&m.ClearedFlags&mntLockFlags != 0 {
673-
return mountErr
678+
if cannotClearFlags := srcFlags & m.ClearedFlags & mntLockFlags; cannotClearFlags != 0 {
679+
return fmt.Errorf("cannot clear locked flags %s: %w", stringifyMountFlags(cannotClearFlags), mountErr)
674680
}
675681

676682
// If an MS_*ATIME flag was requested, it must match the
@@ -691,17 +697,19 @@ func mountToRootfs(c *mountConfig, m mountEntry) error {
691697
// MS_STRICTATIME mounts even if the user requested MS_RELATIME
692698
// or MS_NOATIME.
693699
if m.Flags&mntAtimeFlags != 0 && m.Flags&mntAtimeFlags != srcFlags&mntAtimeFlags {
694-
return mountErr
700+
return fmt.Errorf("cannot change locked atime flags %s: %w", stringifyMountFlags(srcFlags&mntAtimeFlags), mountErr)
695701
}
696702

697703
// Retry the mount with the existing lockable mount flags
698704
// applied.
699705
flags |= srcFlags & mntLockFlags
700706
mountErr = mountViaFds("", nil, m.Destination, dstFd, "", uintptr(flags), "")
701-
logrus.Debugf("remount retry: srcFlags=0x%x flagsSet=0x%x flagsClr=0x%x: %v", srcFlags, m.Flags, m.ClearedFlags, mountErr)
707+
if mountErr != nil {
708+
mountErr = fmt.Errorf("remount with locked flags %s re-applied: %w", stringifyMountFlags(srcFlags&mntLockFlags), mountErr)
709+
}
702710
return mountErr
703711
}); err != nil {
704-
return err
712+
return fmt.Errorf("failed to set user-requested vfs flags on bind-mount: %w", err)
705713
}
706714
}
707715

0 commit comments

Comments
 (0)