diff --git a/cli_args.go b/cli_args.go index 8451083..7743120 100644 --- a/cli_args.go +++ b/cli_args.go @@ -30,7 +30,7 @@ type argContainer struct { noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, sharedstorage, devrandom, fsck bool // Mount options with opposites - dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache bool + dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool masterkey, mountpoint, cipherdir, cpuprofile, memprofile, ko, ctlsock, fsname, force_owner, trace, fido2 string // -extpass, -badname, -passfile can be passed multiple times @@ -180,6 +180,7 @@ func parseCliOpts() (args argContainer) { flagSet.BoolVar(&args.rw, "rw", false, "Mount the filesystem read-write") flagSet.BoolVar(&args.ro, "ro", false, "Mount the filesystem read-only") flagSet.BoolVar(&args.kernel_cache, "kernel_cache", false, "Enable the FUSE kernel_cache option") + flagSet.BoolVar(&args.acl, "acl", false, "Enforce ACLs") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") diff --git a/mount.go b/mount.go index 1194104..9409471 100644 --- a/mount.go +++ b/mount.go @@ -396,6 +396,9 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server { // Make the kernel check the file permissions for us mOpts.Options = append(mOpts.Options, "default_permissions") } + if args.acl { + mOpts.EnableAcl = true + } if args.forcedecode { tlog.Info.Printf(tlog.ColorYellow + "THE OPTION \"-forcedecode\" IS ACTIVE. GOCRYPTFS WILL RETURN CORRUPT DATA!" + tlog.ColorReset) diff --git a/tests/root_test/root_test.go b/tests/root_test/root_test.go index 9a22add..8547e4e 100644 --- a/tests/root_test/root_test.go +++ b/tests/root_test/root_test.go @@ -137,6 +137,7 @@ func writeTillFull(t *testing.T, path string) (int, syscall.Errno) { return sz, 0 } +// TestDiskFull needs root permissions because it creates a loop disk func TestDiskFull(t *testing.T) { if os.Getuid() != 0 { t.Skip("must run as root") @@ -229,3 +230,84 @@ func TestDiskFull(t *testing.T) { t.Fail() } } + +func TestAcl(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("must run as root") + } + cDir := test_helpers.InitFS(t) + os.Chmod(cDir, 0755) + pDir := cDir + ".mnt" + test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-acl", "-extpass=echo test") + defer test_helpers.UnmountPanic(pDir) + + f1 := pDir + "/f1" + if err := ioutil.WriteFile(f1, []byte("hello world\n"), 000); err != nil { + t.Fatal(err) + } + + openUser1234 := func(rwMode int) error { + return asUser(1234, 1234, nil, func() error { + fd, err := syscall.Open(f1, rwMode, 0) + if err != nil { + return err + } + defer syscall.Close(fd) + buf := make([]byte, 100) + if rwMode == syscall.O_RDONLY || rwMode == syscall.O_RDWR { + _, err = syscall.Read(fd, buf) + if err != nil { + return err + } + } + if rwMode == syscall.O_WRONLY || rwMode == syscall.O_RDWR { + _, err = syscall.Write(fd, buf) + if err != nil { + return err + } + } + return err + }) + } + + dumpAcl := func() { + out, err := exec.Command("getfacl", f1).CombinedOutput() + if err != nil { + t.Fatal(err) + } + t.Log(string(out)) + } + + if err := openUser1234(syscall.O_RDONLY); err == nil { + t.Error("this should have failed") + dumpAcl() + } + + // Allow read + out, err := exec.Command("setfacl", "-m", "u:1234:r", f1).CombinedOutput() + if err != nil { + t.Fatal(string(out)) + } + if err := openUser1234(syscall.O_RDONLY); err != nil { + t.Errorf("O_RDONLY should have worked, but got error: %v", err) + dumpAcl() + } + if err := openUser1234(syscall.O_WRONLY); err == nil { + t.Error("O_WRONLY should have failed") + dumpAcl() + } + + // Allow write + out, err = exec.Command("setfacl", "-m", "u:1234:w", f1).CombinedOutput() + if err != nil { + t.Fatal(string(out)) + } + if err := openUser1234(syscall.O_WRONLY); err != nil { + t.Errorf("O_WRONLY should have worked, but got error: %v", err) + dumpAcl() + } + if err := openUser1234(syscall.O_RDONLY); err == nil { + t.Error("O_RDONLY should have failed") + dumpAcl() + } +}