1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
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:
//
// TestVirtualFileIno <---- parent
// └── xxxxxxx[...] <---- child
//
// Which looks like this encrypted:
//
// OLUKdPMg6l87EiKVlufgwIkQL8MD6JdUgOR3a8nEZ-w <---- parent
// ├── gocryptfs.diriv <---- diriv
// ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50 <---- child
// └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name <---- name
//
// It verifies that the inode numbers match what we expect.
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 {
t.Errorf("stat %q: %v", entry, err)
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)
}
// Lower 48 bits should come from the backing file
const mask = 0xffffffffffff
if origInos.parent&mask != cipherInos.diriv&mask {
t.Errorf("diriv ino mismatch: %#x vs %#x", origInos.parent, cipherInos.diriv)
}
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)
}
if origInos.child&mask != cipherInos.name&mask {
t.Errorf("name ino mismatch: %#x vs %#x", origInos.child, cipherInos.name)
}
}
|