summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend/node.go32
-rw-r--r--internal/fusefrontend/node_helpers.go34
-rw-r--r--tests/defaults/main_test.go76
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)
+ }
+}