Skip to content

Commit

Permalink
reverse mode: add --exclude option
Browse files Browse the repository at this point in the history
  • Loading branch information
rfjakob committed Aug 11, 2018
1 parent eaa5aec commit ec2fdc1
Show file tree
Hide file tree
Showing 28 changed files with 258 additions and 3 deletions.
8 changes: 7 additions & 1 deletion Documentation/MANPAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,18 @@ Enable (`-dev`) or disable (`-nodev`) device files in a gocryptfs mount
You need root permissions to use `-dev`.

#### -devrandom
Use /dev/random for generating the master key instead of the default Go
Use `/dev/random` for generating the master key instead of the default Go
implementation. This is especially useful on embedded systems with Go versions
prior to 1.9, which fall back to weak random data when the getrandom syscall
is blocking. Using this option can block indefinitely when the kernel cannot
harvest enough entropy.

#### -exclude PATH
Only for reverse mode: exclude relative plaintext path from the encrypted
view. Can be passed multiple times. Example:

gocryptfs -reverse -exclude Music -exclude Movies /home/user /mnt/user.encrypted

#### -exec, -noexec
Enable (`-exec`) or disable (`-noexec`) executables in a gocryptfs mount
(default: `-exec`). If both are specified, `-noexec` takes precedence.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Changelog
vNEXT, in progress
* Fall back to buffered IO even when passed `O_DIRECT`
([commit](https://github.com/rfjakob/gocryptfs/commit/893e41149ed353f355047003b89eeff456990e76))
* Add `-exclude` option for reverse mode

v1.5, 2018-06-12
* **Support extended attributes (xattr)** in forward mode
Expand Down
5 changes: 5 additions & 0 deletions cli_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type argContainer struct {
dev, nodev, suid, nosuid, exec, noexec, rw, ro bool
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
// For reverse mode, --exclude is available. It can be specified multiple times.
exclude multipleStrings
// Configuration file name override
config string
notifypid, scryptn int
Expand Down Expand Up @@ -173,6 +175,9 @@ func parseCliOpts() (args argContainer) {
flagSet.StringVar(&args.fsname, "fsname", "", "Override the filesystem name")
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")

flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view")

flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
"successful mount - used internally for daemonization")
flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. Possible values: 10-28. "+
Expand Down
2 changes: 2 additions & 0 deletions internal/exitcodes/exitcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ const (
// TrezorError - an error was encountered while interacting with a Trezor
// device
TrezorError = 28
// ExcludeError - an error occoured while processing "-exclude"
ExcludeError = 29
)

// Err wraps an error with an associated numeric exit code
Expand Down
2 changes: 2 additions & 0 deletions internal/fusefrontend/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ type Args struct {
SerializeReads bool
// Force decode even if integrity check fails (openSSL only)
ForceDecode bool
// Exclude is a list of paths to make inaccessible
Exclude []string
}
3 changes: 3 additions & 0 deletions internal/fusefrontend/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func NewFS(args Args, c *contentenc.ContentEnc, n *nametransform.NameTransform)
if args.SerializeReads {
serialize_reads.InitSerializer()
}
if len(args.Exclude) > 0 {
tlog.Warn.Printf("Forward mode does not support -exclude")
}
return &FS{
FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir),
args: args,
Expand Down
6 changes: 6 additions & 0 deletions internal/fusefrontend_reverse/rfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ var inodeTable syncmap.Map
// newFile decrypts and opens the path "relPath" and returns a reverseFile
// object. The backing file descriptor is always read-only.
func (rfs *ReverseFS) newFile(relPath string) (*reverseFile, fuse.Status) {
if rfs.isExcluded(relPath) {
// Excluded paths should have been filtered out beforehand. Better safe
// than sorry.
tlog.Warn.Printf("BUG: newFile: received excluded path %q. This should not happen.", relPath)
return nil, fuse.ENOENT
}
pRelPath, err := rfs.decryptPath(relPath)
if err != nil {
return nil, fuse.ToStatus(err)
Expand Down
74 changes: 72 additions & 2 deletions internal/fusefrontend_reverse/rfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package fusefrontend_reverse

import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"

"golang.org/x/sys/unix"
Expand All @@ -14,6 +16,8 @@ import (
"github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/ctlsock"
"github.com/rfjakob/gocryptfs/internal/exitcodes"
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/pathiv"
Expand All @@ -34,6 +38,8 @@ type ReverseFS struct {
nameTransform *nametransform.NameTransform
// Content encryption helper
contentEnc *contentenc.ContentEnc
// Relative ciphertext paths to exclude (hide) from the user. Used by -exclude.
cExclude []string
}

var _ pathfs.FileSystem = &ReverseFS{}
Expand All @@ -43,14 +49,30 @@ var _ pathfs.FileSystem = &ReverseFS{}
// ReverseFS provides an encrypted view.
func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *ReverseFS {
initLongnameCache()
return &ReverseFS{
fs := &ReverseFS{
// pathfs.defaultFileSystem returns ENOSYS for all operations
FileSystem: pathfs.NewDefaultFileSystem(),
loopbackfs: pathfs.NewLoopbackFileSystem(args.Cipherdir),
args: args,
nameTransform: n,
contentEnc: c,
}
if len(args.Exclude) > 0 {
for _, dirty := range args.Exclude {
clean := ctlsock.SanitizePath(dirty)
if clean != dirty {
tlog.Warn.Printf("-exclude: non-canonical path %q has been interpreted as %q", dirty, clean)
}
cPath, err := fs.EncryptPath(clean)
if err != nil {
tlog.Fatal.Printf("-exclude: EncryptPath %q failed: %v", clean, err)
os.Exit(exitcodes.ExcludeError)
}
fs.cExclude = append(fs.cExclude, cPath)
}
tlog.Debug.Printf("-exclude: %v -> %v", fs.args.Exclude, fs.cExclude)
}
return fs
}

// relDir is identical to filepath.Dir excepts that it returns "" when
Expand All @@ -64,6 +86,21 @@ func relDir(path string) string {
return dir
}

// isExcluded finds out if relative ciphertext path "relPath" is excluded
// (used when -exclude is passed by the user)
func (rfs *ReverseFS) isExcluded(relPath string) bool {
for _, e := range rfs.cExclude {
if e == relPath {
return true
}
// Files inside an excluded directory are also excluded
if strings.HasPrefix(relPath, e+"/") {
return true
}
}
return false
}

// isDirIV determines if the path points to a gocryptfs.diriv file
func (rfs *ReverseFS) isDirIV(relPath string) bool {
if rfs.args.PlaintextNames {
Expand Down Expand Up @@ -99,6 +136,9 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool {
// GetAttr - FUSE call
// "relPath" is the relative ciphertext path
func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
if rfs.isExcluded(relPath) {
return nil, fuse.ENOENT
}
// Handle "gocryptfs.conf"
if rfs.isTranslatedConfig(relPath) {
absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil)
Expand Down Expand Up @@ -180,6 +220,9 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr

// Access - FUSE call
func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status {
if rfs.isExcluded(relPath) {
return fuse.ENOENT
}
if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) || rfs.isNameFile(relPath) {
// access(2) R_OK flag for checking if the file is readable, always 4 as defined in POSIX.
ROK := uint32(0x4)
Expand All @@ -203,6 +246,9 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context)

// Open - FUSE call
func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
if rfs.isExcluded(relPath) {
return nil, fuse.ENOENT
}
if rfs.isTranslatedConfig(relPath) {
return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context)
}
Expand Down Expand Up @@ -242,6 +288,9 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn

// OpenDir - FUSE readdir call
func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
if rfs.isExcluded(cipherPath) {
return nil, fuse.ENOENT
}
relPath, err := rfs.decryptPath(cipherPath)
if err != nil {
return nil, fuse.ToStatus(err)
Expand Down Expand Up @@ -292,6 +341,21 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
}
entries[i].Name = cName
}
// Filter out excluded entries
if rfs.cExclude != nil {
filtered := make([]fuse.DirEntry, 0, len(entries))
for _, entry := range entries {
// filepath.Join handles the case of cipherPath="" correctly:
// Join("", "foo") -> "foo". This does not: cipherPath + "/" + name"
p := filepath.Join(cipherPath, entry.Name)
if rfs.isExcluded(p) {
// Skip file
continue
}
filtered = append(filtered, entry)
}
entries = filtered
}
entries = append(entries, virtualFiles[:nVirtual]...)
return entries, fuse.OK
}
Expand All @@ -301,7 +365,10 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
// Securing statfs against symlink races seems to be more trouble than
// it's worth, so we just ignore the path and always return info about the
// backing storage root dir.
func (rfs *ReverseFS) StatFs(path string) *fuse.StatfsOut {
func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut {
if rfs.isExcluded(relPath) {
return nil
}
var s syscall.Statfs_t
err := syscall.Statfs(rfs.args.Cipherdir, &s)
if err != nil {
Expand All @@ -314,6 +381,9 @@ func (rfs *ReverseFS) StatFs(path string) *fuse.StatfsOut {

// Readlink - FUSE call
func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) {
if rfs.isExcluded(relPath) {
return "", fuse.ENOENT
}
dirfd, name, err := rfs.openBackingDir(relPath)
if err != nil {
return "", fuse.ToStatus(err)
Expand Down
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ func main() {
// "-reverse" implies "-aessiv"
if args.reverse {
args.aessiv = true
} else {
if args.exclude != nil {
tlog.Fatal.Printf("-exclude only works in reverse mode")
os.Exit(exitcodes.ExcludeError)
}
}
// "-config"
if args.config != "" {
Expand Down
1 change: 1 addition & 0 deletions mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func initFuseFrontend(args *argContainer) (pfs pathfs.FileSystem, wipeKeys func(
SerializeReads: args.serialize_reads,
ForceDecode: args.forcedecode,
ForceOwner: args._forceOwner,
Exclude: args.exclude,
}
// confFile is nil when "-zerokey" or "-masterkey" was used
if confFile != nil {
Expand Down
11 changes: 11 additions & 0 deletions tests/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,14 @@ func TestMissingOArg(t *testing.T) {
exitcodes.Usage, exitCode)
}
}

// -exclude must return an error in forward mode
func TestExcludeForward(t *testing.T) {
dir := test_helpers.InitFS(t)
mnt := dir + ".mnt"
err := test_helpers.Mount(dir, mnt, false, "-extpass", "echo test", "-exclude", "foo")
if err == nil {
t.Errorf("-exclude in forward mode should fail")
}
t.Log(err)
}
104 changes: 104 additions & 0 deletions tests/reverse/exclude_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package reverse_test

import (
"io/ioutil"
"testing"

"github.com/rfjakob/gocryptfs/internal/ctlsock"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)

const xxx = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

/*
tree exclude_test_fs
exclude_test_fs/
├── dir1
│ ├── file1
│ ├── file2
│ ├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│ └── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
├── dir2
│ ├── file
│ ├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│ │ └── file
│ ├── longfile.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│ └── subdir
│ └── file
├── file1
├── file2
├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│ └── file
├── longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│ └── file
├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
└── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
*/

func ctlsockEncryptPath(t *testing.T, sock string, path string) string {
req := ctlsock.RequestStruct{EncryptPath: path}
response := test_helpers.QueryCtlSock(t, sock, req)
if response.ErrNo != 0 {
t.Fatal(response)
}
return response.Result
}

func TestExclude(t *testing.T) {
pOk := []string{
"file2",
"dir1/file1",
"dir1/longfile1" + xxx,
"longdir1" + xxx,
"longdir1" + xxx + "/file",
"longfile1" + xxx,
}
pExclude := []string{
"file1",
"dir1/file2",
"dir1/longfile2" + xxx,
"dir2",
"dir2/file",
"dir2/file/xxx",
"dir2/subdir",
"dir2/subdir/file",
"dir2/longdir1" + xxx + "/file",
"dir2/longfile." + xxx,
"longfile2" + xxx,
}
// Mount reverse fs
mnt, err := ioutil.TempDir(test_helpers.TmpDir, "TestExclude")
if err != nil {
t.Fatal(err)
}
sock := mnt + ".sock"
cliArgs := []string{"-reverse", "-extpass", "echo test", "-ctlsock", sock}
for _, v := range pExclude {
cliArgs = append(cliArgs, "-exclude", v)
}
if plaintextnames {
cliArgs = append(cliArgs, "-config", "exclude_test_fs/.gocryptfs.reverse.conf.plaintextnames")
}
test_helpers.MountOrFatal(t, "exclude_test_fs", mnt, cliArgs...)
defer test_helpers.UnmountPanic(mnt)
// Get encrypted version of "ok" and "excluded" paths
cOk := make([]string, len(pOk))
cExclude := make([]string, len(pExclude))
for i, v := range pOk {
cOk[i] = ctlsockEncryptPath(t, sock, v)
}
for i, v := range pExclude {
cExclude[i] = ctlsockEncryptPath(t, sock, v)
}
// Check that "excluded" paths are not there and "ok" paths are there
for i, v := range cExclude {
if test_helpers.VerifyExistence(mnt + "/" + v) {
t.Errorf("File %q / %q is visible, but should be excluded", pExclude[i], v)
}
}
for i, v := range cOk {
if !test_helpers.VerifyExistence(mnt + "/" + v) {
t.Errorf("File %q / %q is hidden, but should be visible", pOk[i], v)
}
}
}
Loading

0 comments on commit ec2fdc1

Please sign in to comment.