mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 14:21:14 +00:00
libs/os: avoid CopyFile truncating destination before checking if regular file (#6428)
This change fixes a potential exploitable vulnerability that can cause the WAL to be consistently truncated by falsely supplying the WAL path which would be any arbitrary dirrectory. Fixes #6427
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -50,17 +51,20 @@ func FileExists(filePath string) bool {
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -135,3 +135,44 @@ func newTestProgram(t *testing.T, environVar string) (cmd *exec.Cmd, stdout *byt
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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 := tmos.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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user