Check that operations that could potentially perform overlayfs mounts
that could cause undefined behaviors.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,111 @@ |
| 0 |
+package container |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "io" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types" |
|
| 9 |
+ "github.com/docker/docker/integration/internal/container" |
|
| 10 |
+ "github.com/docker/docker/pkg/archive" |
|
| 11 |
+ "github.com/docker/docker/pkg/dmesg" |
|
| 12 |
+ "gotest.tools/v3/assert" |
|
| 13 |
+ "gotest.tools/v3/skip" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) {
|
|
| 17 |
+ skip.If(t, testEnv.DaemonInfo.OSType != "linux", "overlayfs is only available on linux") |
|
| 18 |
+ skip.If(t, testEnv.IsRemoteDaemon(), "local daemon is needed for kernel log access") |
|
| 19 |
+ skip.If(t, testEnv.IsRootless(), "root is needed for reading kernel log") |
|
| 20 |
+ |
|
| 21 |
+ defer setupTest(t)() |
|
| 22 |
+ client := testEnv.APIClient() |
|
| 23 |
+ ctx := context.Background() |
|
| 24 |
+ |
|
| 25 |
+ cID := container.Run(ctx, t, client, container.WithCmd("sh", "-c", `while true; do echo $RANDOM >>/file; sleep 0.1; done`))
|
|
| 26 |
+ |
|
| 27 |
+ testCases := []struct {
|
|
| 28 |
+ name string |
|
| 29 |
+ operation func(t *testing.T) error |
|
| 30 |
+ }{
|
|
| 31 |
+ {name: "diff", operation: func(*testing.T) error {
|
|
| 32 |
+ _, err := client.ContainerDiff(ctx, cID) |
|
| 33 |
+ return err |
|
| 34 |
+ }}, |
|
| 35 |
+ {name: "export", operation: func(*testing.T) error {
|
|
| 36 |
+ rc, err := client.ContainerExport(ctx, cID) |
|
| 37 |
+ if err == nil {
|
|
| 38 |
+ defer rc.Close() |
|
| 39 |
+ _, err = io.Copy(io.Discard, rc) |
|
| 40 |
+ } |
|
| 41 |
+ return err |
|
| 42 |
+ }}, |
|
| 43 |
+ {name: "cp to container", operation: func(t *testing.T) error {
|
|
| 44 |
+ archive, err := archive.Generate("new-file", "hello-world")
|
|
| 45 |
+ assert.NilError(t, err, "failed to create a temporary archive") |
|
| 46 |
+ return client.CopyToContainer(ctx, cID, "/", archive, types.CopyToContainerOptions{})
|
|
| 47 |
+ }}, |
|
| 48 |
+ {name: "cp from container", operation: func(*testing.T) error {
|
|
| 49 |
+ rc, _, err := client.CopyFromContainer(ctx, cID, "/file") |
|
| 50 |
+ if err == nil {
|
|
| 51 |
+ defer rc.Close() |
|
| 52 |
+ _, err = io.Copy(io.Discard, rc) |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ return err |
|
| 56 |
+ }}, |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ for _, tc := range testCases {
|
|
| 60 |
+ tc := tc |
|
| 61 |
+ t.Run(tc.name, func(t *testing.T) {
|
|
| 62 |
+ prev := dmesgLines(256) |
|
| 63 |
+ |
|
| 64 |
+ err := tc.operation(t) |
|
| 65 |
+ assert.NilError(t, err) |
|
| 66 |
+ |
|
| 67 |
+ after := dmesgLines(2048) |
|
| 68 |
+ |
|
| 69 |
+ diff := diffDmesg(prev, after) |
|
| 70 |
+ for _, line := range diff {
|
|
| 71 |
+ overlayfs := strings.Contains(line, "overlayfs: ") |
|
| 72 |
+ lowerDirInUse := strings.Contains(line, "lowerdir is in-use as ") |
|
| 73 |
+ upperDirInUse := strings.Contains(line, "upperdir is in-use as ") |
|
| 74 |
+ workDirInuse := strings.Contains(line, "workdir is in-use as ") |
|
| 75 |
+ undefinedBehavior := strings.Contains(line, "will result in undefined behavior") |
|
| 76 |
+ |
|
| 77 |
+ if overlayfs && (lowerDirInUse || upperDirInUse || workDirInuse) && undefinedBehavior {
|
|
| 78 |
+ t.Errorf("%s caused overlayfs kernel warning: %s", tc.name, line)
|
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ }) |
|
| 82 |
+ } |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+func dmesgLines(bytes int) []string {
|
|
| 86 |
+ data := dmesg.Dmesg(bytes) |
|
| 87 |
+ return strings.Split(strings.TrimSpace(string(data)), "\n") |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func diffDmesg(prev, next []string) []string {
|
|
| 91 |
+ // All lines have a timestamp, so just take the last one from the previous |
|
| 92 |
+ // log and find it in the new log. |
|
| 93 |
+ lastPrev := prev[len(prev)-1] |
|
| 94 |
+ |
|
| 95 |
+ for idx := len(next) - 1; idx >= 0; idx-- {
|
|
| 96 |
+ line := next[idx] |
|
| 97 |
+ |
|
| 98 |
+ if line == lastPrev {
|
|
| 99 |
+ nextIdx := idx + 1 |
|
| 100 |
+ if nextIdx < len(next) {
|
|
| 101 |
+ return next[nextIdx:] |
|
| 102 |
+ } else {
|
|
| 103 |
+ // Found at the last position, log is the same. |
|
| 104 |
+ return nil |
|
| 105 |
+ } |
|
| 106 |
+ } |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ return next |
|
| 110 |
+} |