summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend_reverse/rfs.go21
-rw-r--r--internal/fusefrontend_reverse/rpath.go6
-rw-r--r--internal/nametransform/names.go4
-rw-r--r--tests/reverse/correctness_test.go21
4 files changed, 50 insertions, 2 deletions
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)
+ }
+}