Merge pull request #2001 from dmcgowan/fix-whiteout-rootpath
archive: check whiteout path before removal
This commit is contained in:
		| @@ -26,6 +26,8 @@ var bufferPool = &sync.Pool{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var errInvalidArchive = errors.New("invalid archive") | ||||||
|  |  | ||||||
| // Diff returns a tar stream of the computed filesystem | // Diff returns a tar stream of the computed filesystem | ||||||
| // difference between the provided directories. | // difference between the provided directories. | ||||||
| // | // | ||||||
| @@ -231,6 +233,15 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO | |||||||
|  |  | ||||||
| 			originalBase := base[len(whiteoutPrefix):] | 			originalBase := base[len(whiteoutPrefix):] | ||||||
| 			originalPath := filepath.Join(dir, originalBase) | 			originalPath := filepath.Join(dir, originalBase) | ||||||
|  |  | ||||||
|  | 			// Ensure originalPath is under dir | ||||||
|  | 			if dir[len(dir)-1] != filepath.Separator { | ||||||
|  | 				dir += string(filepath.Separator) | ||||||
|  | 			} | ||||||
|  | 			if !strings.HasPrefix(originalPath, dir) { | ||||||
|  | 				return 0, errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			if err := os.RemoveAll(originalPath); err != nil { | 			if err := os.RemoveAll(originalPath); err != nil { | ||||||
| 				return 0, err | 				return 0, err | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -217,12 +217,47 @@ func TestBreakouts(t *testing.T) { | |||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	fileValue := func(f1 string, content []byte) func(string) error { | ||||||
|  | 		return func(root string) error { | ||||||
|  | 			b, err := ioutil.ReadFile(filepath.Join(root, f1)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if bytes.Compare(b, content) != 0 { | ||||||
|  | 				return errors.Errorf("content differs: expected %v, got %v", content, b) | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	fileNotExists := func(f1 string) func(string) error { | ||||||
|  | 		return func(root string) error { | ||||||
|  | 			_, err := os.Lstat(filepath.Join(root, f1)) | ||||||
|  | 			if err == nil { | ||||||
|  | 				return errors.New("file exists") | ||||||
|  | 			} else if !os.IsNotExist(err) { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	all := func(funcs ...func(string) error) func(string) error { | ||||||
|  | 		return func(root string) error { | ||||||
|  | 			for _, f := range funcs { | ||||||
|  | 				if err := f(root); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	breakouts := []struct { | 	breakouts := []struct { | ||||||
| 		name      string | 		name      string | ||||||
| 		w         WriterToTar | 		w         WriterToTar | ||||||
| 		apply     fstest.Applier | 		apply     fstest.Applier | ||||||
| 		validator func(string) error | 		validator func(string) error | ||||||
|  | 		err       error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "SymlinkAbsolute", | 			name: "SymlinkAbsolute", | ||||||
| @@ -468,10 +503,129 @@ func TestBreakouts(t *testing.T) { | |||||||
| 			), | 			), | ||||||
| 			validator: notSameFile("/localetc/localpasswd", "/etc/passwd"), | 			validator: notSameFile("/localetc/localpasswd", "/etc/passwd"), | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutRootParent", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File(".wh...", []byte{}, 0644), // Should wipe out whole directory | ||||||
|  | 			), | ||||||
|  | 			err: errInvalidArchive, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutParent", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File("etc/.wh...", []byte{}, 0644), | ||||||
|  | 			), | ||||||
|  | 			err: errInvalidArchive, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutRoot", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File(".wh..", []byte{}, 0644), | ||||||
|  | 			), | ||||||
|  | 			err: errInvalidArchive, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutCurrentDirectory", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("inside"), 0644), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File("etc/.wh..", []byte{}, 0644), // Should wipe out whole directory | ||||||
|  | 			), | ||||||
|  | 			err: errInvalidArchive, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutSymlink", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), | ||||||
|  | 				fstest.Symlink("/etc", "localetc"), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File(".wh.localetc", []byte{}, 0644), // Should wipe out whole directory | ||||||
|  | 			), | ||||||
|  | 			validator: all( | ||||||
|  | 				fileValue("etc/passwd", []byte("all users")), | ||||||
|  | 				fileNotExists("localetc"), | ||||||
|  | 			), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			// TODO: This test should change once archive apply is disallowing | ||||||
|  | 			// symlinks as parents in the name | ||||||
|  | 			name: "WhiteoutSymlinkPath", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), | ||||||
|  | 				fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644), | ||||||
|  | 				fstest.Symlink("/etc", "localetc"), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File("localetc/.wh.whitedout", []byte{}, 0644), | ||||||
|  | 			), | ||||||
|  | 			validator: all( | ||||||
|  | 				fileValue("etc/passwd", []byte("all users")), | ||||||
|  | 				fileNotExists("etc/whitedout"), | ||||||
|  | 			), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutDirectoryName", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), | ||||||
|  | 				fstest.CreateFile("/etc/whitedout", []byte("ahhhh whiteout"), 0644), | ||||||
|  | 				fstest.Symlink("/etc", "localetc"), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File(".wh.etc/somefile", []byte("non-empty"), 0644), | ||||||
|  | 			), | ||||||
|  | 			validator: all( | ||||||
|  | 				fileValue("etc/passwd", []byte("all users")), | ||||||
|  | 				fileValue(".wh.etc/somefile", []byte("non-empty")), | ||||||
|  | 			), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutDeadSymlinkParent", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), | ||||||
|  | 				fstest.Symlink("/dne", "localetc"), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File("localetc/.wh.etc", []byte{}, 0644), | ||||||
|  | 			), | ||||||
|  | 			// no-op, remove does not | ||||||
|  | 			validator: fileValue("etc/passwd", []byte("all users")), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "WhiteoutRelativePath", | ||||||
|  | 			apply: fstest.Apply( | ||||||
|  | 				fstest.CreateDir("/etc/", 0755), | ||||||
|  | 				fstest.CreateFile("/etc/passwd", []byte("all users"), 0644), | ||||||
|  | 				fstest.Symlink("/dne", "localetc"), | ||||||
|  | 			), | ||||||
|  | 			w: TarAll( | ||||||
|  | 				tc.File("dne/../.wh.etc", []byte{}, 0644), | ||||||
|  | 			), | ||||||
|  | 			// resolution ends up just removing etc | ||||||
|  | 			validator: fileNotExists("etc/passwd"), | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, bo := range breakouts { | 	for _, bo := range breakouts { | ||||||
| 		t.Run(bo.name, makeWriterToTarTest(bo.w, bo.apply, bo.validator)) | 		t.Run(bo.name, makeWriterToTarTest(bo.w, bo.apply, bo.validator, bo.err)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -501,6 +655,7 @@ func TestApplyTar(t *testing.T) { | |||||||
| 		w         WriterToTar | 		w         WriterToTar | ||||||
| 		apply     fstest.Applier | 		apply     fstest.Applier | ||||||
| 		validator func(string) error | 		validator func(string) error | ||||||
|  | 		err       error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "DirectoryCreation", | 			name: "DirectoryCreation", | ||||||
| @@ -525,7 +680,7 @@ func TestApplyTar(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, at := range tests { | 	for _, at := range tests { | ||||||
| 		t.Run(at.name, makeWriterToTarTest(at.w, at.apply, at.validator)) | 		t.Run(at.name, makeWriterToTarTest(at.w, at.apply, at.validator, at.err)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -636,7 +791,7 @@ func testDiffApply(appliers ...fstest.Applier) error { | |||||||
| 	return fstest.CheckDirectoryEqual(td, dest) | 	return fstest.CheckDirectoryEqual(td, dest) | ||||||
| } | } | ||||||
|  |  | ||||||
| func makeWriterToTarTest(wt WriterToTar, a fstest.Applier, validate func(string) error) func(*testing.T) { | func makeWriterToTarTest(wt WriterToTar, a fstest.Applier, validate func(string) error, applyErr error) func(*testing.T) { | ||||||
| 	return func(t *testing.T) { | 	return func(t *testing.T) { | ||||||
| 		td, err := ioutil.TempDir("", "test-writer-to-tar-") | 		td, err := ioutil.TempDir("", "test-writer-to-tar-") | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -653,7 +808,14 @@ func makeWriterToTarTest(wt WriterToTar, a fstest.Applier, validate func(string) | |||||||
| 		tr := TarFromWriterTo(wt) | 		tr := TarFromWriterTo(wt) | ||||||
|  |  | ||||||
| 		if _, err := Apply(context.Background(), td, tr); err != nil { | 		if _, err := Apply(context.Background(), td, tr); err != nil { | ||||||
|  | 			if applyErr == nil { | ||||||
| 				t.Fatalf("Failed to apply tar: %v", err) | 				t.Fatalf("Failed to apply tar: %v", err) | ||||||
|  | 			} else if errors.Cause(err) != applyErr { | ||||||
|  | 				t.Fatalf("Unexpected apply error: %v, expected %v", err, applyErr) | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} else if applyErr != nil { | ||||||
|  | 			t.Fatalf("Expected apply error, got none: %v", applyErr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if validate != nil { | 		if validate != nil { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Crosby
					Michael Crosby