diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 6089d41..cfe23b6 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -1,6 +1,7 @@ package fusefrontend_reverse import ( + "encoding/base64" "fmt" "os" "path/filepath" @@ -275,3 +276,23 @@ func (rfs *reverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. func (rfs *reverseFS) StatFs(name string) *fuse.StatfsOut { return rfs.loopbackfs.StatFs(name) } + +// Readlink - FUSE call +func (rfs *reverseFS) Readlink(cipherPath string, context *fuse.Context) (string, fuse.Status) { + absPath, err := rfs.abs(rfs.decryptPath(cipherPath)) + if err != nil { + return "", fuse.ToStatus(err) + } + plainTarget, err := os.Readlink(absPath) + if err != nil { + return "", fuse.ToStatus(err) + } + if rfs.args.PlaintextNames { + return plainTarget, fuse.OK + } + nonce := derivePathIV(cipherPath) + // Symlinks are encrypted like file contents and base64-encoded + cBinTarget := rfs.contentEnc.EncryptBlock([]byte(plainTarget), 0, nil, contentenc.ExternalNonce, nonce) + cTarget := base64.URLEncoding.EncodeToString(cBinTarget) + return cTarget, fuse.OK +} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index c603cad..6d418e0 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -47,6 +47,12 @@ func (rfs *reverseFS) decryptPath(relPath string) (string, error) { if _, ok := err.(base64.CorruptInputError); ok { return "", syscall.ENOENT } + // Stat attempts on the link target of encrypted symlinks. + // These are always valid base64 but the length is not a + // multiple of 16. + if err == syscall.EINVAL { + return "", syscall.ENOENT + } return "", err } } else if nameType == nametransform.LongNameContent { diff --git a/internal/nametransform/names.go b/internal/nametransform/names.go index e9fe87d..4df3430 100644 --- a/internal/nametransform/names.go +++ b/internal/nametransform/names.go @@ -4,7 +4,6 @@ package nametransform import ( "crypto/aes" "encoding/base64" - "fmt" "syscall" "github.com/rfjakob/eme" @@ -38,7 +37,8 @@ func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error return "", err } if len(bin)%aes.BlockSize != 0 { - return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin)) + tlog.Warn.Printf("DecryptName %q: decoded length %d is not a multiple of 16", cipherName, len(bin)) + return "", syscall.EINVAL } bin = eme.Transform(n.cryptoCore.BlockCipher, iv, bin, eme.DirectionDecrypt) bin, err = unPad16(bin) diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go index 77a440b..40bd320 100644 --- a/tests/reverse/correctness_test.go +++ b/tests/reverse/correctness_test.go @@ -28,3 +28,24 @@ func TestLongnameStat(t *testing.T) { test_helpers.VerifySize(t, path, 10) */ } + +func TestSymlinks(t *testing.T) { + target := "/" + os.Symlink(target, dirA+"/symlink") + cSymlink := dirC + "/symlink" + _, err := os.Lstat(cSymlink) + if err != nil { + t.Errorf("Lstat: %v", err) + } + _, err = os.Stat(cSymlink) + if err != nil { + t.Errorf("Stat: %v", err) + } + actualTarget, err := os.Readlink(cSymlink) + if err != nil { + t.Fatal(err) + } + if target != actualTarget { + t.Errorf("wrong symlink target: want=%q have=%q", target, actualTarget) + } +}