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 :

  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
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 {
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
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,
sharedstorage, devrandom, fsck bool
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro bool
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
// 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.allow_other, "allow_other", false, "Allow other users to access the filesystem. "+
"Only works if user_allow_other is set in /etc/fuse.conf.")
flagSet.BoolVar(&, "ro", false, "Mount the filesystem read-only")
flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode")
flagSet.BoolVar(&args.aessiv, "aessiv", false, "AES-SIV encryption")
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.devrandom, "devrandom", false, "Use /dev/random for generating master key")
flagSet.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR")
// Mount options with opposites
flagSet.BoolVar(&, "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(&, "rw", false, "Mount the filesystem read-write")
flagSet.BoolVar(&, "ro", false, "Mount the filesystem read-only")
flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key")
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu 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")
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")
// 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
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

View File

@ -37,7 +37,7 @@ func forkChild() int {
err := c.Start()
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
err = c.Wait()
@ -47,7 +47,7 @@ func forkChild() int {
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
// 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 {
mOpts.Name += "-reverse"
// Add a volume name if running osxfuse. Otherwise the Finder will show it as
// something like "osxfuse Volume 0 (gocryptfs)".
if runtime.GOOS == "darwin" {
mOpts.Options = append(mOpts.Options, "volname="+path.Base(args.mountpoint))
// The kernel enforces read-only operation, we just have to pass "ro".
// Reverse mounts are always read-only.
if || args.reverse {
mOpts.Options = append(mOpts.Options, "ro")
} else if {
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 {
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
// 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 {
test_helpers.MountOrFatal(t, dir, mnt, "-extpass=echo test", "-noexec")
defer test_helpers.UnmountPanic(mnt)
sh := mnt + "/"
content := `#!/bin/bash
echo hello
err = ioutil.WriteFile(sh, []byte(content), 0755)
if err != nil {
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.
// See also cli_args_test.go for comprehensive tests of "-o" parsing.
func TestMissingOArg(t *testing.T) {

View File

@ -433,9 +433,14 @@ func ExtractCmdExitCode(err error) int {
return 0
// OMG this is convoluted
err2 := err.(*exec.ExitError)
code := err2.Sys().(syscall.WaitStatus).ExitStatus()
return code
if err2, ok := err.(*exec.ExitError); ok {
return err2.Sys().(syscall.WaitStatus).ExitStatus()
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.