reverse mode: add --exclude option

https://github.com/rfjakob/gocryptfs/issues/235
This commit is contained in:
Jakob Unterwurzacher 2018-08-11 23:26:49 +02:00
parent eaa5aecd42
commit ec2fdc19cf
28 changed files with 258 additions and 3 deletions

View File

@ -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.

View File

@ -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

View File

@ -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
@ -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. "+

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -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)

View File

@ -2,7 +2,9 @@ package fusefrontend_reverse
import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"golang.org/x/sys/unix"
@ -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"
@ -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{}
@ -43,7 +49,7 @@ 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),
@ -51,6 +57,22 @@ func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.Na
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
@ -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 {
@ -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)
@ -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)
@ -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)
}
@ -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)
@ -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
}
@ -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 {
@ -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)

View File

@ -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 != "" {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -0,0 +1,21 @@
{
"Creator": "gocryptfs v1.5-41-gf48b731-dirty",
"EncryptedKey": "FkACqloUeFZesem0UzRD3ezLXtPl8wIAxEHoIEfZxFdLMQeWOxqtw5xopJagDWE/GI1VFSUIrJIIIwwgMipmYA==",
"ScryptObject": {
"Salt": "UVfIgV31uj/voHWI4GqGwsTcbVKyYDOWvbleqJKhZbk=",
"N": 1024,
"R": 8,
"P": 1,
"KeyLen": 32
},
"Version": 2,
"FeatureFlags": [
"GCMIV128",
"HKDF",
"DirIV",
"EMENames",
"LongNames",
"Raw64",
"AESSIV"
]
}

View File

@ -0,0 +1,18 @@
{
"Creator": "gocryptfs v1.5-41-gf48b731-dirty",
"EncryptedKey": "wAmckZb7QsIv/GCdkhb5ep8TwJa44qhnswn5tbER6Tifk8TbUmkwBTceaTtYfHAnTQ48q9mnIlcN9cfbNe5oPw==",
"ScryptObject": {
"Salt": "o5XJ78TgG85zZXRnU55ZqHhKLbPge6jsyDiqrLvSqe0=",
"N": 1024,
"R": 8,
"P": 1,
"KeyLen": 32
},
"Version": 2,
"FeatureFlags": [
"GCMIV128",
"HKDF",
"PlaintextNames",
"AESSIV"
]
}

View File

View File

View File

View File

View File