v2api: fix missing size translation in Lookup

This commit is contained in:
Jakob Unterwurzacher 2020-07-26 19:49:26 +02:00
parent 777b95f82f
commit 4572cd2103
3 changed files with 120 additions and 22 deletions

View File

@ -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.

View File

@ -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))
}
}

View File

@ -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)
}
}