Compare commits

...

28 Commits

Author SHA1 Message Date
Jakob Unterwurzacher 2a9d70d48f fido2: drop `-v` option (PIN request)
We used to pass `-v` on `gocryptfs -init` but not for
mount, which seems strange by itself, but more importantly,
`-v` does not work on Yubikeys.

Drop `-v`.

Fixes https://github.com/rfjakob/gocryptfs/issues/571
2021-06-27 11:17:29 +02:00
Jakob Unterwurzacher d6c8d892ff fido2: pretty-print fidoCommand in debug output
Related: https://github.com/rfjakob/gocryptfs/issues/571
2021-06-27 11:12:40 +02:00
Jakob Unterwurzacher fe616ddad5 doc: update performance.txt 2021-06-26 20:57:39 +02:00
Jakob Unterwurzacher 49507ea869 tests/fsck: delete obsolete script run_fsck.bash
Not called by anybody.
2021-06-26 19:27:58 +02:00
Jakob Unterwurzacher ad3eeaedc5 tests, maxlen.bash: speed up TestMaxlen using QUICK=1
From >6 to <1 second.
2021-06-26 19:13:24 +02:00
Jakob Unterwurzacher 446c3d7e93 tests: matrix: show content detail on mismatch 2021-06-26 18:58:29 +02:00
Jakob Unterwurzacher 4fd95b718b fusefrontend: delete openBackingDir 2021-06-26 18:49:54 +02:00
Jakob Unterwurzacher 5306fc345b fusefrontend: convert last callers from openBackingDir to prepareAtSyscall 2021-06-26 18:49:54 +02:00
Jakob Unterwurzacher 1f29542b39 tests: better error message on ctlsock query failure 2021-06-26 18:49:54 +02:00
Jakob Unterwurzacher 45648e567a fusefrontend: ctlsock: get rid of unneccessary wrapper function 2021-06-26 18:49:54 +02:00
Jakob Unterwurzacher f9f4bd214f fusefrontend: convert ctlsock from openBackingDir to prepareAtSyscall
openBackingDir will be removed.

Also, remove leftover debug printfs.
2021-06-26 18:49:54 +02:00
Jakob Unterwurzacher ee59b5269b fusefrontend: convert openBackingDir tests to prepareAtSyscall
openBackingDir will be removed.
2021-06-26 16:28:30 +02:00
Jakob Unterwurzacher cbd5e8ba01 tests/default: add maxlen.bash test 2021-06-26 16:09:04 +02:00
Jakob Unterwurzacher 389aba6a6b maxlen.bash: suppress progress output if not on a terminal 2021-06-26 16:08:29 +02:00
Jakob Unterwurzacher 84e702126a fusefrontend: implement recursive diriv caching
The new contrib/maxlen.bash showed that we have exponential
runtime with respect to directory depth.

The new recursive diriv caching is a lot smarter as it caches
intermediate lookups. maxlen.bash now completes in a few seconds.

xfstests results same as
2d158e4c82/screenlog.0 :

  Failures: generic/035 generic/062 generic/080 generic/093 generic/099 generic/215 generic/285 generic/319 generic/426 generic/444 generic/467 generic/477 generic/523
  Failed 13 of 580 tests

benchmark.bash results are identical:

  $ ./benchmark.bash
  Testing gocryptfs at /tmp/benchmark.bash.BdQ: gocryptfs v2.0.1-17-g6b09bc0; go-fuse v2.1.1-0.20210611132105-24a1dfe6b4f8; 2021-06-25 go1.16.5 linux/amd64
  /tmp/benchmark.bash.BdQ.mnt is a mountpoint
  WRITE: 262144000 bytes (262 MB, 250 MiB) copied, 0,4821 s, 544 MB/s
  READ:  262144000 bytes (262 MB, 250 MiB) copied, 0,266061 s, 985 MB/s
  UNTAR: 8,280
  MD5:   4,564
  LS:    1,745
  RM:    2,244
2021-06-25 13:56:53 +02:00
Jakob Unterwurzacher 05b813f202 nametransform: rename BadNameFlag to BadnameSuffix 2021-06-21 12:12:44 +02:00
Jakob Unterwurzacher 689b74835b nametransform: gather badname functions in badname.go 2021-06-21 12:10:04 +02:00
Jakob Unterwurzacher 2efef1e270 nametransform: delete NameTransformer interface
Useless layer of indirection.
2021-06-21 11:53:33 +02:00
Jakob Unterwurzacher e244b51491 tests: cli: add TestZerokey
TestZerokey verifies that `gocryptfs -zerokey` uses the same options as
`gocryptfs -init`.
2021-06-21 11:48:16 +02:00
Jakob Unterwurzacher 6b0e63c1a8 Improve startup debug output
The startup debug output was very verbose but still missing some
effective crypto settings.
2021-06-21 11:32:04 +02:00
Jakob Unterwurzacher c5d8fa83ae nametransform: pass badname patterns via New
This means we can unexport the field.
2021-06-20 19:09:46 +02:00
Jakob Unterwurzacher 203e65066f main: use JSONDump helper for debug output 2021-06-20 18:25:07 +02:00
Jakob Unterwurzacher 50630e9f3d fido2: hide "FIDO2" in gocryptfs.conf if not used
Result of:

$ gocryptfs -init foo
$ cat foo/gocryptfs.conf

Before:

{
	"Creator": "gocryptfs v2.0.1",
	"EncryptedKey": "FodEdNHD/cCwv1n5BuyAkbIOnJ/O5gfdCh3YssUCJ2DUr0A8DrQ5NH2SLhREeWRL3V8EMiPO2Ncr5IVwE4SSxQ==",
	"ScryptObject": {
		"Salt": "brGaw9Jg1kbPuSXFiwoxqK2oXFTgbniSgpiB+cu+67Y=",
		"N": 65536,
		"R": 8,
		"P": 1,
		"KeyLen": 32
	},
	"Version": 2,
	"FeatureFlags": [
		"GCMIV128",
		"HKDF",
		"DirIV",
		"EMENames",
		"LongNames",
		"Raw64"
	],
	"FIDO2": {
		"CredentialID": null,
		"HMACSalt": null
	}
}

After:

{
	"Creator": "gocryptfs v2.0.1-5-gf9718eb-dirty.DerDonut-badnamecontent",
	"EncryptedKey": "oFMj1lS1ZsM/vEfanNMeCTPw3PZr5VWeL7ap8Jd8YQm6evy2BAhtQ/pd6RzDx84wlCz9TpxqHRihuwSEMnOWWg==",
	"ScryptObject": {
		"Salt": "JZ/5mhy4a8EAQ/wDF1POIEe4/Ss38cfJgXgj26DuA4M=",
		"N": 65536,
		"R": 8,
		"P": 1,
		"KeyLen": 32
	},
	"Version": 2,
	"FeatureFlags": [
		"GCMIV128",
		"HKDF",
		"DirIV",
		"EMENames",
		"LongNames",
		"Raw64"
	]
}
2021-06-20 18:09:21 +02:00
DerDonut a611810ff4 Badname file content access
This proposal is the counterpart of the modifications from the `-badname`
parameter. It modifies the plain -> cipher mapping for filenames when using
`-badname` parameter. The new function `EncryptAndHashBadName` tries to find a
cipher filename for the given plain name with the following steps:

1. If `badname` is disabled or direct mapping is successful: Map directly
(default and current behaviour)

2. If a file with badname flag has a valid cipher file, this is returned
(=File just ends with the badname flag)

3. If a file with a badname flag exists where only the badname flag was added,
this is returned (=File cipher name could not be decrypted by function
`DecryptName` and just the badname flag was added)

4. Search for all files which cipher file name extists when cropping more and
more characters from the end. If only 1 file is found, return this

5. Return an error otherwise

This allows file access in the file browsers but most important it allows that
you rename files with undecryptable cipher names in the plain directories.
Renaming those files will then generate a proper cipher filename One
backdraft: When mounting the cipher dir with -badname parameter, you can never
create (or rename to) files whose file name ends with the badname file flag
(at the moment this is " GOCRYPTFS_BAD_NAME"). This will cause an error.

