main: accept -dev, -nodev, -suid, -nosuid, -exec, -noexec

When mounted via /etc/fstab like this,

  /a /b fuse.gocryptfs default 0 0

we always get extra options passed. As reported by @mahkoh
at https://github.com/rfjakob/gocryptfs/pull/233 :

  mount passes `-o noexec` if `-o user` is set and `-o exec` is not set.
  If both `-o user` and `-o exec` are set, it passes `-o exec`.

Make these options work, and in addtion, also make -suid and -rw
work the same way.

Reported-by: @mahkoh
This commit is contained in:
Jakob Unterwurzacher 2018-06-07 22:50:30 +02:00
parent e29a81efc3
commit 53d6a9999d
5 changed files with 66 additions and 15 deletions

View File

@ -20,9 +20,11 @@ import (
type argContainer struct { type argContainer struct {
debug, init, zerokey, fusedebug, openssl, passwd, fg, version, debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
plaintextnames, quiet, nosyslog, wpanic, plaintextnames, quiet, nosyslog, wpanic,
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64, longnames, allow_other, reverse, aessiv, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
sharedstorage, devrandom, fsck bool sharedstorage, devrandom, fsck bool
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro bool
masterkey, mountpoint, cipherdir, cpuprofile, extpass, masterkey, mountpoint, cipherdir, cpuprofile, extpass,
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
// Configuration file name override // Configuration file name override
@ -121,7 +123,6 @@ func parseCliOpts() (args argContainer) {
flagSet.BoolVar(&args.longnames, "longnames", true, "Store names longer than 176 bytes in extra files") flagSet.BoolVar(&args.longnames, "longnames", true, "Store names longer than 176 bytes in extra files")
flagSet.BoolVar(&args.allow_other, "allow_other", false, "Allow other users to access the filesystem. "+ flagSet.BoolVar(&args.allow_other, "allow_other", false, "Allow other users to access the filesystem. "+
"Only works if user_allow_other is set in /etc/fuse.conf.") "Only works if user_allow_other is set in /etc/fuse.conf.")
flagSet.BoolVar(&args.ro, "ro", false, "Mount the filesystem read-only")
flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode") flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode")
flagSet.BoolVar(&args.aessiv, "aessiv", false, "AES-SIV encryption") flagSet.BoolVar(&args.aessiv, "aessiv", false, "AES-SIV encryption")
flagSet.BoolVar(&args.nonempty, "nonempty", false, "Allow mounting over non-empty directories") flagSet.BoolVar(&args.nonempty, "nonempty", false, "Allow mounting over non-empty directories")
@ -137,6 +138,17 @@ func parseCliOpts() (args argContainer) {
flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer") flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer")
flagSet.BoolVar(&args.devrandom, "devrandom", false, "Use /dev/random for generating master key") flagSet.BoolVar(&args.devrandom, "devrandom", false, "Use /dev/random for generating master key")
flagSet.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR") flagSet.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR")
// Mount options with opposites
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
flagSet.BoolVar(&args.nodev, "nodev", false, "Deny device files")
flagSet.BoolVar(&args.suid, "suid", false, "Allow suid binaries")
flagSet.BoolVar(&args.nosuid, "nosuid", false, "Deny suid binaries")
flagSet.BoolVar(&args.exec, "exec", false, "Allow executables")
flagSet.BoolVar(&args.noexec, "noexec", false, "Deny executables")
flagSet.BoolVar(&args.rw, "rw", false, "Mount the filesystem read-write")
flagSet.BoolVar(&args.ro, "ro", false, "Mount the filesystem read-only")
flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key")
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file") flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")
@ -152,12 +164,6 @@ func parseCliOpts() (args argContainer) {
"successful mount - used internally for daemonization") "successful mount - used internally for daemonization")
flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. Possible values: 10-28. "+ flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. Possible values: 10-28. "+
"A lower value speeds up mounting and reduces its memory needs, but makes the password susceptible to brute-force attacks") "A lower value speeds up mounting and reduces its memory needs, but makes the password susceptible to brute-force attacks")
// Ignored otions
var dummyBool bool
ignoreText := "(ignored for compatibility)"
flagSet.BoolVar(&dummyBool, "rw", false, ignoreText)
flagSet.BoolVar(&dummyBool, "nosuid", false, ignoreText)
flagSet.BoolVar(&dummyBool, "nodev", false, ignoreText)
var dummyString string var dummyString string
flagSet.StringVar(&dummyString, "o", "", "For compatibility with mount(1), options can be also passed as a comma-separated list to -o on the end.") flagSet.StringVar(&dummyString, "o", "", "For compatibility with mount(1), options can be also passed as a comma-separated list to -o on the end.")
// Actual parsing // Actual parsing

View File

