diff options
| -rw-r--r-- | internal/fusefrontend/node.go | 32 | ||||
| -rw-r--r-- | internal/fusefrontend/node_helpers.go | 34 | ||||
| -rw-r--r-- | tests/defaults/main_test.go | 76 | 
3 files changed, 120 insertions, 22 deletions
| diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index 9d49add..7505f92 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -69,7 +69,13 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch  	if err != nil {  		return nil, fs.ToErrno(err)  	} + +	// Create new inode and fill `out`  	ch = n.newChild(ctx, st, out) + +	// Translate ciphertext size in `out.Attr.Size` to plaintext size +	n.translateSize(dirfd, cName, &out.Attr) +  	return ch, 0  } @@ -98,13 +104,9 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)  	rn.inoMap.TranslateStat(st)  	out.Attr.FromStat(st) -	// Fix size -	if out.IsRegular() { -		out.Size = rn.contentEnc.CipherSizeToPlainSize(out.Size) -	} else if out.IsSymlink() { -		target, _ := n.Readlink(ctx) -		out.Size = uint64(len(target)) -	} +	// Translate ciphertext size in `out.Attr.Size` to plaintext size +	n.translateSize(dirfd, cName, &out.Attr) +  	if rn.args.ForceOwner != nil {  		out.Owner = *rn.args.ForceOwner  	} @@ -221,21 +223,7 @@ func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {  	}  	defer syscall.Close(dirfd) -	cTarget, err := syscallcompat.Readlinkat(dirfd, cName) -	if err != nil { -		return nil, fs.ToErrno(err) -	} -	rn := n.rootNode() -	if rn.args.PlaintextNames { -		return []byte(cTarget), 0 -	} -	// Symlinks are encrypted like file contents (GCM) and base64-encoded -	target, err := rn.decryptSymlinkTarget(cTarget) -	if err != nil { -		tlog.Warn.Printf("Readlink %q: decrypting target failed: %v", cName, err) -		return nil, syscall.EIO -	} -	return []byte(target), 0 +	return n.readlink(dirfd, cName)  }  // Open - FUSE call. Open already-existing file. diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go index a7a32af..a31a41d 100644 --- a/internal/fusefrontend/node_helpers.go +++ b/internal/fusefrontend/node_helpers.go @@ -2,10 +2,14 @@ package fusefrontend  import (  	"context" +	"syscall"  	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/hanwen/go-fuse/v2/fuse" + +	"github.com/rfjakob/gocryptfs/internal/syscallcompat" +	"github.com/rfjakob/gocryptfs/internal/tlog"  )  // toFuseCtx tries to extract a fuse.Context from a generic context.Context. @@ -29,3 +33,33 @@ func toNode(op fs.InodeEmbedder) *Node {  	}  	return op.(*Node)  } + +// readlink reads and decrypts a symlink. Used by Readlink, Getattr, Lookup. +func (n *Node) readlink(dirfd int, cName string) (out []byte, errno syscall.Errno) { +	cTarget, err := syscallcompat.Readlinkat(dirfd, cName) +	if err != nil { +		return nil, fs.ToErrno(err) +	} +	rn := n.rootNode() +	if rn.args.PlaintextNames { +		return []byte(cTarget), 0 +	} +	// Symlinks are encrypted like file contents (GCM) and base64-encoded +	target, err := rn.decryptSymlinkTarget(cTarget) +	if err != nil { +		tlog.Warn.Printf("Readlink %q: decrypting target failed: %v", cName, err) +		return nil, syscall.EIO +	} +	return []byte(target), 0 +} + +// translateSize translates the ciphertext size in `out` into plaintext size. +func (n *Node) translateSize(dirfd int, cName string, out *fuse.Attr) { +	if out.IsRegular() { +		rn := n.rootNode() +		out.Size = rn.contentEnc.CipherSizeToPlainSize(out.Size) +	} else if out.IsSymlink() { +		target, _ := n.readlink(dirfd, cName) +		out.Size = uint64(len(target)) +	} +} diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go index f59ea38..1982c90 100644 --- a/tests/defaults/main_test.go +++ b/tests/defaults/main_test.go @@ -293,3 +293,79 @@ func TestSeekData(t *testing.T) {  	}  	f.Close()  } + +/* +TestMd5sumMaintainers tries to repro this interesting +bug that was seen during gocryptfs v2.0 development: + +$ md5sum linux-3.0/MAINTAINERS linux-3.0/MAINTAINERS linux-3.0/MAINTAINERS linux-3.0/MAINTAINERS +279b6ab0491e7532132e8f32afe6c04d  linux-3.0/MAINTAINERS <-- WRONG!!!! +99cc9f0dfd86e63231b94edd43a43e02  linux-3.0/MAINTAINERS <-- correct +99cc9f0dfd86e63231b94edd43a43e02  linux-3.0/MAINTAINERS +99cc9f0dfd86e63231b94edd43a43e02  linux-3.0/MAINTAINERS + +strace shows: + +Bad +--- +fstat(3, {st_mode=S_IFREG|0644, st_size=196745, ...}) = 0 +read(3, "\n\tList of maintainers and how to"..., 32768) = 32768 +read(3, "M:\tSylwester Nawrocki <s.nawrock"..., 32768) = 32768 +read(3, "rs/scsi/eata*\n\nEATA ISA/EISA/PCI"..., 32768) = 32768 +read(3, "F:\tDocumentation/isapnp.txt\nF:\td"..., 32768) = 32768 +read(3, "hunkeey@googlemail.com>\nL:\tlinux"..., 32768) = 32768 +read(3, "ach-spear3xx/\n\nSPEAR6XX MACHINE "..., 32768) = 32768 +read(3, "", 32768)                      = 0 +lseek(3, 0, SEEK_CUR)                   = 196608 +close(3)                                = 0 +fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0 +write(1, "279b6ab0491e7532132e8f32afe6c04d"..., 56279b6ab0491e7532132e8f32afe6c04d  linux-3.0/MAINTAINERS + +Good +---- +fstat(3, {st_mode=S_IFREG|0644, st_size=195191, ...}) = 0 +read(3, "\n\tList of maintainers and how to"..., 32768) = 32768 +read(3, "M:\tSylwester Nawrocki <s.nawrock"..., 32768) = 32768 +read(3, "rs/scsi/eata*\n\nEATA ISA/EISA/PCI"..., 32768) = 32768 +read(3, "F:\tDocumentation/isapnp.txt\nF:\td"..., 32768) = 32768 +read(3, "hunkeey@googlemail.com>\nL:\tlinux"..., 32768) = 32768 +read(3, "ach-spear3xx/\n\nSPEAR6XX MACHINE "..., 32768) = 31351 +read(3, "", 4096)                       = 0 +lseek(3, 0, SEEK_CUR)                   = 195191 +close(3)                                = 0 +fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0 +write(1, "99cc9f0dfd86e63231b94edd43a43e02"..., 5699cc9f0dfd86e63231b94edd43a43e02  linux-3.0/MAINTAINERS +*/ +func TestMd5sumMaintainers(t *testing.T) { +	fn := filepath.Join(test_helpers.DefaultPlainDir, t.Name()) +	f, err := os.Create(fn) +	if err != nil { +		t.Fatal(err) +	} +	// Size of the MAINTAINERS file = 195191 +	const sizeWant = 195191 +	content := make([]byte, sizeWant) +	_, err = f.Write(content) +	if err != nil { +		t.Fatal(err) +	} +	f.Close() + +	// Remount to clear the linux kernel attr cache +	// (otherwise we would have to wait 2 seconds for the entry to expire) +	test_helpers.UnmountPanic(test_helpers.DefaultPlainDir) +	test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey") + +	cmd := exec.Command("md5sum", fn, fn, fn, fn) +	out2, err := cmd.CombinedOutput() +	out := string(out2) + +	// 195191 zero bytes have this md5sum +	const md5Want = "b99bf6917f688068acd49126f3b1b005" + +	n := strings.Count(out, md5Want) +	if n != 4 { +		t.Errorf("found %d instead of %d instances of %q", n, 4, md5Want) +		t.Logf("full output:\n%s", out) +	} +} | 
