Merge pull request #2001 from dmcgowan/fix-whiteout-rootpath

archive: check whiteout path before removal
This commit is contained in:
Michael Crosby 2018-01-12 15:09:11 -05:00 committed by GitHub
commit bbb5b2f15e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 177 additions and 4 deletions

View File

@ -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
} }

View File

@ -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 {
t.Fatalf("Failed to apply tar: %v", err) if applyErr == nil {
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 {