From db72fcea41f01f24ac3edb1cbf86d6b0be60f137 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 10 Apr 2016 19:32:10 +0200 Subject: longnames: fix fsstress failure, use dirfd Using dirfd-relative operations allows safe lockless handling of the ".name" files. --- internal/nametransform/longnames.go | 78 ++++++++++++++++++-------------- internal/nametransform/longnames_test.go | 6 +-- internal/nametransform/names_diriv.go | 58 +++++++++++++++--------- 3 files changed, 83 insertions(+), 59 deletions(-) (limited to 'internal/nametransform') diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go index d048f95..0746cd6 100644 --- a/internal/nametransform/longnames.go +++ b/internal/nametransform/longnames.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/base64" "io/ioutil" + "os" "path/filepath" "strings" "syscall" @@ -11,11 +12,13 @@ import ( "github.com/rfjakob/gocryptfs/internal/toggledlog" ) -// Files with long names are stored in two files: -// gocryptfs.longname.[sha256] <--- File content -// gocryptfs.longname.[sha256].name <--- File name -const longNamePrefix = "gocryptfs.longname." -const longNameSuffix = ".name" +const ( + // Files with long names are stored in two files: + // gocryptfs.longname.[sha256] <--- File content, prefix = gocryptfs.longname. + // gocryptfs.longname.[sha256].name <--- File name, suffix = .name + LongNameSuffix = ".name" + longNamePrefix = "gocryptfs.longname." +) // HashLongName - take the hash of a long string "name" and return // "gocryptfs.longname.[sha256]" @@ -32,63 +35,68 @@ const ( LongNameNone = iota ) -// IsLongName - detect if cName is +// NameType - detect if cName is // gocryptfs.longname.[sha256] ........ LongNameContent (content of a long name file) // gocryptfs.longname.[sha256].name .... LongNameFilename (full file name of a long name file) // else ................................ LongNameNone (normal file) -func IsLongName(cName string) int { +func NameType(cName string) int { if !strings.HasPrefix(cName, longNamePrefix) { return LongNameNone } - if strings.HasSuffix(cName, longNameSuffix) { + if strings.HasSuffix(cName, LongNameSuffix) { return LongNameFilename } return LongNameContent } +// IsLongContent returns true if "cName" is the content store of a long name file. +func IsLongContent(cName string) bool { + return NameType(cName) == LongNameContent +} + // ReadLongName - read path.name func ReadLongName(path string) (string, error) { - content, err := ioutil.ReadFile(path + longNameSuffix) + content, err := ioutil.ReadFile(path + LongNameSuffix) if err != nil { toggledlog.Warn.Printf("ReadLongName: %v", err) } return string(content), err } -// DeleteLongName - if cPath ends in "gocryptfs.longname.[sha256]", -// delete the "gocryptfs.longname.[sha256].name" file -func DeleteLongName(cPath string) error { - if IsLongName(filepath.Base(cPath)) == LongNameContent { - err := syscall.Unlink(cPath + longNameSuffix) - if err != nil { - toggledlog.Warn.Printf("DeleteLongName: %v", err) - } - return err +// DeleteLongName deletes "hashName.name". +func DeleteLongName(dirfd *os.File, hashName string) error { + err := syscall.Unlinkat(int(dirfd.Fd()), hashName+LongNameSuffix) + if err != nil { + toggledlog.Warn.Printf("DeleteLongName: %v", err) } - return nil + return err } -// WriteLongName - if cPath ends in "gocryptfs.longname.[sha256]", write the -// "gocryptfs.longname.[sha256].name" file -func (n *NameTransform) WriteLongName(cPath string, plainPath string) (err error) { - cHashedName := filepath.Base(cPath) - if IsLongName(cHashedName) != LongNameContent { - // This is not a hashed file name, nothing to do - return nil - } - // Encrypt (but do not hash) the plaintext name - cDir := filepath.Dir(cPath) - dirIV, err := ReadDirIV(cDir) +// WriteLongName encrypts plainName and writes it into "hashName.name". +// For the convenience of the caller, plainName may also be a path and will be +// converted internally. +func (n *NameTransform) WriteLongName(dirfd *os.File, hashName string, plainName string) (err error) { + plainName = filepath.Base(plainName) + + // Encrypt the basename + dirIV, err := ReadDirIVAt(dirfd) if err != nil { - toggledlog.Warn.Printf("WriteLongName: %v", err) return err } - plainName := filepath.Base(plainPath) cName := n.EncryptName(plainName, dirIV) - // Write the encrypted name into gocryptfs.longname.[sha256].name - err = ioutil.WriteFile(filepath.Join(cDir, cHashedName+longNameSuffix), []byte(cName), 0600) + + // Write the encrypted name into hashName.name + fdRaw, err := syscall.Openat(int(dirfd.Fd()), hashName+LongNameSuffix, + syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, 0600) + if err != nil { + toggledlog.Warn.Printf("WriteLongName: Openat: %v", err) + return err + } + fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix) + defer fd.Close() + _, err = fd.Write([]byte(cName)) if err != nil { - toggledlog.Warn.Printf("WriteLongName: %v", err) + toggledlog.Warn.Printf("WriteLongName: Write: %v", err) } return err } diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go index 62073ec..8fa19fe 100644 --- a/internal/nametransform/longnames_test.go +++ b/internal/nametransform/longnames_test.go @@ -6,17 +6,17 @@ import ( func TestIsLongName(t *testing.T) { n := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name" - if IsLongName(n) != LongNameFilename { + if NameType(n) != LongNameFilename { t.Errorf("False negative") } n = "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" - if IsLongName(n) != LongNameContent { + if NameType(n) != LongNameContent { t.Errorf("False negative") } n = "LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=" - if IsLongName(n) != LongNameNone { + if NameType(n) != LongNameNone { t.Errorf("False positive") } } diff --git a/internal/nametransform/names_diriv.go b/internal/nametransform/names_diriv.go index 9336f5d..1beda3f 100644 --- a/internal/nametransform/names_diriv.go +++ b/internal/nametransform/names_diriv.go @@ -1,7 +1,7 @@ package nametransform import ( - "fmt" + "errors" "io/ioutil" "os" "path/filepath" @@ -21,25 +21,38 @@ const ( ) // ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) -// This function is exported because it allows for an efficient readdir implementation -func ReadDirIV(dir string) (iv []byte, readErr error) { - ivfile := filepath.Join(dir, DirIVFilename) - toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile) - iv, readErr = ioutil.ReadFile(ivfile) - if readErr != nil { - // The directory may have been concurrently deleted or moved. Failure to - // read the diriv is not an error in that case. - _, statErr := os.Stat(dir) - if os.IsNotExist(statErr) { - toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir) - } else { - // This should not happen - toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr) - } - return nil, readErr +// This function is exported because it allows for an efficient readdir implementation. +func ReadDirIV(dir string) (iv []byte, err error) { + dirfd, err := os.Open(dir) + if err != nil { + return nil, err + } + defer dirfd.Close() + + return ReadDirIVAt(dirfd) +} + +// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd". +// Using the dirfd makes it immune to concurrent renames of the directory. +func ReadDirIVAt(dirfd *os.File) (iv []byte, err error) { + fdRaw, err := syscall.Openat(int(dirfd.Fd()), DirIVFilename, syscall.O_RDONLY, 0) + if err != nil { + toggledlog.Warn.Printf("ReadDirIVAt: %v", err) + return nil, err + } + fd := os.NewFile(uintptr(fdRaw), DirIVFilename) + defer fd.Close() + + iv = make([]byte, dirIVLen+1) + n, err := fd.Read(iv) + if err != nil { + toggledlog.Warn.Printf("ReadDirIVAt: %v", err) + return nil, err } + iv = iv[0:n] if len(iv) != dirIVLen { - return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv)) + toggledlog.Warn.Printf("ReadDirIVAt: wanted %d bytes, got %d", dirIVLen, len(iv)) + return nil, errors.New("invalid iv length") } return iv, nil } @@ -50,12 +63,15 @@ func ReadDirIV(dir string) (iv []byte, readErr error) { func WriteDirIV(dir string) error { iv := cryptocore.RandBytes(dirIVLen) file := filepath.Join(dir, DirIVFilename) - // 0444 permissions: the file is not secret but should not be written to - return ioutil.WriteFile(file, iv, 0444) + err := ioutil.WriteFile(file, iv, 0400) + if err != nil { + toggledlog.Warn.Printf("WriteDirIV: %v", err) + } + return err } // EncryptPathDirIV - encrypt relative plaintext path using EME with DirIV. -// Components that are longer than 255 bytes are hashed. +// Components that are longer than 255 bytes are hashed if be.longnames == true. func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) { // Empty string means root directory if plainPath == "" { -- cgit v1.2.3