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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
|
package reverse_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/v2/ctlsock"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
// TestLongnameStat checks that file names of all sizes (1 to 255) show up in
// the decrypted reverse view (dirC, mounted in TestMain).
func TestLongnameStat(t *testing.T) {
for i := 1; i <= 255; i++ {
name := string(bytes.Repeat([]byte("x"), i))
fd, err := os.Create(dirA + "/" + name)
if err != nil {
t.Fatal(err)
}
fd.Close()
path := dirC + "/" + name
if !test_helpers.VerifyExistence(t, path) {
t.Fatalf("failed to verify %q", path)
}
test_helpers.VerifySize(t, path, 0)
// A large number of longname files is a performance problem in
// reverse mode. Move the file out of the way once we are done with it
// to speed up the test (2 seconds -> 0.2 seconds).
// We do NOT unlink it because ext4 reuses inode numbers immediately,
// which will cause "Found linked inode, but Nlink == 1" warnings and
// file not found errors.
// TODO: This problem should be handled at the go-fuse level.
syscall.Rename(dirA+"/"+name, test_helpers.TmpDir+"/"+fmt.Sprintf("x%d", i))
}
}
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)
}
}
// Symbolic link dentry sizes should be set to the length of the string
// that contains the target path.
func TestSymlinkDentrySize(t *testing.T) {
if plaintextnames {
t.Skip("this only tests encrypted names")
}
symlink := "a_symlink"
mnt, err := ioutil.TempDir(test_helpers.TmpDir, "reverse_mnt_")
if err != nil {
t.Fatal(err)
}
sock := mnt + ".sock"
test_helpers.MountOrFatal(t, "ctlsock_reverse_test_fs", mnt, "-reverse", "-extpass", "echo test", "-ctlsock="+sock)
defer test_helpers.UnmountPanic(mnt)
req := ctlsock.RequestStruct{EncryptPath: symlink}
symlinkResponse := test_helpers.QueryCtlSock(t, sock, req)
if symlinkResponse.ErrNo != 0 {
t.Errorf("Encrypt: %q ErrNo=%d ErrText=%s", symlink, symlinkResponse.ErrNo, symlinkResponse.ErrText)
}
fi, err := os.Lstat(mnt + "/" + symlinkResponse.Result)
if err != nil {
t.Fatalf("Lstat: %v", err)
}
target, err := os.Readlink(mnt + "/" + symlinkResponse.Result)
if err != nil {
t.Errorf("Readlink: %v", err)
}
if fi.Size() != int64(len(target)) {
t.Errorf("Lstat reports that symbolic link %q's dentry size is %d, but this does not "+
"match the length of the string returned by readlink, which is %d.",
symlink, fi.Size(), len(target))
}
}
// .gocryptfs.reverse.conf in the plaintext dir should be visible as
// gocryptfs.conf
func TestConfigMapping(t *testing.T) {
c := dirB + "/gocryptfs.conf"
if !test_helpers.VerifyExistence(t, c) {
t.Errorf("%s missing", c)
}
data, err := ioutil.ReadFile(c)
if err != nil {
t.Fatal(err)
}
if len(data) == 0 {
t.Errorf("empty file")
}
}
// Check that the access() syscall works on virtual gocryptfs.diriv files
func TestAccessVirtualDirIV(t *testing.T) {
if plaintextnames || deterministic_names {
t.Skip("test makes no sense for plaintextnames or deterministic_names")
}
var R_OK uint32 = 4
var W_OK uint32 = 2
var X_OK uint32 = 1
fn := dirB + "/gocryptfs.diriv"
err := syscall.Access(fn, R_OK)
if err != nil {
t.Errorf("%q should be readable, but got error: %v", fn, err)
}
err = syscall.Access(fn, W_OK)
if err == nil {
t.Errorf("should NOT be writeable")
}
err = syscall.Access(fn, X_OK)
if err == nil {
t.Errorf("should NOT be executable")
}
}
// Check that the access() syscall works on regular files
func TestAccess(t *testing.T) {
f, err := os.Create(dirA + "/testaccess1")
if err != nil {
t.Fatal(err)
}
f.Close()
f, err = os.Open(dirB)
if err != nil {
t.Fatal(err)
}
defer f.Close()
names, err := f.Readdirnames(0)
if err != nil {
t.Fatal(err)
}
for _, n := range names {
// Check if file exists - this should never fail
err = syscallcompat.Faccessat(unix.AT_FDCWD, dirB+"/"+n, unix.F_OK)
if err != nil {
t.Errorf("%s: %v", n, err)
}
// Check if file is readable
err = syscallcompat.Faccessat(unix.AT_FDCWD, dirB+"/"+n, unix.R_OK)
if err != nil {
t.Logf("%s: %v", n, err)
}
}
}
// Opening a nonexistent file name should return ENOENT
// and not EBADMSG or EIO or anything else.
func TestEnoent(t *testing.T) {
fn := dirB + "/TestEnoent"
_, err := syscall.Open(fn, syscall.O_RDONLY, 0)
if err != syscall.ENOENT {
t.Errorf("want ENOENT, got: %v", err)
}
}
// If the symlink target gets too long due to base64 encoding, we should
// return ENAMETOOLONG instead of having the kernel reject the data and
// returning an I/O error to the user.
// https://github.com/rfjakob/gocryptfs/issues/167
func TestTooLongSymlink(t *testing.T) {
var err error
var l int
fn := dirA + "/TooLongSymlink"
// Try 4000 first (works on ext4 and tmpfs), then retry with 1000 (XFS and
// Darwin have a limit of about 1024)
for _, l = range []int{4000, 1000} {
target := string(bytes.Repeat([]byte("x"), l))
err = os.Symlink(target, fn)
if err == nil {
break
}
}
if err != nil {
t.Fatal(err)
}
// save later tests the trouble of dealing with ENAMETOOLONG errors
defer func() {
os.Remove(fn)
// immediately create a new symlink so the inode number is not
// reused for something else
os.Symlink("/tmp", fn)
}()
t.Logf("Created symlink of length %d", l)
_, err = os.Readlink(dirC + "/TooLongSymlink")
if err == nil {
return
}
err2 := err.(*os.PathError)
if err2.Err != syscall.ENAMETOOLONG {
t.Errorf("Expected %q error, got %q instead", syscall.ENAMETOOLONG,
err2.Err)
}
}
// Test that we can traverse a directory with 0100 permissions
// (execute but no read). This used to be a problem as OpenDirNofollow opened
// all directories in the path with O_RDONLY. Now it uses O_PATH, which only needs
// the executable bit.
func Test0100Dir(t *testing.T) {
dir := dirA + "/" + t.Name()
err := os.Mkdir(dir, 0700)
if err != nil {
t.Fatal(err)
}
file := dir + "/hello"
err = ioutil.WriteFile(file, []byte("hello"), 0600)
if err != nil {
t.Fatal(err)
}
err = os.Chmod(dir, 0100)
if err != nil {
t.Fatal(err)
}
fileReverse := dirC + "/" + t.Name() + "/hello"
fd, err := os.Open(fileReverse)
// Make sure the dir can be removed after the test is done
os.Chmod(dir, 0700)
if err != nil {
t.Fatal(err)
}
fd.Close()
}
func TestStatfs(t *testing.T) {
var st syscall.Statfs_t
syscall.Statfs(dirB, &st)
if st.Bsize == 0 {
t.Errorf("statfs reports size zero: %#v", st)
}
}
// TestSeekData tests that SEEK_DATA works
func TestSeekData(t *testing.T) {
if !plaintextnames {
t.Skip()
}
fn := filepath.Join(dirA, t.Name())
f, err := os.Create(fn)
if err != nil {
t.Fatal(err)
}
var dataOffset int64 = 1 * 1024 * 1024 * 1024 // 1 GiB
if _, err = f.Seek(dataOffset, 0); err != nil {
t.Fatal(err)
}
if _, err = f.Write([]byte("foo")); err != nil {
t.Fatal(err)
}
f.Close()
fn2 := filepath.Join(dirB, t.Name())
f, err = os.Open(fn2)
if err != nil {
t.Fatal(err)
}
off, err := f.Seek(1024*1024, unix.SEEK_DATA)
if err != nil {
t.Fatal(err)
}
if off < dataOffset-1024*1024 {
t.Errorf("off=%d, expected=%d\n", off, dataOffset)
}
f.Close()
}
// newWorkdir creates a new empty dir in dirA and returns the full path to it along
// with the corresponding encrypted path in dirB
func newWorkdir(t *testing.T) (workdirA, workdirB string) {
workdirA = dirA + "/" + t.Name()
if err := os.Mkdir(workdirA, 0700); err != nil {
t.Fatal(err)
}
// Find workdir in dirB (=encrypted view)
var st syscall.Stat_t
if err := syscall.Stat(workdirA, &st); err != nil {
t.Fatal(err)
}
workdirB = dirB + "/" + findIno(dirB, st.Ino)
t.Logf("newWorkdir: workdirA=%q workdirB=%q", workdirA, workdirB)
return
}
// gocryptfs.longname.*.name of hardlinked files should not appear hardlinked (as the
// contents are different).
//
// This means that
// 1) They have a different NodeID, hence the kernel knows it's different files
// 2) They have a different inode number, hence userspace knows they are not hard-linked.
//
// https://github.com/rfjakob/gocryptfs/issues/802
func TestHardlinkedLongname(t *testing.T) {
if plaintextnames {
t.Skip()
}
workdirA, workdirB := newWorkdir(t)
long1 := workdirA + "/" + strings.Repeat("x", 200)
if err := ioutil.WriteFile(long1, []byte("hello"), 0600); err != nil {
t.Fatal(err)
}
var long1_stat syscall.Stat_t
if err := syscall.Stat(long1, &long1_stat); err != nil {
t.Fatal(err)
}
long2 := workdirA + "/" + strings.Repeat("y", 220)
if err := syscall.Link(long1, long2); err != nil {
t.Fatal(err)
}
matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
if err != nil {
t.Fatal(err)
}
if len(matches) != 2 {
t.Fatalf("BUG: only %d matches, want 2", len(matches))
}
if test_helpers.Md5fn(matches[0]) == test_helpers.Md5fn(matches[1]) {
t.Errorf("Files %q are identical - that's wrong!", matches)
}
var st0 syscall.Stat_t
if err := syscall.Stat(matches[0], &st0); err != nil {
t.Fatal(err)
}
var st1 syscall.Stat_t
if err := syscall.Stat(matches[1], &st1); err != nil {
t.Fatal(err)
}
if st0.Ino == st1.Ino {
t.Errorf("Files %q have the same inode number - that's wrong!", matches)
}
}
// With inode number reuse and hard links, we could have returned
// wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
// (https://github.com/rfjakob/gocryptfs/issues/802).
//
// Now that this is fixed, ensure that rsync and similar tools pick up the new
// correct files by advancing mtime and ctime by 10 seconds, which should be more
// than any filesytems' timestamp granularity (FAT32 has 2 seconds).
func TestMtimePlus10(t *testing.T) {
if plaintextnames {
t.Skip("plaintextnames mode does not have virtual files")
}
workdirA, workdirB := newWorkdir(t)
long := workdirA + "/" + strings.Repeat("x", 200)
if err := os.WriteFile(long, nil, 0600); err != nil {
t.Fatal(err)
}
long_stat, err := os.Stat(long)
if err != nil {
t.Fatal(err)
}
workdirA_stat, err := os.Stat(workdirA)
if err != nil {
t.Fatal(err)
}
// Find and check gocryptfs.longname.*.name
matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
if err != nil {
t.Fatal(err)
}
if len(matches) != 1 {
t.Fatal(matches)
}
name_stat, err := os.Stat(matches[0])
if err != nil {
t.Fatal(err)
}
if name_stat.ModTime().Unix() != long_stat.ModTime().Unix()+10 {
t.Errorf(".name file should show mtime+10")
}
// Check gocryptfs.diriv
if deterministic_names {
// No gocryptfs.diriv
return
}
diriv_stat, err := os.Stat(workdirB + "/gocryptfs.diriv")
if err != nil {
t.Fatal(err)
}
if diriv_stat.ModTime().Unix() != workdirA_stat.ModTime().Unix()+10 {
t.Errorf("diriv file should show mtime+10")
}
}
|