I modified the CLI test function to cover additional test cases. Test [Case
7](https://github.com/DerDonut/gocryptfs/blob/badnamecontent/tests/cli/cli_test.go#L712)
cannot be performed since the cli tests are executed in panic mode. The
testing is stopped on error. Since the function`DecryptName` produces internal
errors when hitting non-decryptable file names, this test was omitted.

This implementation is a proposal where I tried to change the minimum amount
of existing code. Another possibility would be instead of creating the new
function `EncryptAndHashBadName` to modify the signature of the existing
function `EncryptAndHashName(name string, iv []byte)` to
`EncryptAndHashName(name string, iv []byte, dirfd int)` and integrate the
functionality into this function directly. You may allow calling with dirfd=-1
or other invalid values an then performing the current functionality.
2021-06-20 18:09:21 +02:00
Jakob Unterwurzacher cdddd1d711 MANPAGE: describe -badname 2021-06-20 18:09:21 +02:00
Jakob Unterwurzacher 9ac77d42d1 contrib/maxlen.bash: also test dir and path length
Move the script from tests to contrib as it may now
be useful to somebody else.

https://github.com/rfjakob/gocryptfs/issues/552
2021-06-20 18:09:21 +02:00
Marcel Bochtler 2e738efe86 README: Add MacPorts install instructions
See [1] for the Portfile.

[1]: https://github.com/macports/macports-ports/blob/master/fuse/gocryptfs/Portfile.
2021-06-19 20:22:20 +02:00
Marcel Bochtler 7889346947 README: Rename Mac OS X to its latest name
See: https://www.apple.com/macos.
2021-06-19 20:22:20 +02:00
37 changed files with 774 additions and 432 deletions

View File

@ -152,6 +152,31 @@ other users, subject to file permission checking. Only works if
user_allow_other is set in /etc/fuse.conf. This option is equivalent to
"allow_other" plus "default_permissions" described in fuse(8).
#### -badname string
When gocryptfs encounters a "bad" file name (cannot be decrypted or decrypts
to garbage), a warning is logged and the file is hidden from the
plaintext view.
With the `-badname` option, you can select "bad" file names that should
still be shown in the plaintext view instead of hiding them. Bad files
will get ` GOCRYPTFS_BAD_NAME` appended to their name.
Glob pattern. Can be passed multiple times for multiple patterns.
Examples:
Dropbox sync conflicts:
-badname '*conflicted copy*'
Syncthing sync conflicts:
-badname '*.sync-conflict*'
Show all invalid filenames:
-badname '*'
#### -ctlsock string
Create a control socket at the specified location. The socket can be
used to decrypt and encrypt paths inside the filesystem. When using

View File

@ -72,6 +72,7 @@ v2.0-beta2-36-g6aae2aa 505 1000 16.1 8.2 6.3 7.7
v2.0-beta2-37-g24d5d39 558 1000 12.3 6.4 4.4 2.8 fs: add initial dirfd caching
v2.0-beta2-42-g4a07d65 549 1000 8.2 4.7 1.8 2.4 fusefrontend: make dirCache work for "node itself"
v2.0 420 1000 8.5 4.5 1.8 2.3 go1.16.5, Linux 5.11.21
v2.0.1-28-g49507ea 471 991 8.6 4.5 1.7 2.2
Results for EncFS for comparison (benchmark.bash -encfs):

View File

@ -42,10 +42,10 @@ Platforms
Linux is gocryptfs' native platform.
Beta-quality Mac OS X support is available, which means most things work
Beta-quality macOS support is available, which means most things work
fine but you may hit an occasional problem. Check out
[ticket #15](https://github.com/rfjakob/gocryptfs/issues/15) for the history
of Mac OS X support but please create a new ticket if you hit a problem.
of macOS support but please create a new ticket if you hit a problem.
For Windows, an independent C++ reimplementation can be found here:
[cppcryptfs](https://github.com/bailey27/cppcryptfs)
@ -62,11 +62,16 @@ On Debian, gocryptfs is available as a deb package:
apt install gocryptfs
```
On Mac OS X, gocryptfs is available as a Homebrew formula:
On macOS, gocryptfs is available as a Homebrew formula:
```bash
brew install gocryptfs
```
Alternatively, gocryptfs is also available via [MacPorts](https://www.macports.org/) on macOS:
```bash
sudo port install gocryptfs
```
On Fedora, gocryptfs is available as an rpm package:
```bash
sudo dnf install gocryptfs
@ -203,6 +208,9 @@ RM: 2,367
Changelog
---------
v2.1 (IN PROGRESS)
* fido2: do not request PIN on `gocryptfs -init` ([#571](https://github.com/rfjakob/gocryptfs/issues/571))
v2.0.1, 2021-06-07
* Fix symlink creation reporting the wrong size, causing git to report it as modified
([#574](https://github.com/rfjakob/gocryptfs/issues/574))

View File

@ -10,6 +10,7 @@ import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@ -290,6 +291,15 @@ func parseCliOpts() (args argContainer) {
tlog.Fatal.Printf("Idle timeout cannot be less than 0")
os.Exit(exitcodes.Usage)
}
// Make sure all badname patterns are valid
for _, pattern := range args.badname {
_, err := filepath.Match(pattern, "")
if err != nil {
tlog.Fatal.Printf("-badname: invalid pattern %q supplied", pattern)
os.Exit(exitcodes.Usage)
}
}
return args
}

99
contrib/maxlen.bash Executable file
View File

@ -0,0 +1,99 @@
#!/bin/bash
#
# Find out the maximum supported filename length and print it.
#
# Part of the gocryptfs test suite
# https://nuetzlich.net/gocryptfs/
set -eu
MYNAME=$(basename "$0")
if [[ $# -ne 1 || $1 == -* ]]; then
echo "Usage: $MYNAME DIRECTORY"
exit 1
fi
# Only show live progress if connected to a termial
# https://stackoverflow.com/a/911213/1380267
INTERACTIVE=0
if [[ -t 1 ]] ; then
INTERACTIVE=1
fi
cd "$1"
echo "Testing $PWD"
echo -n " Maximum filename length: "
# Add one character at a time until we hit an error
NAME=""
while true ; do
NEXT="${NAME}x"
if [[ -e $NEXT ]]; then
echo "error: file $PWD/$NEXT already exists"
exit 1
fi
echo -n 2> /dev/null > $NEXT || break
rm $NEXT
NAME="$NEXT"
done
echo "${#NAME}"
# Set to 0 if undefined
: ${QUICK:=0}
if [[ $QUICK -ne 1 ]]; then
echo -n " Maximum dirname length: "
# Add one character at a time until we hit an error
NAME=""
while true ; do
NEXT="${NAME}x"
mkdir $NEXT 2> /dev/null || break
rmdir $NEXT
NAME="$NEXT"
done
MAX_DIRNAME=${#NAME}
echo "${#NAME}"
fi
if [[ $QUICK -eq 1 ]]; then
CHARS_TODO=100
else
CHARS_TODO="1 10 100 $MAX_DIRNAME"
fi
for CHARS_PER_SUBDIR in $CHARS_TODO ; do
echo -n " Maximum path length with $(printf %3d $CHARS_PER_SUBDIR) chars per subdir: "
if [[ $INTERACTIVE -eq 1 ]] ; then
echo -n " "
fi
# Trick from https://stackoverflow.com/a/5349842/1380267
SUBDIR=$(printf 'x%.0s' $(seq 1 $CHARS_PER_SUBDIR))
mkdir "$SUBDIR"
P=$SUBDIR
# Create a deep path, one $SUBDIR at a time, until we hit an error
while true ; do
NEXT="$P/$SUBDIR"
mkdir "$NEXT" 2> /dev/null || break
P=$NEXT
if [[ $INTERACTIVE -eq 1 ]] ; then
echo -n -e "\b\b\b\b"
printf %4d ${#P}
fi
done
# Then add one character at a time until we hit an error
NAME=""
while true ; do
NEXT="${NAME}x"
touch "$P/$NEXT" 2> /dev/null || break
NAME=$NEXT
done
if [[ $NAME != "" ]] ; then
P=$P/$NAME
fi
if [[ $INTERACTIVE -eq 1 ]] ; then
echo -n -e "\b\b\b\b"
fi
printf %4d ${#P}
echo
rm -R "$SUBDIR"
done

View File

@ -56,7 +56,7 @@ type ConfFile struct {
// stored in the superblock.
FeatureFlags []string
// FIDO2 parameters
FIDO2 FIDO2Params
FIDO2 *FIDO2Params `json:",omitempty"`
// Filename is the name of the config file. Not exported to JSON.
filename string
}
@ -102,8 +102,10 @@ func Create(filename string, password []byte, plaintextNames bool,
}
if len(fido2CredentialID) > 0 {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2])
cf.FIDO2.CredentialID = fido2CredentialID
cf.FIDO2.HMACSalt = fido2HmacSalt
cf.FIDO2 = &FIDO2Params{
CredentialID: fido2CredentialID,
HMACSalt: fido2HmacSalt,
}
}
{
// Generate new random master key

View File

@ -73,6 +73,9 @@ type ContentEnc struct {
// New returns an initialized ContentEnc instance.
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
tlog.Debug.Printf("contentenc.New: plainBS=%d, forceDecode=%v",
plainBS, forceDecode)
if fuse.MAX_KERNEL_WRITE%plainBS != 0 {
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", fuse.MAX_KERNEL_WRITE)
}

View File

@ -36,6 +36,19 @@ const (
BackendAESSIV AEADTypeEnum = 5
)
func (a AEADTypeEnum) String() string {
switch a {
case BackendOpenSSL:
return "BackendOpenSSL"
case BackendGoGCM:
return "BackendGoGCM"
case BackendAESSIV:
return "BackendAESSIV"
default:
return fmt.Sprintf("%d", a)
}
}
// CryptoCore is the low level crypto implementation.
type CryptoCore struct {
// EME is used for filename encryption.
@ -58,6 +71,9 @@ type CryptoCore struct {
// Note: "key" is either the scrypt hash of the password (when decrypting
// a config file) or the masterkey (when finally mounting the filesystem).
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore {
tlog.Debug.Printf("cryptocore.New: key=%d bytes, aeadType=%v, IVBitLen=%d, useHKDF=%v, forceDecode=%v",
len(key), aeadType, IVBitLen, useHKDF, forceDecode)
if len(key) != KeyLen {
log.Panic(fmt.Sprintf("Unsupported key length %d", len(key)))
}

View File

@ -17,11 +17,22 @@ import (
type fidoCommand int
const (
cred fidoCommand = iota
assert fidoCommand = iota
assertWithPIN fidoCommand = iota
cred fidoCommand = iota
assert fidoCommand = iota
)
// String pretty-prints for debug output
func (fc fidoCommand) String() string {
switch fc {
case cred:
return "cred"
case assert:
return "assert"
default:
return fmt.Sprintf("%d", fc)
}
}
const relyingPartyID = "gocryptfs"
func callFidoCommand(command fidoCommand, device string, stdin []string) ([]string, error) {
@ -31,10 +42,8 @@ func callFidoCommand(command fidoCommand, device string, stdin []string) ([]stri
cmd = exec.Command("fido2-cred", "-M", "-h", "-v", device)
case assert:
cmd = exec.Command("fido2-assert", "-G", "-h", device)
case assertWithPIN:
cmd = exec.Command("fido2-assert", "-G", "-h", "-v", device)
}
tlog.Debug.Printf("callFidoCommand: executing %q with args %q", cmd.Path, cmd.Args)
tlog.Debug.Printf("callFidoCommand %s: executing %q with args %q", command, cmd.Path, cmd.Args)
cmd.Stderr = os.Stderr
in, err := cmd.StdinPipe()
if err != nil {
@ -78,15 +87,11 @@ func Secret(device string, credentialID []byte, salt []byte) (secret []byte) {
crid := base64.StdEncoding.EncodeToString(credentialID)
hmacsalt := base64.StdEncoding.EncodeToString(salt)
stdin := []string{cdh, relyingPartyID, crid, hmacsalt}
// try asserting without PIN first
// call fido2-assert
out, err := callFidoCommand(assert, device, stdin)
if err != nil {
// if that fails, let's assert with PIN
out, err = callFidoCommand(assertWithPIN, device, stdin)
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.FIDO2Error)
}
tlog.Fatal.Println(err)
os.Exit(exitcodes.FIDO2Error)
}
secret, err = base64.StdEncoding.DecodeString(out[4])
if err != nil {

View File

@ -1,7 +1,6 @@
package fusefrontend
import (
"fmt"
"path"
"path/filepath"
"strings"
@ -18,30 +17,46 @@ var _ ctlsocksrv.Interface = &RootNode{} // Verify that interface is implemented
// EncryptPath implements ctlsock.Backend
//
// Symlink-safe through openBackingDir().
func (rn *RootNode) EncryptPath(plainPath string) (string, error) {
if plainPath == "" {
// Empty string gets encrypted as empty string
func (rn *RootNode) EncryptPath(plainPath string) (cipherPath string, err error) {
if rn.args.PlaintextNames || plainPath == "" {
return plainPath, nil
}
if rn.args.PlaintextNames {
return plainPath, nil
dirfd, _, errno := rn.prepareAtSyscallMyself()
if errno != 0 {
return "", errno
}
// Encrypt path level by level using openBackingDir. Pretty inefficient,
// but does not matter here.
defer syscall.Close(dirfd)
// Encrypt path level by level
parts := strings.Split(plainPath, "/")
wd := ""
cPath := ""
for _, part := range parts {
wd = filepath.Join(wd, part)
dirfd, cName, err := rn.openBackingDir(wd)
wd := dirfd
for i, part := range parts {
dirIV, err := nametransform.ReadDirIVAt(wd)
if err != nil {
return "", err
}
syscall.Close(dirfd)
cPath = filepath.Join(cPath, cName)
cPart, err := rn.nameTransform.EncryptAndHashName(part, dirIV)
if err != nil {
return "", err
}
cipherPath = filepath.Join(cipherPath, cPart)
// Last path component? We are done.
if i == len(parts)-1 {
break
}
// Descend into next directory
wd, err = syscallcompat.Openat(wd, cPart, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return "", err
}
// Yes this is somewhat wasteful in terms of used file descriptors:
// we keep them all open until the function returns. But it is simple
// and reliable.
defer syscall.Close(wd)
}
tlog.Debug.Printf("encryptPath '%s' -> '%s'", plainPath, cPath)
return cPath, nil
tlog.Debug.Printf("EncryptPath %q -> %q", plainPath, cipherPath)
return cipherPath, nil
}
// DecryptPath implements ctlsock.Backend
@ -49,40 +64,33 @@ func (rn *RootNode) EncryptPath(plainPath string) (string, error) {
// DecryptPath is symlink-safe because openBackingDir() and decryptPathAt()
// are symlink-safe.
func (rn *RootNode) DecryptPath(cipherPath string) (plainPath string, err error) {
dirfd, _, err := rn.openBackingDir("")
if err != nil {
return "", err
}
defer syscall.Close(dirfd)
return rn.decryptPathAt(dirfd, cipherPath)
}
// decryptPathAt decrypts a ciphertext path relative to dirfd.
//
// Symlink-safe through ReadDirIVAt() and ReadLongNameAt().
func (rn *RootNode) decryptPathAt(dirfd int, cipherPath string) (plainPath string, err error) {
if rn.args.PlaintextNames || cipherPath == "" {
return cipherPath, nil
}
dirfd, _, errno := rn.prepareAtSyscallMyself()
if errno != 0 {
return "", errno
}
defer syscall.Close(dirfd)
// Decrypt path level by level
parts := strings.Split(cipherPath, "/")
wd := dirfd
for i, part := range parts {
dirIV, err := nametransform.ReadDirIVAt(wd)
if err != nil {
fmt.Printf("ReadDirIV: %v\n", err)
return "", err
}
longPart := part
if nametransform.IsLongContent(part) {
longPart, err = nametransform.ReadLongNameAt(wd, part)
if err != nil {
fmt.Printf("ReadLongName: %v\n", err)
return "", err
}
}
name, err := rn.nameTransform.DecryptName(longPart, dirIV)
if err != nil {
fmt.Printf("DecryptName: %v\n", err)
return "", err
}
plainPath = path.Join(plainPath, name)
@ -100,6 +108,5 @@ func (rn *RootNode) decryptPathAt(dirfd int, cipherPath string) (plainPath strin
// and reliable.
defer syscall.Close(wd)
}
return plainPath, nil
}

View File

@ -7,7 +7,6 @@ import (
"syscall"
"time"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
@ -50,6 +49,9 @@ func (e *dirCacheEntry) Clear() {
type dirCache struct {
sync.Mutex
// Expected length of the stored IVs. Only used for sanity checks.
// Usually set to 16, but 0 in plaintextnames mode.
ivLen int
// Cache entries
entries [dirCacheSize]dirCacheEntry
// Where to store the next entry (index into entries)
@ -77,7 +79,7 @@ func (d *dirCache) Clear() {
func (d *dirCache) Store(node *Node, fd int, iv []byte) {
// Note: package ensurefds012, imported from main, guarantees that dirCache
// can never get fds 0,1,2.
if fd <= 0 || len(iv) != nametransform.DirIVLen {
if fd <= 0 || len(iv) != d.ivLen {
log.Panicf("Store sanity check failed: fd=%d len=%d", fd, len(iv))
}
d.Lock()
@ -139,7 +141,7 @@ func (d *dirCache) Lookup(node *Node) (fd int, iv []byte) {
if enableStats {
d.hits++
}
if fd <= 0 || len(iv) != nametransform.DirIVLen {
if fd <= 0 || len(iv) != d.ivLen {
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
}
d.dbg("dirCache.Lookup %p hit fd=%d dup=%d iv=%x\n", node, e.fd, fd, iv)

View File

@ -52,7 +52,7 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
return f.(fs.FileGetattrer).Getattr(ctx, out)
}
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -106,7 +106,7 @@ func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
//
// Symlink-safe through openBackingDir() + Readlinkat().
func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -123,7 +123,7 @@ func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn,
return f2.Setattr(ctx, in, out)
}
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -271,7 +271,7 @@ func (n *Node) Link(ctx context.Context, target fs.InodeEmbedder, name string, o
defer syscall.Close(dirfd)
n2 := toNode(target)
dirfd2, cName2, errno := n2.prepareAtSyscall("")
dirfd2, cName2, errno := n2.prepareAtSyscallMyself()
if errno != 0 {
return
}

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"path/filepath"
"runtime"
"syscall"
@ -154,7 +153,7 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
// This function is symlink-safe through use of openBackingDir() and
// ReadDirIVAt().
func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
parentDirFd, cDirName, errno := n.prepareAtSyscall("")
parentDirFd, cDirName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return nil, errno
}
@ -239,15 +238,14 @@ func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
// Symlink-safe through Unlinkat() + AT_REMOVEDIR.
func (n *Node) Rmdir(ctx context.Context, name string) (code syscall.Errno) {
rn := n.rootNode()
p := filepath.Join(n.Path(), name)
parentDirFd, cName, err := rn.openBackingDir(p)
if err != nil {
return fs.ToErrno(err)
parentDirFd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return errno
}
defer syscall.Close(parentDirFd)
if rn.args.PlaintextNames {
// Unlinkat with AT_REMOVEDIR is equivalent to Rmdir
err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
err := unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
return fs.ToErrno(err)
}
// Unless we are running as root, we need read, write and execute permissions
@ -256,7 +254,7 @@ func (n *Node) Rmdir(ctx context.Context, name string) (code syscall.Errno) {
var origMode uint32
if !rn.args.PreserveOwner {
var st unix.Stat_t
err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)
err := syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return fs.ToErrno(err)
}
@ -360,7 +358,7 @@ retry:
// Opendir is a FUSE call to check if the directory can be opened.
func (n *Node) Opendir(ctx context.Context) (errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}

View File

@ -2,16 +2,12 @@ package fusefrontend
import (
"context"
"log"
"path/filepath"
"sync/atomic"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
@ -80,83 +76,6 @@ func (n *Node) rootNode() *RootNode {
return n.Root().Operations().(*RootNode)
}
// prepareAtSyscall returns a (dirfd, cName) pair that can be used
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
// access the backing encrypted directory.
//
// If you pass a `child` file name, the (dirfd, cName) pair will refer to
// a child of this node.
// If `child` is empty, the (dirfd, cName) pair refers to this node itself. For
// the root node, that means (dirfd, ".").
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
rn := n.rootNode()
// all filesystem operations go through prepareAtSyscall(), so this is a
// good place to reset the idle marker.
atomic.StoreUint32(&rn.IsIdle, 0)
// root node itself is special
if child == "" && n.IsRoot() {
var err error
dirfd, cName, err = rn.openBackingDir("")
if err != nil {
errno = fs.ToErrno(err)
}
return
}
// normal node itself can be converted to child of parent node
if child == "" {
name, p1 := n.Parent()
if p1 == nil || name == "" {
return -1, "", syscall.ENOENT
}
p2 := toNode(p1.Operations())
return p2.prepareAtSyscall(name)
}
// Cache lookup
// TODO make it work for plaintextnames as well?
cacheable := (!rn.args.PlaintextNames)
if cacheable {
var iv []byte
dirfd, iv = rn.dirCache.Lookup(n)
if dirfd > 0 {
cName, err := rn.nameTransform.EncryptAndHashName(child, iv)
if err != nil {
return -1, "", fs.ToErrno(err)
}
return dirfd, cName, 0
}
}
// Slowpath
if child == "" {
log.Panicf("BUG: child name is empty - this cannot happen")
}
p := filepath.Join(n.Path(), child)
if rn.isFiltered(p) {
errno = syscall.EPERM
return
}
dirfd, cName, err := rn.openBackingDir(p)
if err != nil {
errno = fs.ToErrno(err)
return
}
// Cache store
if cacheable {
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work?
iv, err := nametransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", fs.ToErrno(err)
}
rn.dirCache.Store(n, dirfd, iv)
}
return
}
// newChild attaches a new child inode to n.
// The passed-in `st` will be modified to get a unique inode number
// (or, in `-sharedstorage` mode, the inode number will be set to zero).

View File

@ -16,7 +16,7 @@ import (
//
// Symlink-safe through Openat().
func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}

View File

@ -0,0 +1,117 @@
package fusefrontend
import (
"sync/atomic"
"syscall"
"github.com/rfjakob/gocryptfs/internal/tlog"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
)
// prepareAtSyscall returns a (dirfd, cName) pair that can be used
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
// access the backing encrypted child file.
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
if child == "" {
tlog.Warn.Printf("BUG: prepareAtSyscall: child=%q, should have called prepareAtSyscallMyself", child)
return n.prepareAtSyscallMyself()
}
rn := n.rootNode()
// All filesystem operations go through here, so this is a good place
// to reset the idle marker.
atomic.StoreUint32(&rn.IsIdle, 0)
if n.IsRoot() && rn.isFiltered(child) {
return -1, "", syscall.EPERM
}
var encryptName func(int, string, []byte) (string, error)
if !rn.args.PlaintextNames {
encryptName = func(dirfd int, child string, iv []byte) (cName string, err error) {
// Badname allowed, try to determine filenames
if rn.nameTransform.HaveBadnamePatterns() {
return rn.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
}
return rn.nameTransform.EncryptAndHashName(child, iv)
}
}
// Cache lookup
var iv []byte
dirfd, iv = rn.dirCache.Lookup(n)
if dirfd > 0 {
if rn.args.PlaintextNames {
return dirfd, child, 0
}
var err error
cName, err = encryptName(dirfd, child, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", fs.ToErrno(err)
}
return
}
// Slowpath: Open ourselves & read diriv
parentDirfd, myCName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
defer syscall.Close(parentDirfd)
dirfd, err := syscallcompat.Openat(parentDirfd, myCName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
// Cache store
if !rn.args.PlaintextNames {
var err error
iv, err = nametransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", fs.ToErrno(err)
}
}
rn.dirCache.Store(n, dirfd, iv)
if rn.args.PlaintextNames {
return dirfd, child, 0
}
cName, err = encryptName(dirfd, child, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", fs.ToErrno(err)
}
return
}
func (n *Node) prepareAtSyscallMyself() (dirfd int, cName string, errno syscall.Errno) {
dirfd = -1
// Handle root node
if n.IsRoot() {
var err error
rn := n.rootNode()
// Open cipherdir (following symlinks)
dirfd, err = syscallcompat.Open(rn.args.Cipherdir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return -1, "", fs.ToErrno(err)
}
return dirfd, ".", 0
}
// Otherwise convert to prepareAtSyscall of parent node
myName, p1 := n.Parent()
if p1 == nil || myName == "" {
errno = syscall.ENOENT
return
}
parent := toNode(p1.Operations())
return parent.prepareAtSyscall(myName)
}

View File

@ -20,7 +20,7 @@ func filterXattrSetFlags(flags int) int {
}
func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -42,7 +42,7 @@ func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
}
func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags uint32) (errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -64,7 +64,7 @@ func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags
}
func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -86,7 +86,7 @@ func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
}
func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}

View File

@ -17,7 +17,7 @@ func filterXattrSetFlags(flags int) int {
}
func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -32,7 +32,7 @@ func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
}
func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags uint32) (errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -44,7 +44,7 @@ func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags
}
func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}
@ -55,7 +55,7 @@ func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
}
func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("")
dirfd, cName, errno := n.prepareAtSyscallMyself()
if errno != 0 {
return
}

View File

@ -13,7 +13,7 @@ import (
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)
func TestOpenBackingDir(t *testing.T) {
func TestPrepareAtSyscall(t *testing.T) {
cipherdir := test_helpers.InitFS(t)
t.Logf("cipherdir = %q", cipherdir)
args := Args{
@ -33,9 +33,9 @@ func TestOpenBackingDir(t *testing.T) {
t.Fatal(errno)
}
dirfd, cName, err := rn.openBackingDir("")
if err != nil {
t.Fatal(err)
dirfd, cName, errno := rn.prepareAtSyscallMyself()
if errno != 0 {
t.Fatal(errno)
}
if cName != "." {
t.Fatal("cName should be .")
@ -44,15 +44,15 @@ func TestOpenBackingDir(t *testing.T) {
// Again, but populate the cache for "" by looking up a non-existing file
rn.Lookup(nil, "xyz1234", &fuse.EntryOut{})
dirfd, cName, err = rn.openBackingDir("")
if err != nil {
t.Fatal(err)
dirfd, cName, errno = rn.prepareAtSyscallMyself()
if errno != 0 {
t.Fatal(errno)
}
if cName != "." {
t.Fatal("cName should be .")
}
err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
err := syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
if err != nil {
t.Error(err)
}
@ -62,7 +62,7 @@ func TestOpenBackingDir(t *testing.T) {
}
syscall.Close(dirfd)
dirfd, cName, err = rn.openBackingDir("dir1")
dirfd, cName, errno = rn.prepareAtSyscall("dir1")
if err != nil {
t.Fatal(err)
}
@ -75,9 +75,9 @@ func TestOpenBackingDir(t *testing.T) {
}
syscall.Close(dirfd)
dirfd, cName, err = rn.openBackingDir("dir1/dir2")
if err != nil {
t.Fatal(err)
dirfd, cName, errno = dir1.prepareAtSyscall("dir2")
if errno != 0 {
t.Fatal(errno)
}
if cName == "" {
t.Fatal("cName should not be empty")
@ -90,9 +90,9 @@ func TestOpenBackingDir(t *testing.T) {
n255 := strings.Repeat("n", 255)
dir1.Mkdir(nil, n255, 0700, out)
dirfd, cName, err = rn.openBackingDir("dir1/" + n255)
if err != nil {
t.Fatal(err)
dirfd, cName, errno = dir1.prepareAtSyscall(n255)
if errno != 0 {
t.Fatal(errno)
}
if cName == "" {
t.Fatal("cName should not be empty")
@ -107,32 +107,34 @@ func TestOpenBackingDir(t *testing.T) {
syscall.Close(dirfd)
}
func TestOpenBackingDirPlaintextNames(t *testing.T) {
func TestPrepareAtSyscallPlaintextnames(t *testing.T) {
cipherdir := test_helpers.InitFS(t, "-plaintextnames")
args := Args{
Cipherdir: cipherdir,
PlaintextNames: true,
}
fs := newTestFS(args)
rn := newTestFS(args)
out := &fuse.EntryOut{}
_, errno := fs.Mkdir(nil, "dir1", 0700, out)
child, errno := rn.Mkdir(nil, "dir1", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
_, errno = fs.Mkdir(nil, "dir1/dir2", 0700, out)
rn.AddChild("dir1", child, false)
dir1 := toNode(child.Operations())
_, errno = dir1.Mkdir(nil, "dir2", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
dirfd, cName, err := fs.openBackingDir("")
if err != nil {
t.Fatal(err)
dirfd, cName, errno := rn.prepareAtSyscallMyself()
if errno != 0 {
t.Fatal(errno)
}
if cName != "." {
t.Fatal("cName should be .")
}
err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
err := syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
if err != nil {
t.Error(err)
}
@ -142,9 +144,9 @@ func TestOpenBackingDirPlaintextNames(t *testing.T) {
}
syscall.Close(dirfd)
dirfd, cName, err = fs.openBackingDir("dir1")
if err != nil {
t.Fatal(err)
dirfd, cName, errno = rn.prepareAtSyscall("dir1")
if errno != 0 {
t.Fatal(errno)
}
if cName != "dir1" {
t.Fatalf("wrong cName: %q", cName)
@ -155,9 +157,9 @@ func TestOpenBackingDirPlaintextNames(t *testing.T) {
}
syscall.Close(dirfd)
dirfd, cName, err = fs.openBackingDir("dir1/dir2")
if err != nil {
t.Fatal(err)
dirfd, cName, errno = dir1.prepareAtSyscall("dir2")
if errno != 0 {
t.Fatal(errno)
}
if cName != "dir2" {
t.Fatalf("wrong cName: %q", cName)

View File

@ -2,7 +2,6 @@ package fusefrontend
import (
"os"
"path/filepath"
"strings"
"sync"
"syscall"
@ -27,7 +26,7 @@ type RootNode struct {
// states
dirIVLock sync.RWMutex
// Filename encryption helper
nameTransform nametransform.NameTransformer
nameTransform *nametransform.NameTransform
// Content encryption helper
contentEnc *contentenc.ContentEnc
// This lock is used by openWriteOnlyFile() to block concurrent opens while
@ -54,18 +53,23 @@ type RootNode struct {
inoMap inomap.TranslateStater
}
func NewRootNode(args Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode {
func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode {
if args.SerializeReads {
serialize_reads.InitSerializer()
}
if len(args.Exclude) > 0 {
tlog.Warn.Printf("Forward mode does not support -exclude")
}
ivLen := nametransform.DirIVLen
if args.PlaintextNames {
ivLen = 0
}
rn := &RootNode{
args: args,
nameTransform: n,
contentEnc: c,
inoMap: inomap.New(),
dirCache: dirCache{ivLen: ivLen},
}
// In `-sharedstorage` mode we always set the inode number to zero.
// This makes go-fuse generate a new inode number for each lookup.
@ -122,15 +126,15 @@ func (rn *RootNode) reportMitigatedCorruption(item string) {
}
}
// isFiltered - check if plaintext "path" should be forbidden
// isFiltered - check if plaintext file "child" should be forbidden
//
// Prevents name clashes with internal files when file names are not encrypted
func (rn *RootNode) isFiltered(path string) bool {
func (rn *RootNode) isFiltered(child string) bool {
if !rn.args.PlaintextNames {
return false
}
// gocryptfs.conf in the root directory is forbidden
if path == configfile.ConfDefaultName {
if child == configfile.ConfDefaultName {
tlog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n",
configfile.ConfDefaultName)
return true
@ -205,66 +209,6 @@ func (rn *RootNode) openWriteOnlyFile(dirfd int, cName string, newFlags int) (rw
return syscallcompat.Openat(dirfd, cName, newFlags, 0)
}
// openBackingDir opens the parent ciphertext directory of plaintext path
// "relPath". It returns the dirfd (opened with O_PATH) and the encrypted
// basename.
//
// The caller should then use Openat(dirfd, cName, ...) and friends.
// For convenience, if relPath is "", cName is going to be ".".
//
// openBackingDir is secure against symlink races by using Openat and
// ReadDirIVAt.
//
// Retries on EINTR.
func (rn *RootNode) openBackingDir(relPath string) (dirfd int, cName string, err error) {
dirRelPath := nametransform.Dir(relPath)
// With PlaintextNames, we don't need to read DirIVs. Easy.
if rn.args.PlaintextNames {
dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, dirRelPath)
if err != nil {
return -1, "", err
}
// If relPath is empty, cName is ".".
cName = filepath.Base(relPath)
return dirfd, cName, nil
}
// Open cipherdir (following symlinks)
dirfd, err = syscallcompat.Open(rn.args.Cipherdir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
if err != nil {
return -1, "", err
}
// If relPath is empty, cName is ".".
if relPath == "" {
return dirfd, ".", nil
}
// Walk the directory tree
parts := strings.Split(relPath, "/")
for i, name := range parts {
iv, err := nametransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
cName, err = rn.nameTransform.EncryptAndHashName(name, iv)
if err != nil {
syscall.Close(dirfd)
return -1, "", err
}
// Last part? We are done.
if i == len(parts)-1 {
break
}
// Not the last part? Descend into next directory.
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
syscall.Close(dirfd)
if err != nil {
return -1, "", err
}
dirfd = dirfd2
}
return dirfd, cName, nil
}
// encryptSymlinkTarget: "data" is encrypted like file contents (GCM)
// and base64-encoded.
// The empty string encrypts to the empty string.

View File

@ -19,7 +19,7 @@ func newTestFS(args Args) *RootNode {
key := make([]byte, cryptocore.KeyLen)
cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true, false)
cEnc := contentenc.New(cCore, contentenc.DefaultBS, false)
n := nametransform.New(cCore.EMECipher, true, true)
n := nametransform.New(cCore.EMECipher, true, true, nil)
rn := NewRootNode(args, cEnc, n)
oneSec := time.Second
options := &fs.Options{

View File

@ -64,12 +64,3 @@ func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) {
t.Error("Should not exclude any path if no exclusions were specified")
}
}
func TestShouldCallIgnoreParserToCheckExclusion(t *testing.T) {
rfs, ignorerMock := createRFSWithMocks()
rfs.isExcludedPlain("some/path")
if ignorerMock.calledWith != "some/path" {
t.Error("Failed to call IgnoreParser")
}
}

View File

@ -1,32 +0,0 @@
package fusefrontend_reverse
import (
"github.com/rfjakob/gocryptfs/internal/nametransform"
)
type IgnoreParserMock struct {
toExclude string
calledWith string
}
func (parser *IgnoreParserMock) MatchesPath(f string) bool {
parser.calledWith = f
return f == parser.toExclude
}
type NameTransformMock struct {
nametransform.NameTransform
}
func (n *NameTransformMock) DecryptName(cipherName string, iv []byte) (string, error) {
return "mockdecrypt_" + cipherName, nil
}
func createRFSWithMocks() (*RootNode, *IgnoreParserMock) {
ignorerMock := &IgnoreParserMock{}
nameTransformMock := &NameTransformMock{}
var rfs RootNode
rfs.excluder = ignorerMock
rfs.nameTransform = nameTransformMock
return &rfs, ignorerMock
}

View File

@ -28,7 +28,7 @@ type RootNode struct {
// Stores configuration arguments
args fusefrontend.Args
// Filename encryption helper
nameTransform nametransform.NameTransformer
nameTransform *nametransform.NameTransform
// Content encryption helper
contentEnc *contentenc.ContentEnc
// Tests whether a path is excluded (hidden) from the user. Used by -exclude.
@ -41,7 +41,7 @@ type RootNode struct {
// NewRootNode returns an encrypted FUSE overlay filesystem.
// In this case (reverse mode) the backing directory is plain-text and
// ReverseFS provides an encrypted view.
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode {
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode {
rn := &RootNode{
args: args,
nameTransform: n,

View File

@ -0,0 +1,91 @@
package nametransform
import (
"crypto/aes"
"path/filepath"
"strings"
"syscall"
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
)
const (
// BadnameSuffix is appended to filenames in plaintext view if a corrupt
// ciphername is shown due to a matching `-badname` pattern
BadnameSuffix = " GOCRYPTFS_BAD_NAME"
)
// EncryptAndHashBadName tries to find the "name" substring, which (encrypted and hashed)
// leads to an unique existing file
// Returns ENOENT if cipher file does not exist or is not unique
func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int) (cName string, err error) {
var st unix.Stat_t
var filesFound int
lastFoundName, err := be.EncryptAndHashName(name, iv)
if !strings.HasSuffix(name, BadnameSuffix) || err != nil {
//Default mode: same behaviour on error or no BadNameFlag on "name"
return lastFoundName, err
}
//Default mode: Check if File extists without modifications
err = syscallcompat.Fstatat(dirfd, lastFoundName, &st, unix.AT_SYMLINK_NOFOLLOW)
if err == nil {
//file found, return result
return lastFoundName, nil
}
//BadName Mode: check if the name was tranformed without change (badname suffix and undecryptable cipher name)
err = syscallcompat.Fstatat(dirfd, name[:len(name)-len(BadnameSuffix)], &st, unix.AT_SYMLINK_NOFOLLOW)
if err == nil {
filesFound++
lastFoundName = name[:len(name)-len(BadnameSuffix)]
}
// search for the longest badname pattern match
for charpos := len(name) - len(BadnameSuffix); charpos > 0; charpos-- {
//only use original cipher name and append assumed suffix (without badname flag)
cNamePart, err := be.EncryptName(name[:charpos], iv)
if err != nil {
//expand suffix on error
continue
}
if be.longNames && len(cName) > NameMax {
cNamePart = be.HashLongName(cName)
}
cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)]
err = syscallcompat.Fstatat(dirfd, cNameBadReverse, &st, unix.AT_SYMLINK_NOFOLLOW)
if err == nil {
filesFound++
lastFoundName = cNameBadReverse
}
}
if filesFound == 1 {
return lastFoundName, nil
}
// more than 1 possible file found, ignore
return "", syscall.ENOENT
}
func (n *NameTransform) decryptBadname(cipherName string, iv []byte) (string, error) {
for _, pattern := range n.badnamePatterns {
match, err := filepath.Match(pattern, cipherName)
// Pattern should have been validated already
if err == nil && match {
// Find longest decryptable substring
// At least 16 bytes due to AES --> at least 22 characters in base64
nameMin := n.B64.EncodedLen(aes.BlockSize)
for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- {
res, err := n.decryptName(cipherName[:charpos], iv)
if err == nil {
return res + cipherName[charpos:] + BadnameSuffix, nil
}
}
return cipherName + BadnameSuffix, nil
}
}
return "", syscall.EBADMSG
}
// HaveBadnamePatterns returns true if `-badname` patterns were provided
func (n *NameTransform) HaveBadnamePatterns() bool {
return len(n.badnamePatterns) > 0
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
@ -93,30 +92,3 @@ func WriteDirIVAt(dirfd int) error {
}
return nil
}
// encryptAndHashName encrypts "name" and hashes it to a longname if it is
// too long.
// Returns ENAMETOOLONG if "name" is longer than 255 bytes.
func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) {
// Prevent the user from creating files longer than 255 chars.
if len(name) > NameMax {
return "", syscall.ENAMETOOLONG
}
cName, err := be.EncryptName(name, iv)
if err != nil {
return "", err
}
if be.longNames && len(cName) > NameMax {
return be.HashLongName(cName), nil
}
return cName, nil
}
// Dir is like filepath.Dir but returns "" instead of ".".
func Dir(path string) string {
d := filepath.Dir(path)
if d == "." {
return ""
}
return d
}

View File

@ -17,21 +17,6 @@ const (
NameMax = 255
)
// NameTransformer is an interface used to transform filenames.
type NameTransformer interface {
DecryptName(cipherName string, iv []byte) (string, error)
EncryptName(plainName string, iv []byte) (string, error)
EncryptAndHashName(name string, iv []byte) (string, error)
// HashLongName - take the hash of a long string "name" and return
// "gocryptfs.longname.[sha256]"
//
// This function does not do any I/O.
HashLongName(name string) string
WriteLongNameAt(dirfd int, hashName string, plainName string) error
B64EncodeToString(src []byte) string
B64DecodeString(s string) ([]byte, error)
}
// NameTransform is used to transform filenames.
type NameTransform struct {
emeCipher *eme.EMECipher
@ -40,19 +25,23 @@ type NameTransform struct {
// on the Raw64 feature flag
B64 *base64.Encoding
// Patterns to bypass decryption
BadnamePatterns []string
badnamePatterns []string
}
// New returns a new NameTransform instance.
func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform {
func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string) *NameTransform {
tlog.Debug.Printf("nametransform.New: longNames=%v, raw64=%v, badname=%q",
longNames, raw64, badname)
b64 := base64.URLEncoding
if raw64 {
b64 = base64.RawURLEncoding
}
return &NameTransform{
emeCipher: e,
longNames: longNames,
B64: b64,
emeCipher: e,
longNames: longNames,
B64: b64,
badnamePatterns: badname,
}
}
@ -60,22 +49,8 @@ func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform {
// filename "cipherName", and failing that checks if it can be bypassed
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
res, err := n.decryptName(cipherName, iv)
if err != nil {
for _, pattern := range n.BadnamePatterns {
match, err := filepath.Match(pattern, cipherName)
if err == nil && match { // Pattern should have been validated already
// Find longest decryptable substring
// At least 16 bytes due to AES --> at least 22 characters in base64
nameMin := n.B64.EncodedLen(aes.BlockSize)
for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- {
res, err = n.decryptName(cipherName[:charpos], iv)
if err == nil {
return res + cipherName[charpos:] + " GOCRYPTFS_BAD_NAME", nil
}
}
return cipherName + " GOCRYPTFS_BAD_NAME", nil
}
}
if err != nil && n.HaveBadnamePatterns() {
return n.decryptBadname(cipherName, iv)
}
return res, err
}
@ -126,6 +101,24 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s
return cipherName64, nil
}
// EncryptAndHashName encrypts "name" and hashes it to a longname if it is
// too long.
// Returns ENAMETOOLONG if "name" is longer than 255 bytes.
func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) {
// Prevent the user from creating files longer than 255 chars.
if len(name) > NameMax {
return "", syscall.ENAMETOOLONG
}
cName, err := be.EncryptName(name, iv)
if err != nil {
return "", err
}
if be.longNames && len(cName) > NameMax {
return be.HashLongName(cName), nil
}
return cName, nil
}
// B64EncodeToString returns a Base64-encoded string
func (n *NameTransform) B64EncodeToString(src []byte) string {
return n.B64.EncodeToString(src)
@ -135,3 +128,12 @@ func (n *NameTransform) B64EncodeToString(src []byte) string {
func (n *NameTransform) B64DecodeString(s string) ([]byte, error) {
return n.B64.DecodeString(s)
}
// Dir is like filepath.Dir but returns "" instead of ".".
func Dir(path string) string {
d := filepath.Dir(path)
if d == "." {
return ""
}
return d
}

View File

@ -176,6 +176,7 @@ func main() {
if args.debug {
tlog.Debug.Enabled = true
}
tlog.Debug.Printf("cli args: %q", os.Args)
// "-v"
if args.version {
tlog.Debug.Printf("openssl=%v\n", args.openssl)
@ -282,12 +283,6 @@ func main() {
if args.cpuprofile != "" || args.memprofile != "" || args.trace != "" {
tlog.Info.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n")
}
// "-openssl"
if !args.openssl {
tlog.Debug.Printf("OpenSSL disabled, using Go GCM")
} else {
tlog.Debug.Printf("OpenSSL enabled")
}
// Operation flags
nOps := countOpFlags(&args)
if nOps == 0 {

View File

@ -2,7 +2,6 @@ package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"log/syslog"
@ -118,8 +117,6 @@ func doMount(args *argContainer) {
args.noprealloc = true
}
}
// We cannot use JSON for pretty-printing as the fields are unexported
tlog.Debug.Printf("cli args: %#v", args)
// Initialize gocryptfs (read config file, ask for password, ...)
fs, wipeKeys := initFuseFrontend(args)
// Try to wipe secret keys from memory after unmount
@ -309,24 +306,11 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
if args.allow_other && os.Getuid() == 0 {
frontendArgs.PreserveOwner = true
}
jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t")
tlog.Debug.Printf("frontendArgs: %s", string(jsonBytes))
// Init crypto backend
cCore := cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, args.hkdf, args.forcedecode)
cEnc := contentenc.New(cCore, contentenc.DefaultBS, args.forcedecode)
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, args.raw64)
// Init badname patterns
nameTransform.BadnamePatterns = make([]string, 0)
for _, pattern := range args.badname {
_, err := filepath.Match(pattern, "") // Make sure pattern is valid
if err != nil {
tlog.Fatal.Printf("-badname: invalid pattern %q supplied", pattern)
os.Exit(exitcodes.Usage)
} else {
nameTransform.BadnamePatterns = append(nameTransform.BadnamePatterns, pattern)
}
}
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, args.raw64, []string(args.badname))
// After the crypto backend is initialized,
// we can purge the master key from memory.
for i := range masterkey {
@ -334,6 +318,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
}
masterkey = nil
// Spawn fusefrontend
tlog.Debug.Printf("frontendArgs: %s", tlog.JSONDump(frontendArgs))
if args.reverse {
if cryptoBackend != cryptocore.BackendAESSIV {
log.Panic("reverse mode must use AES-SIV, everything else is insecure")

View File

@ -16,6 +16,7 @@ import (
"github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/exitcodes"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)
@ -698,18 +699,29 @@ func TestSymlinkedCipherdir(t *testing.T) {
// TestBadname tests the `-badname` option
func TestBadname(t *testing.T) {
//Supported structure of badname: <ciphername><badname pattern><badname suffix>
//"Visible" shows the success of function DecryptName (cipher -> plain)
//"Access" shows the success of function EncryptAndHashBadName (plain -> cipher)
//Case Visible Access Description
//Case 1 x x Access file without BadName suffix (default mode)
//Case 2 x x Access file with BadName suffix which has a valid cipher file (will only be possible if file was created without badname option)
//Case 3 Access file with valid ciphername + BadName suffix (impossible since this would not be produced by DecryptName)
//Case 4 x x Access file with decryptable part of name and Badname suffix (default badname case)
//Case 5 x x Access file with undecryptable name and BadName suffix (e. g. when part of the cipher name was cut)
//Case 6 x Access file with multiple possible matches.
//Case 7 Access file with BadName suffix and non-matching pattern
dir := test_helpers.InitFS(t)
mnt := dir + ".mnt"
validFileName := "file"
invalidSuffix := ".invalid_file"
invalidSuffix := "_invalid_file"
var contentCipher [7][]byte
//first mount without badname (see case 2)
test_helpers.MountOrFatal(t, dir, mnt, "-extpass=echo test", "-wpanic=false")
// use static suffix for testing
test_helpers.MountOrFatal(t, dir, mnt, "-badname=*", "-extpass=echo test")
defer test_helpers.UnmountPanic(mnt)
// write one valid filename (empty content)
file := mnt + "/" + validFileName
err := ioutil.WriteFile(file, nil, 0600)
// Case 1: write one valid filename (empty content)
err := ioutil.WriteFile(file, []byte("Content Case 1."), 0600)
if err != nil {
t.Fatal(err)
}
@ -720,7 +732,6 @@ func TestBadname(t *testing.T) {
t.Fatal(err)
}
defer fread.Close()
encryptedfilename := ""
ciphernames, err := fread.Readdirnames(0)
if err != nil {
@ -733,14 +744,64 @@ func TestBadname(t *testing.T) {
break
}
}
//Generate valid cipherdata for all cases
for i := 0; i < len(contentCipher); i++ {
err := ioutil.WriteFile(file, []byte(fmt.Sprintf("Content Case %d.", i+1)), 0600)
if err != nil {
t.Fatal(err)
}
//save the cipher data for file operations in cipher dir
contentCipher[i], err = ioutil.ReadFile(dir + "/" + encryptedfilename)
if err != nil {
t.Fatal(err)
}
}
// write invalid file which should be decodable
err = ioutil.WriteFile(dir+"/"+encryptedfilename+invalidSuffix, nil, 0600)
//re-write content for case 1
err = ioutil.WriteFile(file, []byte("Content Case 1."), 0600)
if err != nil {
t.Fatal(err)
}
// write invalid file which is not decodable (cropping the encrpyted file name)
err = ioutil.WriteFile(dir+"/"+encryptedfilename[:len(encryptedfilename)-2]+invalidSuffix, nil, 0600)
// Case 2: File with invalid suffix in plain name but valid cipher file
file = mnt + "/" + validFileName + nametransform.BadnameSuffix
err = ioutil.WriteFile(file, []byte("Content Case 2."), 0600)
if err != nil {
t.Fatal(err)
}
// unmount...
test_helpers.UnmountPanic(mnt)
// ...and remount with -badname.
test_helpers.MountOrFatal(t, dir, mnt, "-badname=*valid*", "-extpass=echo test", "-wpanic=false")
defer test_helpers.UnmountPanic(mnt)
// Case 3 is impossible: only BadnameSuffix would mean the cipher name is valid
// Case 4: write invalid file which should be decodable
err = ioutil.WriteFile(dir+"/"+encryptedfilename+invalidSuffix, contentCipher[3], 0600)
if err != nil {
t.Fatal(err)
}
//Case 5: write invalid file which is not decodable (cropping the encrpyted file name)
err = ioutil.WriteFile(dir+"/"+encryptedfilename[:len(encryptedfilename)-2]+invalidSuffix, contentCipher[4], 0600)
if err != nil {
t.Fatal(err)
}
// Case 6: Multiple possible matches
// generate two files with invalid cipher names which can both match the badname pattern
err = ioutil.WriteFile(dir+"/mzaZRF9_0IU-_5vv2wPC"+invalidSuffix, contentCipher[5], 0600)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(dir+"/mzaZRF9_0IU-_5vv2wP"+invalidSuffix, contentCipher[5], 0600)
if err != nil {
t.Fatal(err)
}
// Case 7: Non-Matching badname pattern
err = ioutil.WriteFile(dir+"/"+encryptedfilename+"wrongPattern", contentCipher[6], 0600)
if err != nil {
t.Fatal(err)
}
@ -755,22 +816,74 @@ func TestBadname(t *testing.T) {
if err != nil {
t.Fatal(err)
}
foundDecodable := false
foundUndecodable := false
searchstrings := []string{
validFileName,
validFileName + nametransform.BadnameSuffix,
"",
validFileName + invalidSuffix + nametransform.BadnameSuffix,
encryptedfilename[:len(encryptedfilename)-2] + invalidSuffix + nametransform.BadnameSuffix,
"",
validFileName + "wrongPattern" + nametransform.BadnameSuffix}
results := []bool{false, false, true, false, false, true, true}
var filecontent string
var filebytes []byte
for _, name := range names {
if strings.Contains(name, validFileName+invalidSuffix+" GOCRYPTFS_BAD_NAME") {
foundDecodable = true
} else if strings.Contains(name, encryptedfilename[:len(encryptedfilename)-2]+invalidSuffix+" GOCRYPTFS_BAD_NAME") {
foundUndecodable = true
if name == searchstrings[0] {
//Case 1: Test access
filebytes, err = ioutil.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
filecontent = string(filebytes)
if filecontent == "Content Case 1." {
results[0] = true
}
} else if name == searchstrings[1] {
//Case 2: Test Access
filebytes, err = ioutil.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
filecontent = string(filebytes)
if filecontent == "Content Case 2." {
results[1] = true
}
} else if name == searchstrings[3] {
//Case 4: Test Access
filebytes, err = ioutil.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
filecontent = string(filebytes)
if filecontent == "Content Case 4." {
results[3] = true
}
} else if name == searchstrings[4] {
//Case 5: Test Access
filebytes, err = ioutil.ReadFile(mnt + "/" + name)
if err != nil {
t.Fatal(err)
}
filecontent = string(filebytes)
if filecontent == "Content Case 5." {
results[4] = true
}
} else if name == searchstrings[6] {
//Case 7
results[6] = false
}
//Case 3 is always passed
//Case 6 is highly obscure:
//The last part of a valid cipher name must match the badname pattern AND
//the remaining cipher name must still be decryptable. Test case not programmable in a general case
}
if !foundDecodable {
t.Errorf("did not find invalid name %s in %v", validFileName+invalidSuffix+" GOCRYPTFS_BAD_NAME", names)
}
if !foundUndecodable {
t.Errorf("did not find invalid name %s in %v", encryptedfilename[:len(encryptedfilename)-2]+invalidSuffix+" GOCRYPTFS_BAD_NAME", names)
for i := 0; i < len(results); i++ {
if !results[i] {
t.Errorf("Case %d failed: '%s' in [%s]", i+1, searchstrings[i], strings.Join(names, ","))
}
}
}

60
tests/cli/zerokey.go Normal file
View File

@ -0,0 +1,60 @@
package cli
import (
"io/ioutil"
"os"
"os/exec"
"testing"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)
// TestZerokey verifies that `gocryptfs -zerokey` uses the same options as
// `gocryptfs -init`.
func TestZerokey(t *testing.T) {
// Create FS
dir := test_helpers.InitFS(t)
// Change masterkey to all-zero using password change
args := []string{"-q", "-passwd", "-masterkey",
"00000000-00000000-00000000-00000000-00000000-00000000-00000000-00000000"}
args = append(args, dir)
cmd := exec.Command(test_helpers.GocryptfsBinary, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
p, err := cmd.StdinPipe()
if err != nil {
t.Fatal(err)
}
err = cmd.Start()
if err != nil {
t.Error(err)
}
// New password = old password
p.Write([]byte("test\n"))
p.Close()
err = cmd.Wait()
if err != nil {
t.Error(err)
}
// Add content
mnt := dir + ".mnt"
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
file1 := mnt + "/file1"
err = ioutil.WriteFile(file1, []byte("somecontent"), 0600)
if err != nil {
t.Fatal(err)
}
test_helpers.UnmountPanic(mnt)
// Mount using -zerokey and verify we get the same result
test_helpers.MountOrFatal(t, dir, mnt, "-extpass", "echo test")
content, err := ioutil.ReadFile(file1)
if err != nil {
t.Error(err)
} else if string(content) != "somecontent" {
t.Errorf("wrong content: %q", string(content))
}
test_helpers.UnmountPanic(mnt)
}

View File

@ -66,7 +66,7 @@ func TestCtlSockDecrypt(t *testing.T) {
}
response := test_helpers.QueryCtlSock(t, sock, req)
if response.Result == "" || response.ErrNo != 0 {
t.Fatalf("got an error reply: %+v", response)
t.Fatalf("got an error for query %+v: %+v", req, response)
}
// Check if the encrypted path actually exists
cPath := response.Result

View File

@ -18,6 +18,9 @@ import (
func TestMain(m *testing.M) {
test_helpers.ResetTmpDir(true)
// TestZerokey() in tests/cli verifies that mounting with `-zerokey` is equivalent
// to mounting with a config file with all-default options (just the masterkey
// set to all-zero).
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey")
r := m.Run()
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
@ -315,9 +318,9 @@ read(3, "M:\tSylwester Nawrocki <s.nawrock"..., 32768) = 32768
read(3, "rs/scsi/eata*\n\nEATA ISA/EISA/PCI"..., 32768) = 32768
read(3, "F:\tDocumentation/isapnp.txt\nF:\td"..., 32768) = 32768
read(3, "hunkeey@googlemail.com>\nL:\tlinux"..., 32768) = 32768
read(3, "ach-spear3xx/\n\nSPEAR6XX MACHINE "..., 32768) = 32768
read(3, "ach-spear3xx/\n\nSPEAR6XX MACHINE "..., 32768) = 32768 <--- WRONG LENGTH!!!
read(3, "", 32768) = 0
lseek(3, 0, SEEK_CUR) = 196608
lseek(3, 0, SEEK_CUR) = 196608 <--- WRONG LENGTH!!!
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
write(1, "279b6ab0491e7532132e8f32afe6c04d"..., 56279b6ab0491e7532132e8f32afe6c04d linux-3.0/MAINTAINERS
@ -370,3 +373,24 @@ func TestMd5sumMaintainers(t *testing.T) {
t.Logf("full output:\n%s", out)
}
}
func TestMaxlen(t *testing.T) {
workDir := filepath.Join(test_helpers.DefaultPlainDir, t.Name())
if err := os.Mkdir(workDir, 0700); err != nil {
t.Fatal(err)
}
cmd := exec.Command("../../contrib/maxlen.bash", workDir)
cmd.Env = []string{"QUICK=1"}
out, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
t.Fatal(err)
}
want := `
Maximum filename length: 255
Maximum path length with 100 chars per subdir: 4095
`
if !strings.HasSuffix(string(out), want) {
t.Errorf("wrong output: %s", string(out))
}
}

View File

@ -1,2 +0,0 @@
#!/bin/bash
exec ../../gocryptfs -fsck -extpass "echo test" broken_fs_v1.4

View File

@ -119,7 +119,8 @@ func TestConcurrentReadCreate(t *testing.T) {
}
buf := buf0[:n]
if bytes.Compare(buf, content) != 0 {
log.Fatal("content mismatch")
// Calling t.Fatal() from a goroutine hangs the test so we use log.Fatal
log.Fatalf("%s: content mismatch: have=%q want=%q", t.Name(), string(buf), string(content))
}
f.Close()
}

View File

@ -1,18 +0,0 @@
#!/bin/bash -eu
#
# Find out the maximum supported filename length and print it.
#
# Part of the gocryptfs test suite
# https://nuetzlich.net/gocryptfs/
NAME="maxlen."
LEN=0
while [ $LEN -le 10000 ]; do
touch $NAME 2> /dev/null || break
rm $NAME
LEN=${#NAME}
NAME="${NAME}x"
done
echo $LEN

View File

@ -141,6 +141,8 @@ func isExt4(path string) bool {
// gocryptfs -q -init -extpass "echo test" -scryptn=10 $extraArgs $cipherdir
//
// It returns cipherdir without a trailing slash.
//
// If t is set, t.Fatal() is called on error, log.Panic() otherwise.
func InitFS(t *testing.T, extraArgs ...string) string {
prefix := "x."
if t != nil {