From cf1ded5236157e2f9ec06eeea26023b67b40f16d Mon Sep 17 00:00:00 2001 From: Charles Duffy Date: Tue, 30 May 2017 16:01:06 -0500 Subject: [PATCH] Implement force_owner option to display ownership as a specific user. --- Documentation/MANPAGE.md | 11 +++++++++++ README.md | 3 +++ cli_args.go | 6 +++++- internal/fusefrontend/args.go | 7 +++++++ internal/fusefrontend/fs.go | 3 +++ internal/fusefrontend_reverse/rfs.go | 9 +++++++++ main.go | 22 ++++++++++++++++++++++ mount.go | 6 ++++++ 8 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index 054fc66..119a3cb 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -78,6 +78,17 @@ that uses built-in Go crypto. Setting this option forces the filesystem to read-only and noexec. +#### -force_owner string +If given a string of the form "uid:gid" (where both "uid" and "gid" are +substituted with positive integers), presents all files as owned by the given +uid and gid, regardless of their actual ownership. Implies "allow_other". + +This is rarely desired behavior: One should *usually* run gocryptfs as the +account which owns the backing-store files, which should *usually* be one and +the same with the account intended to access the decrypted content. An example +of a case where this may be useful is a situation where content is stored on a +filesystem that doesn't properly support UNIX ownership and permissions. + #### -fsname string Override the filesystem name (first column in df -T). Can also be passed as "-o fsname=" and is equivalent to libfuse's option of the diff --git a/README.md b/README.md index ed1c1fc..7902287 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,9 @@ Changelog --------- v1.4 (not yet released) +* Add `force_owner` option to allow files to be presented as owned by a + different user or group from the user running gocryptfs. Please see caveats + and guidance in the man page before using this functionality. * Increase open file limit to 4096 ([#82](https://github.com/rfjakob/gocryptfs/issues/82)). * Implement path decryption via ctlsock ([#84](https://github.com/rfjakob/gocryptfs/issues/84)). Previously, decryption was only implemented for reverse mode. Now both diff --git a/cli_args.go b/cli_args.go index 5f4b328..bdce4ec 100644 --- a/cli_args.go +++ b/cli_args.go @@ -13,6 +13,7 @@ import ( "github.com/rfjakob/gocryptfs/internal/prefer_openssl" "github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/tlog" + "github.com/hanwen/go-fuse/fuse" ) // argContainer stores the parsed CLI options and arguments @@ -22,7 +23,7 @@ type argContainer struct { longnames, allow_other, ro, reverse, aessiv, nonempty, raw64, noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, - memprofile, ko, passfile, ctlsock, fsname string + memprofile, ko, passfile, ctlsock, fsname, force_owner string // Configuration file name override config string notifypid, scryptn int @@ -31,6 +32,8 @@ type argContainer struct { _configCustom bool // _ctlsockFd stores the control socket file descriptor (ctlsock stores the path) _ctlsockFd net.Listener + // _forceOwner is, if non-nil, a parsed, validated Owner (as opposed to the string above) + _forceOwner *fuse.Owner } var flagSet *flag.FlagSet @@ -136,6 +139,7 @@ func parseCliOpts() (args argContainer) { flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list") flagSet.StringVar(&args.ctlsock, "ctlsock", "", "Create control socket at specified path") flagSet.StringVar(&args.fsname, "fsname", "", "Override the filesystem name") + flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership") 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. "+ diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 5781db8..37f4463 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -1,6 +1,7 @@ package fusefrontend import ( + "github.com/hanwen/go-fuse/fuse" "github.com/rfjakob/gocryptfs/internal/cryptocore" ) @@ -16,6 +17,12 @@ type Args struct { // Should we chown a file after it has been created? // This only makes sense if (1) allow_other is set and (2) we run as root. PreserveOwner bool + // Should we force ownership to be presented with a given user and group? + // This only makes sense if allow_other is set. In *most* cases, it also + // only makes sense with PreserveOwner set, but can also make sense without + // PreserveOwner if the underlying filesystem acting as backing store + // enforces ownership itself. + ForceOwner *fuse.Owner // ConfigCustom is true when the user select a non-default config file // location. If it is false, reverse mode maps ".gocryptfs.reverse.conf" // to "gocryptfs.conf" in the plaintext dir. diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index c589302..16707d6 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -77,6 +77,9 @@ func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Stat target, _ := fs.Readlink(name, context) a.Size = uint64(len(target)) } + if fs.args.ForceOwner != nil { + a.Owner = *fs.args.ForceOwner + } return a, status } diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 63384ac..3c84e15 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -115,6 +115,9 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr } var a fuse.Attr a.FromStat(&st) + if rfs.args.ForceOwner != nil { + a.Owner = *rfs.args.ForceOwner + } return &a, fuse.OK } // Handle virtual files (gocryptfs.diriv, *.name) @@ -136,6 +139,9 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr } var a fuse.Attr status = f.GetAttr(&a) + if rfs.args.ForceOwner != nil { + a.Owner = *rfs.args.ForceOwner + } return &a, status } // Decrypt path to "plaintext relative path" @@ -177,6 +183,9 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr a.Size = uint64(len(linkTarget)) } + if rfs.args.ForceOwner != nil { + a.Owner = *rfs.args.ForceOwner + } return &a, fuse.OK } diff --git a/main.go b/main.go index 8dada52..9b0e31b 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "runtime" "runtime/pprof" "strconv" + "strings" "time" "github.com/rfjakob/gocryptfs/internal/configfile" @@ -17,6 +18,7 @@ import ( "github.com/rfjakob/gocryptfs/internal/speed" "github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/tlog" + "github.com/hanwen/go-fuse/fuse" ) // GitVersion is the gocryptfs version according to git, set by build.bash @@ -190,6 +192,26 @@ func main() { pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } + // "-force_owner" + if args.force_owner != "" { + var uidNum, gidNum int64 + ownerPieces := strings.SplitN(args.force_owner, ":", 2) + if len(ownerPieces) != 2 { + tlog.Fatal.Printf("force_owner must be in form UID:GID") + os.Exit(exitcodes.Usage) + } + uidNum, err = strconv.ParseInt(ownerPieces[0], 0, 32) + if err != nil || uidNum < 0 { + tlog.Fatal.Printf("force_owner: Unable to parse UID %v as positive integer", ownerPieces[0]) + os.Exit(exitcodes.Usage) + } + gidNum, err = strconv.ParseInt(ownerPieces[1], 0, 32) + if err != nil || gidNum < 0 { + tlog.Fatal.Printf("force_owner: Unable to parse GID %v as positive integer", ownerPieces[1]) + os.Exit(exitcodes.Usage) + } + args._forceOwner = &fuse.Owner{Uid: uint32(uidNum), Gid: uint32(gidNum)} + } // "-memprofile" if args.memprofile != "" { tlog.Info.Printf("Writing mem profile to %s", args.memprofile) diff --git a/mount.go b/mount.go index c10f90b..c2d1f74 100644 --- a/mount.go +++ b/mount.go @@ -217,6 +217,11 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF if args.aessiv { cryptoBackend = cryptocore.BackendAESSIV } + // forceOwner implies allow_other, as documented. + // Set this early, so args.allow_other can be relied on below this point. + if args._forceOwner != nil { + args.allow_other = true + } frontendArgs := fusefrontend.Args{ Cipherdir: args.cipherdir, Masterkey: key, @@ -229,6 +234,7 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF HKDF: args.hkdf, SerializeReads: args.serialize_reads, ForceDecode: args.forcedecode, + ForceOwner: args._forceOwner, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil {