libs/os: avoid CopyFile truncating destination before checking if regular file (backport: #6428) (#6436)

This commit is contained in:
Callum Waters
2021-05-10 13:24:33 +02:00
committed by GitHub
parent 353e3a3243
commit 8dd8a4e8ea
2 changed files with 50 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
package os
import (
"errors"
"fmt"
"io"
"io/ioutil"
@@ -84,17 +85,20 @@ func MustWriteFile(filePath string, contents []byte, mode os.FileMode) {
// CopyFile copies a file. It truncates the destination file if it exists.
func CopyFile(src, dst string) error {
info, err := os.Stat(src)
if err != nil {
return err
}
srcfile, err := os.Open(src)
if err != nil {
return err
}
defer srcfile.Close()
info, err := srcfile.Stat()
if err != nil {
return err
}
if info.IsDir() {
return errors.New("cannot read from directories")
}
// create new file, truncate if exists and apply same permissions as the original one
dstfile, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode().Perm())
if err != nil {

View File

@@ -71,3 +71,44 @@ func TestEnsureDir(t *testing.T) {
err = EnsureDir(filepath.Join(tmp, "linkfile"), 0755)
require.Error(t, err)
}
// Ensure that using CopyFile does not truncate the destination file before
// the origin is positively a non-directory and that it is ready for copying.
// See https://github.com/tendermint/tendermint/issues/6427
func TestTrickedTruncation(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "pwn_truncate")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpDir)
originalWALPath := filepath.Join(tmpDir, "wal")
originalWALContent := []byte("I AM BECOME DEATH, DESTROYER OF ALL WORLDS!")
if err := ioutil.WriteFile(originalWALPath, originalWALContent, 0755); err != nil {
t.Fatal(err)
}
// 1. Sanity check.
readWAL, err := ioutil.ReadFile(originalWALPath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(readWAL, originalWALContent) {
t.Fatalf("Cannot proceed as the content does not match\nGot: %q\nWant: %q", readWAL, originalWALContent)
}
// 2. Now cause the truncation of the original file.
// It is absolutely legal to invoke os.Open on a directory.
if err := CopyFile(tmpDir, originalWALPath); err == nil {
t.Fatal("Expected an error")
}
// 3. Check the WAL's content
reReadWAL, err := ioutil.ReadFile(originalWALPath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(reReadWAL, originalWALContent) {
t.Fatalf("Oops, the WAL's content was changed :(\nGot: %q\nWant: %q", reReadWAL, originalWALContent)
}
}