@ -37,7 +37,7 @@ func forkChild() int {
exitOnUsr1() exitOnUsr1()
err := c.Start() err := c.Start()
if err != nil { if err != nil {
tlog.Fatal.Printf("forkChild: starting %s failed: %v\n", name, err) tlog.Fatal.Printf("forkChild: starting %s failed: %v", name, err)
return exitcodes.ForkChild return exitcodes.ForkChild
} }
err = c.Wait() err = c.Wait()
@ -47,7 +47,7 @@ func forkChild() int {
os.Exit(waitstat.ExitStatus()) os.Exit(waitstat.ExitStatus())
} }
} }
tlog.Fatal.Printf("forkChild: wait returned an unknown error: %v\n", err) tlog.Fatal.Printf("forkChild: wait returned an unknown error: %v", err)
return exitcodes.ForkChild return exitcodes.ForkChild
} }
// The child exited with 0 - let's do the same. // The child exited with 0 - let's do the same.

View File

@ -314,17 +314,33 @@ func initGoFuse(fs pathfs.FileSystem, args *argContainer) *fuse.Server {
if args.reverse { if args.reverse {
mOpts.Name += "-reverse" mOpts.Name += "-reverse"
} }
// Add a volume name if running osxfuse. Otherwise the Finder will show it as // Add a volume name if running osxfuse. Otherwise the Finder will show it as
// something like "osxfuse Volume 0 (gocryptfs)". // something like "osxfuse Volume 0 (gocryptfs)".
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
mOpts.Options = append(mOpts.Options, "volname="+path.Base(args.mountpoint)) mOpts.Options = append(mOpts.Options, "volname="+path.Base(args.mountpoint))
} }
// The kernel enforces read-only operation, we just have to pass "ro". // The kernel enforces read-only operation, we just have to pass "ro".
// Reverse mounts are always read-only. // Reverse mounts are always read-only.
if args.ro || args.reverse { if args.ro || args.reverse {
mOpts.Options = append(mOpts.Options, "ro") mOpts.Options = append(mOpts.Options, "ro")
} else if args.rw {
mOpts.Options = append(mOpts.Options, "rw")
}
// If both "nosuid" and "suid" were passed, the safer option wins.
if args.nosuid {
mOpts.Options = append(mOpts.Options, "nosuid")
} else if args.suid {
mOpts.Options = append(mOpts.Options, "suid")
}
if args.nodev {
mOpts.Options = append(mOpts.Options, "nodev")
} else if args.dev {
mOpts.Options = append(mOpts.Options, "dev")
}
if args.noexec {
mOpts.Options = append(mOpts.Options, "noexec")
} else if args.exec {
mOpts.Options = append(mOpts.Options, "exec")
} }
// Add additional mount options (if any) after the stock ones, so the user has // Add additional mount options (if any) after the stock ones, so the user has
// a chance to override them. // a chance to override them.

View File

@ -449,6 +449,30 @@ func TestMultipleOperationFlags(t *testing.T) {
} }
} }
func TestNoexec(t *testing.T) {
dir := test_helpers.InitFS(t)
mnt := dir + ".mnt"
err := os.Mkdir(mnt, 0700)
if err != nil {
t.Fatal(err)
}
test_helpers.MountOrFatal(t, dir, mnt, "-extpass=echo test", "-noexec")
defer test_helpers.UnmountPanic(mnt)
sh := mnt + "/x.sh"
content := `#!/bin/bash
echo hello
`
err = ioutil.WriteFile(sh, []byte(content), 0755)
if err != nil {
t.Fatal(err)
}
err = exec.Command(sh).Run()
exitCode := test_helpers.ExtractCmdExitCode(err)
if exitCode != int(syscall.EACCES) {
t.Errorf("got exitcode %d instead of EPERM (%d)", exitCode, syscall.EPERM)
}
}
// Test that a missing argument to "-o" triggers exit code 1. // Test that a missing argument to "-o" triggers exit code 1.
// See also cli_args_test.go for comprehensive tests of "-o" parsing. // See also cli_args_test.go for comprehensive tests of "-o" parsing.
func TestMissingOArg(t *testing.T) { func TestMissingOArg(t *testing.T) {

View File

@ -433,9 +433,14 @@ func ExtractCmdExitCode(err error) int {
return 0 return 0
} }
// OMG this is convoluted // OMG this is convoluted
err2 := err.(*exec.ExitError) if err2, ok := err.(*exec.ExitError); ok {
code := err2.Sys().(syscall.WaitStatus).ExitStatus() return err2.Sys().(syscall.WaitStatus).ExitStatus()
return code }
if err2, ok := err.(*os.PathError); ok {
return int(err2.Err.(syscall.Errno))
}
log.Panicf("could not decode error %#v", err)
return 0
} }
// ListFds lists our open file descriptors. // ListFds lists our open file descriptors.