2020-05-03 13:23:00 +02:00
|
|
|
package reverse_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
// findIno looks for the file having inode number `ino` in `dir`.
|
|
|
|
// Returns "" if not found.
|
|
|
|
func findIno(dir string, ino uint64) string {
|
|
|
|
fd, err := os.Open(dir)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
dirents, err := fd.Readdirnames(0)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
fd.Close()
|
|
|
|
for _, entry := range dirents {
|
|
|
|
var st syscall.Stat_t
|
|
|
|
err = syscall.Lstat(dir+"/"+entry, &st)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ino == st.Ino {
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestVirtualFileIno creates a directory tree like this:
|
|
|
|
//
|
2022-12-29 15:00:24 +01:00
|
|
|
// TestVirtualFileIno <---- parent
|
|
|
|
// └── xxxxxxx[...] <---- child
|
2020-05-03 13:23:00 +02:00
|
|
|
//
|
|
|
|
// Which looks like this encrypted:
|
|
|
|
//
|
2022-12-29 15:00:24 +01:00
|
|
|
// OLUKdPMg6l87EiKVlufgwIkQL8MD6JdUgOR3a8nEZ-w <---- parent
|
|
|
|
// ├── gocryptfs.diriv <---- diriv
|
|
|
|
// ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50 <---- child
|
|
|
|
// └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name <---- name
|
2020-05-03 13:23:00 +02:00
|
|
|
//
|
2020-08-15 17:31:25 +02:00
|
|
|
// It verifies that the inode numbers match what we expect.
|
2020-05-03 13:23:00 +02:00
|
|
|
func TestVirtualFileIno(t *testing.T) {
|
|
|
|
if plaintextnames {
|
|
|
|
t.Skip("plaintextnames mode does not have virtual files")
|
|
|
|
}
|
|
|
|
|
|
|
|
type inoTable struct {
|
|
|
|
parent uint64
|
|
|
|
diriv uint64
|
|
|
|
child uint64
|
|
|
|
name uint64
|
|
|
|
}
|
|
|
|
var origInos inoTable
|
|
|
|
var cipherInos inoTable
|
|
|
|
|
|
|
|
parent := dirA + "/TestVirtualFileIno"
|
|
|
|
name := string(bytes.Repeat([]byte("x"), 240))
|
|
|
|
err := os.MkdirAll(parent+"/"+name, 0700)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
var st syscall.Stat_t
|
|
|
|
err = syscall.Lstat(parent+"/"+name, &st)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
origInos.child = st.Ino
|
|
|
|
// get inode number of plain parent
|
|
|
|
err = syscall.Lstat(parent, &st)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
origInos.parent = st.Ino
|
|
|
|
// find it in encrypted `dirB`
|
|
|
|
fd, err := os.Open(dirB)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
dirents, err := fd.Readdirnames(0)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
fd.Close()
|
|
|
|
encryptedParent := findIno(dirB, origInos.parent)
|
|
|
|
if encryptedParent == "" {
|
|
|
|
t.Fatalf("could not find ino %d in %q", origInos.parent, dirB)
|
|
|
|
}
|
|
|
|
encryptedParent = dirB + "/" + encryptedParent
|
|
|
|
err = syscall.Stat(encryptedParent, &st)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
cipherInos.parent = st.Ino
|
|
|
|
fd, err = os.Open(encryptedParent)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
dirents, err = fd.Readdirnames(0)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
fd.Close()
|
|
|
|
for _, entry := range dirents {
|
|
|
|
var st2 syscall.Stat_t
|
|
|
|
err = syscall.Lstat(encryptedParent+"/"+entry, &st2)
|
|
|
|
if err != nil {
|
2020-08-15 17:31:25 +02:00
|
|
|
t.Errorf("stat %q: %v", entry, err)
|
2020-05-03 13:23:00 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if entry == "gocryptfs.diriv" {
|
|
|
|
cipherInos.diriv = st2.Ino
|
|
|
|
} else if strings.HasSuffix(entry, ".name") {
|
|
|
|
cipherInos.name = st2.Ino
|
|
|
|
} else {
|
|
|
|
cipherInos.child = st2.Ino
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if origInos.parent != cipherInos.parent {
|
|
|
|
t.Errorf("parent ino mismatch: %d != %d", origInos.parent, cipherInos.parent)
|
|
|
|
}
|
|
|
|
if origInos.parent == cipherInos.diriv {
|
|
|
|
t.Errorf("diriv ino collision: %d == %d", origInos.parent, cipherInos.diriv)
|
|
|
|
}
|
2020-05-03 15:22:10 +02:00
|
|
|
// Lower 48 bits should come from the backing file
|
|
|
|
const mask = 0xffffffffffff
|
2021-08-20 17:06:18 +02:00
|
|
|
if !deterministic_names { // no diriv files with -deterministic-names
|
|
|
|
if origInos.parent&mask != cipherInos.diriv&mask {
|
|
|
|
t.Errorf("diriv ino mismatch: %#x vs %#x", origInos.parent, cipherInos.diriv)
|
|
|
|
}
|
2020-05-03 13:23:00 +02:00
|
|
|
}
|
|
|
|
if origInos.child != cipherInos.child {
|
|
|
|
t.Errorf("child ino mismatch: %d vs %d", origInos.child, cipherInos.child)
|
|
|
|
}
|
|
|
|
if origInos.child == cipherInos.name {
|
|
|
|
t.Errorf("name ino collision: %d == %d", origInos.child, cipherInos.name)
|
|
|
|
}
|
2020-05-03 15:22:10 +02:00
|
|
|
if origInos.child&mask != cipherInos.name&mask {
|
|
|
|
t.Errorf("name ino mismatch: %#x vs %#x", origInos.child, cipherInos.name)
|
2020-05-03 13:23:00 +02:00
|
|
|
}
|
|
|
|
}
|