aboutsummaryrefslogtreecommitdiff
path: root/tests/reverse/inomap_test.go
blob: aadb782c7dceb06cb1216fe1e02b2615e34c4993 (plain)
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
146
147
148
149
package reverse_test

import (
	"bytes"
	"log"
	"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
		}
	}
	log.Panicf("ino %d not found", ino)
	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 !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)
		}
	}
	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)
	}
}