aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml16
-rw-r--r--Documentation/MANPAGE.md2
-rw-r--r--README.md42
-rwxr-xr-xbuild.bash3
-rwxr-xr-xcrossbuild.bash10
-rw-r--r--go.mod6
-rw-r--r--go.sum14
-rw-r--r--internal/fusefrontend/node.go4
-rw-r--r--internal/fusefrontend_reverse/node_helpers.go2
-rw-r--r--internal/fusefrontend_reverse/virtualnode.go9
-rw-r--r--internal/syscallcompat/asuser.go59
-rw-r--r--internal/syscallcompat/asuser_darwin.go46
-rw-r--r--internal/syscallcompat/asuser_linux.go80
-rw-r--r--internal/syscallcompat/sys_darwin.go75
-rw-r--r--internal/syscallcompat/sys_linux.go123
-rw-r--r--internal/syscallcompat/thread_credentials_linux.go61
-rw-r--r--internal/syscallcompat/thread_credentials_linux_32.go40
-rw-r--r--internal/syscallcompat/thread_credentials_linux_other.go37
-rwxr-xr-xpackage-release-tarballs.bash2
-rwxr-xr-xtest.bash10
-rw-r--r--tests/fsck/.gitignore7
-rw-r--r--tests/fsck/fsck_test.go11
-rw-r--r--tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g0
-rw-r--r--tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g0
-rwxr-xr-xtests/issue893.sh27
-rw-r--r--tests/plaintextnames/file_holes_test.go2
-rw-r--r--tests/reverse/correctness_test.go97
-rw-r--r--tests/root_test/issue893_test.go99
-rw-r--r--tests/root_test/main_test.go20
-rw-r--r--tests/root_test/root_test.go51
-rwxr-xr-xtests/stress_tests/fsstress-gocryptfs.bash9
-rwxr-xr-xtests/stress_tests/parallel_cp.sh8
-rwxr-xr-xtests/stress_tests/pingpong.bash6
33 files changed, 687 insertions, 291 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index abda518..c29194a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,15 +14,16 @@ jobs:
# Each major Go release is supported until there are two newer major releases.
# https://go.dev/doc/devel/release#policy
go:
- - "1.18.x" # Ubuntu 22.04 LTS "jammy"
- "1.19.x" # Debian bookworm, bullseye-backports
- - "1.20.x"
- - "1.21.x"
+ - "1.22.x" # Ubuntu 24.04 LTS "noble"
- "oldstable" # 2nd-latest Golang upstream stable
- "stable" # Latest Go upstream stable
+ os:
+ - "ubuntu-24.04"
+ - "ubuntu-24.04-arm"
# Don't cancel everything when one Go version fails
fail-fast: false
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.os }}
steps:
- name: Install Go ${{ matrix.go }}
@@ -42,13 +43,14 @@ jobs:
- run: sudo apt-get install -qq fuse3 libssl-dev
# Fix "/usr/bin/fusermount: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf"
- - run: echo user_allow_other | sudo tee -a /etc/fuse.conf
+ # and "/usr/bin/fusermount3: too many FUSE filesystems mounted"
+ - run: echo -e 'user_allow_other\nmount_max = 10000' | sudo tee -a /etc/fuse.conf && cat /etc/fuse.conf
# Build & upload static binary
- run: ./build-without-openssl.bash
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
- name: gocryptfs static binary (Go ${{ matrix.go }})
+ name: gocryptfs ${{ github.sha }} static ${{ runner.arch }} binary, Go ${{ matrix.go }}
path: gocryptfs
# Actual test steps are in the Makefile
diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md
index 1d3ab6c..c7a1c03 100644
--- a/Documentation/MANPAGE.md
+++ b/Documentation/MANPAGE.md
@@ -580,7 +580,7 @@ files. They are concatenated for the effective password.
Example:
echo hello > hello.txt
- echo word > world.txt
+ echo world > world.txt
gocryptfs -passfile hello.txt -passfile world.txt
The effective password will be "helloworld".
diff --git a/README.md b/README.md
index f5783a4..e77a2f4 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[![gocryptfs](Documentation/gocryptfs-logo.png)](https://nuetzlich.net/gocryptfs/)
[![CI](https://github.com/rfjakob/gocryptfs/actions/workflows/ci.yml/badge.svg)](https://github.com/rfjakob/gocryptfs/actions/workflows/ci.yml)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
-[![Go Report Card](https://goreportcard.com/badge/github.com/rfjakob/gocryptfs)](https://goreportcard.com/report/github.com/rfjakob/gocryptfs)
+[![Go Report Card](https://goreportcard.com/badge/github.com/rfjakob/gocryptfs/v2)](https://goreportcard.com/report/github.com/rfjakob/gocryptfs/v2)
[![Latest release](https://img.shields.io/github/release/rfjakob/gocryptfs.svg)](https://github.com/rfjakob/gocryptfs/releases)
[![Homebrew version](https://img.shields.io/homebrew/v/gocryptfs.svg)](https://formulae.brew.sh/formula/gocryptfs#default)
@@ -195,6 +195,46 @@ RM: 2,367
Changelog
---------
+#### v2.5.4, 2025-04-13
+* Drop `GOAMD64=v2` from `build.bash`, there's user(s) still running `GOAMD64=v1` CPUs
+ ([#908](https://github.com/rfjakob/gocryptfs/issues/908),
+ [commit](https://github.com/rfjakob/gocryptfs/commit/4851c322d5ce06c559eed9e9f3cb0a5c2c72fd5e))
+
+#### v2.5.3, 2025-04-05
+* Fix `go install` failing with `g: malformed file path "tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z\ng": invalid char '\n'`
+ ([c80558](https://github.com/rfjakob/gocryptfs/commit/c8055829c311ecaf532fd171f3a5d104f873272d))
+* Fix panic when go-fuse is newer than specified in go.mod ([#897](https://github.com/rfjakob/gocryptfs/issues/897))
+
+#### v2.5.2, 2025-03-19
+* Use our own `syscallcompat.Setreuid` (and friends) wrappers
+ ([6b1ba584](https://github.com/rfjakob/gocryptfs/commit/6b1ba5846b17eec710a70cb6c6bf23e3f2024289))
+* Upgrade `golang.org/x/sys` again
+* Provide arm64 binaries in releases in addition to amd64
+
+#### v2.5.1, 2025-01-23
+* **Downgrade `golang.org/x/sys` to unbreak `unix.Setreuid` and `-allow_other`
+ ([6d342f3](https://github.com/rfjakob/gocryptfs/commit/6d342f3f4f1e9468da00b141b2abaf1e55f28665),
+ [#893](https://github.com/rfjakob/gocryptfs/issues/893), [#892](https://github.com/rfjakob/gocryptfs/issues/892))**
+
+#### v2.5.0, 2025-01-18
+* **Important fixes for `-reverse` mode affecting the virtual `gocryptfs.diriv` and
+ `gocryptfs.longname.*.name` files.** The bug can cause file *names* to become
+ undecryptable. To make sure that sync tools like rsync copy new, good copies,
+ gocryptfs v2.5.0 and later advance ctime and mtime for these files by 10 seconds.
+ * Fix `-reverse` mode sometimes (triggered by inode number reuse) returning stale
+ data for `gocryptfs.diriv` (#802)
+ * Fix `-reverse` mode hardlinking `gocryptfs.longname.*.name` files of hardlinked
+ files together (#802)
+* Fix `-reverse` mode ignoring `-force-owner` (#809)
+* Add workaround for excessive file fragementation on btrfs (#811)
+* `-ctlsock`: automatically delete orphaned colliding socket file (#776)
+* MacOS: Fix XTIMES panic on startup (#823)
+ * Fixed by updating the go-fuse library to v2.5.0
+* MacOS: merge kernel options before passing them on (#854, #557)
+* Add `-fido2-assert-option` (#807)
+* `-init` now accepts `-masterkey`
+* `-passwd` now ignores `-extpass` and `-passfile` for the *new* password (#287, #882)
+
#### v2.4.0, 2023-06-10
* Try the `mount(2)` syscall before falling back to `fusermount(1)`. This means we
don't need `fusermount(1)` at all if running as root or in a root-like namespace
diff --git a/build.bash b/build.bash
index ffdd307..5e95c3e 100755
--- a/build.bash
+++ b/build.bash
@@ -85,9 +85,6 @@ if [[ -n ${LDFLAGS:-} ]] ; then
GO_LDFLAGS="$GO_LDFLAGS \"-extldflags=$LDFLAGS\""
fi
-# Set GOAMD64 version to v2
-export GOAMD64=v2
-
# Actual "go build" call for gocryptfs
go build "-ldflags=$GO_LDFLAGS" "$@"
# Additional binaries
diff --git a/crossbuild.bash b/crossbuild.bash
index a9fc6ae..ff773ec 100755
--- a/crossbuild.bash
+++ b/crossbuild.bash
@@ -15,12 +15,12 @@ function compile_tests {
set -eux
-export GO111MODULE=on
export CGO_ENABLED=0
GOOS=linux GOARCH=amd64 build
+GOOS=linux GOARCH=386 build
-# See https://github.com/golang/go/wiki/GoArm
+# See https://go.dev/wiki/GoArm
GOOS=linux GOARCH=arm GOARM=7 build
GOOS=linux GOARCH=arm64 build
@@ -30,8 +30,4 @@ GOOS=darwin GOARCH=amd64 build
time GOOS=darwin GOARCH=amd64 compile_tests
# MacOS on Apple Silicon M1.
-# Go 1.16 added support for the M1 and added ios/arm64,
-# so we use this to check if we should attempt a build.
-if go tool dist list | grep ios/arm64 ; then
- GOOS=darwin GOARCH=arm64 build
-fi
+GOOS=darwin GOARCH=arm64 build
diff --git a/go.mod b/go.mod
index 63ac969..3ebb832 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
github.com/rfjakob/eme v1.1.2
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/spf13/pflag v1.0.5
- golang.org/x/crypto v0.18.0
- golang.org/x/sys v0.16.0
- golang.org/x/term v0.16.0
+ golang.org/x/crypto v0.33.0
+ golang.org/x/sys v0.30.0
+ golang.org/x/term v0.29.0
)
diff --git a/go.sum b/go.sum
index 37b5355..3e4cb01 100644
--- a/go.sum
+++ b/go.sum
@@ -25,16 +25,16 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
-golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 687a386..91c947e 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -71,7 +71,9 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) (errno syscall.Errno) {
// If the kernel gives us a file handle, use it.
if f != nil {
- return f.(fs.FileGetattrer).Getattr(ctx, out)
+ if fga, ok := f.(fs.FileGetattrer); ok {
+ return fga.Getattr(ctx, out)
+ }
}
dirfd, cName, errno := n.prepareAtSyscallMyself()
diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go
index 30361bc..898587b 100644
--- a/internal/fusefrontend_reverse/node_helpers.go
+++ b/internal/fusefrontend_reverse/node_helpers.go
@@ -175,7 +175,7 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
errno = fs.ToErrno(err)
return
}
- content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
+ content := rn.deriveDirIV(d.cPath)
var vf *VirtualMemNode
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
if errno != 0 {
diff --git a/internal/fusefrontend_reverse/virtualnode.go b/internal/fusefrontend_reverse/virtualnode.go
index 732564a..95e71ab 100644
--- a/internal/fusefrontend_reverse/virtualnode.go
+++ b/internal/fusefrontend_reverse/virtualnode.go
@@ -100,6 +100,15 @@ func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, ino
st.Nlink = 1
var a fuse.Attr
a.FromStat(st)
+ // With inode number reuse and hard links, we could have returned
+ // wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
+ // (https://github.com/rfjakob/gocryptfs/issues/802).
+ //
+ // Now that this is fixed, ensure that rsync and similar tools pick up the new
+ // correct files by advancing mtime and ctime by 10 seconds, which should be more
+ // than any filesytems' timestamp granularity (FAT32 has 2 seconds).
+ a.Mtime += 10
+ a.Ctime += 10
if rn.args.ForceOwner != nil {
a.Owner = *rn.args.ForceOwner
}
diff --git a/internal/syscallcompat/asuser.go b/internal/syscallcompat/asuser.go
new file mode 100644
index 0000000..0c083ec
--- /dev/null
+++ b/internal/syscallcompat/asuser.go
@@ -0,0 +1,59 @@
+package syscallcompat
+
+import (
+ "golang.org/x/sys/unix"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// OpenatUser runs the Openat syscall in the context of a different user.
+// It switches the current thread to the new user, performs the syscall,
+// and switches back.
+//
+// If `context` is nil, this function behaves like ordinary Openat (no
+// user switching).
+func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
+ f := func() (int, error) {
+ return Openat(dirfd, path, flags, mode)
+ }
+ return asUser(f, context)
+}
+
+// MknodatUser runs the Mknodat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Mknodat.
+//
+// See OpenatUser() for how this works.
+func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := Mknodat(dirfd, path, mode, dev)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
+
+// SymlinkatUser runs the Symlinkat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Symlinkat.
+//
+// See OpenatUser() for how this works.
+func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := unix.Symlinkat(oldpath, newdirfd, newpath)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
+
+// MkdiratUser runs the Mkdirat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Mkdirat.
+//
+// See OpenatUser() for how this works.
+func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := unix.Mkdirat(dirfd, path, mode)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
diff --git a/internal/syscallcompat/asuser_darwin.go b/internal/syscallcompat/asuser_darwin.go
new file mode 100644
index 0000000..5da2782
--- /dev/null
+++ b/internal/syscallcompat/asuser_darwin.go
@@ -0,0 +1,46 @@
+package syscallcompat
+
+import (
+ "runtime"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// asUser runs `f()` under the effective uid, gid, groups specified
+// in `context`.
+//
+// If `context` is nil, `f()` is executed directly without switching user id.
+func asUser(f func() (int, error), context *fuse.Context) (int, error) {
+ if context == nil {
+ return f()
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
+ if err != nil {
+ return -1, err
+ }
+
+ const (
+ // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
+ // revert permissions to the process credentials.
+ KAUTH_UID_NONE = ^uint32(0) - 100
+ KAUTH_GID_NONE = ^uint32(0) - 100
+ )
+
+ defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
+
+ return f()
+}
+
+// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
+func pthread_setugid_np(uid uint32, gid uint32) (err error) {
+ _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/syscallcompat/asuser_linux.go b/internal/syscallcompat/asuser_linux.go
new file mode 100644
index 0000000..804a898
--- /dev/null
+++ b/internal/syscallcompat/asuser_linux.go
@@ -0,0 +1,80 @@
+package syscallcompat
+
+import (
+ "fmt"
+ "io/ioutil"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// asUser runs `f()` under the effective uid, gid, groups specified
+// in `context`.
+//
+// If `context` is nil, `f()` is executed directly without switching user id.
+func asUser(f func() (int, error), context *fuse.Context) (int, error) {
+ if context == nil {
+ return f()
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c ,
+ // https://go-review.googlesource.com/c/go/+/210639 )
+ // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which
+ // is exactly what we not want.
+ //
+ // And unix.{Setgroups,Setregid,Setreuid} also changed to this behavoir in
+ // v0.1.0 (commit d0df966e6959f00dc1c74363e537872647352d51 ,
+ // https://go-review.googlesource.com/c/sys/+/428174 ), so we use
+ // our own syscall wrappers.
+
+ err := Setgroups(getSupplementaryGroups(context.Pid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetgroupsPanic(nil)
+
+ err = Setregid(-1, int(context.Owner.Gid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetregidPanic(-1, 0)
+
+ err = Setreuid(-1, int(context.Owner.Uid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetreuidPanic(-1, 0)
+
+ return f()
+}
+
+func getSupplementaryGroups(pid uint32) (gids []int) {
+ procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
+ blob, err := ioutil.ReadFile(procPath)
+ if err != nil {
+ return nil
+ }
+
+ lines := strings.Split(string(blob), "\n")
+ for _, line := range lines {
+ if strings.HasPrefix(line, "Groups:") {
+ f := strings.Fields(line[7:])
+ gids = make([]int, len(f))
+ for i := range gids {
+ val, err := strconv.ParseInt(f[i], 10, 32)
+ if err != nil {
+ return nil
+ }
+ gids[i] = int(val)
+ }
+ return gids
+ }
+ }
+
+ return nil
+}
diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go
index 06f09f0..cf2f3f0 100644
--- a/internal/syscallcompat/sys_darwin.go
+++ b/internal/syscallcompat/sys_darwin.go
@@ -3,7 +3,6 @@ package syscallcompat
import (
"log"
"path/filepath"
- "runtime"
"syscall"
"time"
"unsafe"
@@ -26,22 +25,8 @@ const (
RENAME_NOREPLACE = 1
RENAME_EXCHANGE = 2
RENAME_WHITEOUT = 4
-
- // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
- // revert permissions to the process credentials.
- KAUTH_UID_NONE = ^uint32(0) - 100
- KAUTH_GID_NONE = ^uint32(0) - 100
)
-// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
-func pthread_setugid_np(uid uint32, gid uint32) (err error) {
- _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
- if e1 != 0 {
- err = e1
- }
- return
-}
-
// Unfortunately fsetattrlist does not have a syscall wrapper yet.
func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
_, _, e1 := syscall.Syscall6(syscall.SYS_FSETATTRLIST, uintptr(fd), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
@@ -84,74 +69,14 @@ func Dup3(oldfd int, newfd int, flags int) (err error) {
//// Emulated Syscalls (see emulate.go) ////////////////
////////////////////////////////////////////////////////
-func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return -1, err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return Openat(dirfd, path, flags, mode)
-}
-
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return emulateMknodat(dirfd, path, mode, dev)
}
-func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return Mknodat(dirfd, path, mode, dev)
-}
-
func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW)
}
-func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return unix.Symlinkat(oldpath, newdirfd, newpath)
-}
-
-func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return unix.Mkdirat(dirfd, path, mode)
-}
-
type attrList struct {
bitmapCount uint16
_ uint16
diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go
index a64b27e..5a4a4ab 100644
--- a/internal/syscallcompat/sys_linux.go
+++ b/internal/syscallcompat/sys_linux.go
@@ -3,10 +3,6 @@ package syscallcompat
import (
"fmt"
- "io/ioutil"
- "runtime"
- "strconv"
- "strings"
"sync"
"syscall"
"time"
@@ -67,104 +63,11 @@ func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return syscall.Fallocate(fd, mode, off, len)
}
-func getSupplementaryGroups(pid uint32) (gids []int) {
- procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
- blob, err := ioutil.ReadFile(procPath)
- if err != nil {
- return nil
- }
-
- lines := strings.Split(string(blob), "\n")
- for _, line := range lines {
- if strings.HasPrefix(line, "Groups:") {
- f := strings.Fields(line[7:])
- gids = make([]int, len(f))
- for i := range gids {
- val, err := strconv.ParseInt(f[i], 10, 32)
- if err != nil {
- return nil
- }
- gids[i] = int(val)
- }
- return gids
- }
- }
-
- return nil
-}
-
-// asUser runs `f()` under the effective uid, gid, groups specified
-// in `context`.
-//
-// If `context` is nil, `f()` is executed directly without switching user id.
-func asUser(f func() (int, error), context *fuse.Context) (int, error) {
- if context == nil {
- return f()
- }
-
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c ,
- // https://go-review.googlesource.com/c/go/+/210639 )
- // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which
- // is exactly what we not want.
- //
- // We now use unix.{Setgroups,Setregid,Setreuid} instead.
-
- err := unix.Setgroups(getSupplementaryGroups(context.Pid))
- if err != nil {
- return -1, err
- }
- defer unix.Setgroups(nil)
-
- err = unix.Setregid(-1, int(context.Owner.Gid))
- if err != nil {
- return -1, err
- }
- defer unix.Setregid(-1, 0)
-
- err = unix.Setreuid(-1, int(context.Owner.Uid))
- if err != nil {
- return -1, err
- }
- defer unix.Setreuid(-1, 0)
-
- return f()
-}
-
-// OpenatUser runs the Openat syscall in the context of a different user.
-//
-// It switches the current thread to the new user, performs the syscall,
-// and switches back.
-//
-// If `context` is nil, this function behaves like ordinary Openat (no
-// user switching).
-func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
- f := func() (int, error) {
- return Openat(dirfd, path, flags, mode)
- }
- return asUser(f, context)
-}
-
// Mknodat wraps the Mknodat syscall.
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return syscall.Mknodat(dirfd, path, mode, dev)
}
-// MknodatUser runs the Mknodat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Mknodat.
-//
-// See OpenatUser() for how this works.
-func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := Mknodat(dirfd, path, mode, dev)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
// Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2
// is not implemented on arm64.
func Dup3(oldfd int, newfd int, flags int) (err error) {
@@ -205,32 +108,6 @@ func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
return syscall.Chmod(procPath, mode)
}
-// SymlinkatUser runs the Symlinkat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Symlinkat.
-//
-// See OpenatUser() for how this works.
-func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := unix.Symlinkat(oldpath, newdirfd, newpath)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
-// MkdiratUser runs the Mkdirat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Mkdirat.
-//
-// See OpenatUser() for how this works.
-func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := unix.Mkdirat(dirfd, path, mode)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
// LsetxattrUser runs the Lsetxattr syscall in the context of a different user.
// This is useful when setting ACLs, as the result depends on the user running
// the operation (see fuse-xfstests generic/375).
diff --git a/internal/syscallcompat/thread_credentials_linux.go b/internal/syscallcompat/thread_credentials_linux.go
new file mode 100644
index 0000000..b5ec6cd
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux.go
@@ -0,0 +1,61 @@
+//go:build linux
+
+// golang.org/x/sys/unix commit
+// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51
+// changed unix.Setreuid/unix.Setregid functions to affect the whole thread, which is
+// what gocryptfs does NOT want (https://github.com/rfjakob/gocryptfs/issues/893).
+// The functions Setreuid/Setegid are copy-pasted from one commit before
+// (9e1f76180b77a12eb07c82eb8e1ea8a7f8d202e7).
+//
+// Looking at the diff at https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51
+// we see that only two architectures, 386 and arm, use SYS_SETREUID32/SYS_SETREGID32
+// (see "man 2 setreuid" for why).
+// All the others architectures use SYS_SETREUID/SYS_SETREGID.
+//
+// As of golang.org/x/sys/unix v0.30.0, Setgroups/setgroups is still per-thread, but
+// it is likely that this will change, too. Setgroups/setgroups are copy-pasted from
+// v0.30.0. The SYS_SETGROUPS32/SYS_SETGROUPS split is the same as for Setreuid.
+//
+// Note: _Gid_t is always uint32 on linux, so we can directly use uint32 for setgroups.
+package syscallcompat
+
+import (
+ "log"
+)
+
+// Setgroups is like setgroups(2) but affects only the current thread
+func Setgroups(gids []int) (err error) {
+ if len(gids) == 0 {
+ return setgroups(0, nil)
+ }
+
+ a := make([]uint32, len(gids))
+ for i, v := range gids {
+ a[i] = uint32(v)
+ }
+ return setgroups(len(a), &a[0])
+}
+
+// SetgroupsPanic calls Setgroups and panics on error
+func SetgroupsPanic(gids []int) {
+ err := Setgroups(gids)
+ if err != nil {
+ log.Panic(err)
+ }
+}
+
+// SetregidPanic calls Setregid and panics on error
+func SetregidPanic(rgid int, egid int) {
+ err := Setregid(rgid, egid)
+ if err != nil {
+ log.Panic(err)
+ }
+}
+
+// SetreuidPanic calls Setreuid and panics on error
+func SetreuidPanic(ruid int, euid int) {
+ err := Setreuid(ruid, euid)
+ if err != nil {
+ log.Panic(err)
+ }
+}
diff --git a/internal/syscallcompat/thread_credentials_linux_32.go b/internal/syscallcompat/thread_credentials_linux_32.go
new file mode 100644
index 0000000..69fffca
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux_32.go
@@ -0,0 +1,40 @@
+//go:build (linux && 386) || (linux && arm)
+
+// Linux on i386 and 32-bit ARM has SYS_SETREUID and friends returning 16-bit values.
+// We need to use SYS_SETREUID32 instead.
+
+package syscallcompat
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// See thread_credentials_linux.go for docs
+
+// Setreuid is like setreuid(2) but affects only the current thread
+func Setreuid(ruid int, euid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID32, uintptr(ruid), uintptr(euid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+// Setreuid is like setregid(2) but affects only the current thread
+func Setregid(rgid int, egid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID32, uintptr(rgid), uintptr(egid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+func setgroups(n int, list *uint32) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS32, uintptr(n), uintptr(unsafe.Pointer(list)), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/syscallcompat/thread_credentials_linux_other.go b/internal/syscallcompat/thread_credentials_linux_other.go
new file mode 100644
index 0000000..ab11c71
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux_other.go
@@ -0,0 +1,37 @@
+//go:build !((linux && 386) || (linux && arm))
+
+package syscallcompat
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// See thread_credentials_linux.go for docs
+
+// Setreuid is like setreuid(2) but affects only the current thread
+func Setreuid(ruid int, euid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID, uintptr(ruid), uintptr(euid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+// Setreuid is like setregid(2) but affects only the current thread
+func Setregid(rgid int, egid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID, uintptr(rgid), uintptr(egid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+func setgroups(n int, list *uint32) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS, uintptr(n), uintptr(unsafe.Pointer(list)), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/package-release-tarballs.bash b/package-release-tarballs.bash
index fde214e..881bce0 100755
--- a/package-release-tarballs.bash
+++ b/package-release-tarballs.bash
@@ -94,4 +94,6 @@ fi
package_source
package_static_binary
+export GOARCH=arm64
+package_static_binary
signing_hint
diff --git a/test.bash b/test.bash
index 174236e..d701f76 100755
--- a/test.bash
+++ b/test.bash
@@ -22,7 +22,6 @@ else
fi
cd "$(dirname "$0")"
-export GO111MODULE=on
MYNAME=$(basename "$0")
TESTDIR=$TMPDIR/gocryptfs-test-parent-$UID
mkdir -p "$TESTDIR"
@@ -110,9 +109,12 @@ if find internal -type f -name \*.go -print0 | xargs -0 grep "panic("; then
exit 1
fi
-# All functions from the commit msg in https://go-review.googlesource.com/c/go/+/210639
-if find . -type f -name \*.go -print0 | xargs -0 grep -E 'syscall.(Setegid|Seteuid|Setgroups|Setgid|Setregid|Setreuid|Setresgid|Setresuid|Setuid)\(' ; then
- echo "$MYNAME: You probably want to use unix.Setgroups and friends. See the comments in OpenatUser() for why."
+# Both syscall.Setreuid etc (since 2020, https://github.com/golang/go/commit/d1b1145cace8b968307f9311ff611e4bb810710c)
+# and unix.Setreuid etc (since 2022, https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51)
+# affect the whole process, not only the current thread, which is what we do NOT want.
+if find . -type f -name \*.go -print0 | xargs -0 grep -v -E '^//' |
+ grep -E '(syscall|unix).(Setegid|Seteuid|Setgroups|Setgid|Setregid|Setreuid|Setresgid|Setresuid|Setuid)\(' ; then
+ echo "$MYNAME: This affects the whole process. Please use the syscallcompat wrappers instead."
exit 1
fi
diff --git a/tests/fsck/.gitignore b/tests/fsck/.gitignore
new file mode 100644
index 0000000..9c9febc
--- /dev/null
+++ b/tests/fsck/.gitignore
@@ -0,0 +1,7 @@
+# Cannot have these "evil" files in git because they break "go install"
+# https://github.com/golang/go/issues/28001
+#
+# "malleable_base64/27AG8t-XZH7G9ou2OSD_z\ng"
+# "malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg"
+#
+malleable_base64/27AG8t-XZH7G9ou2OSD_z?g
diff --git a/tests/fsck/fsck_test.go b/tests/fsck/fsck_test.go
index b70fd49..de49819 100644
--- a/tests/fsck/fsck_test.go
+++ b/tests/fsck/fsck_test.go
@@ -50,6 +50,17 @@ func TestBrokenFsV14(t *testing.T) {
}
func TestMalleableBase64(t *testing.T) {
+ // Evil filenames. Cannot have them in git, because if we do,
+ // go install github.com/rfjakob/gocryptfs/v2@latest
+ // fails with
+ // g: malformed file path "tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg": invalid char '\r'
+ // g: malformed file path "tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg": invalid char '\r'
+ if err := os.WriteFile("malleable_base64/27AG8t-XZH7G9ou2OSD_z\ng", nil, 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile("malleable_base64/27AG8t-XZH7G9ou2OSD_z\rg", nil, 0644); err != nil {
+ t.Fatal(err)
+ }
cmd := exec.Command(test_helpers.GocryptfsBinary, "-fsck", "-extpass", "echo test", "malleable_base64")
outBin, err := cmd.CombinedOutput()
out := string(outBin)
diff --git a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g b/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
deleted file mode 100644
index e69de29..0000000
--- a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
+++ /dev/null
diff --git a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g b/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
deleted file mode 100644
index e69de29..0000000
--- a/tests/fsck/malleable_base64/27AG8t-XZH7G9ou2OSD_z g
+++ /dev/null
diff --git a/tests/issue893.sh b/tests/issue893.sh
new file mode 100755
index 0000000..a1e7cdb
--- /dev/null
+++ b/tests/issue893.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Reproducer for https://github.com/rfjakob/gocryptfs/issues/893 .
+# Run this script as non-root against a root-mounted gocryptfs -allow_other.
+
+set -eu
+
+mountpoint $1
+cd $1
+
+work() {
+ for i in $(seq 100) ; do
+ D=mtest.$BASHPID.$i/foo/bar/baz
+ mkdir -p $D
+ touch $D/foo $D/bar
+ echo AAAAAAAAAAAAAAAAAAAAA > $D/foo
+ rm $D/foo
+ mkdir $D/baz
+ done
+}
+
+rm -Rf mtest.*
+echo .
+
+work &
+work &
+
+wait
diff --git a/tests/plaintextnames/file_holes_test.go b/tests/plaintextnames/file_holes_test.go
index a17597a..ea47113 100644
--- a/tests/plaintextnames/file_holes_test.go
+++ b/tests/plaintextnames/file_holes_test.go
@@ -129,6 +129,8 @@ func doTestFileHoleCopy(t *testing.T, name string, writeOffsets []int64) {
// The test runs with -plaintextnames because that makes it easier to manipulate
// cipherdir directly.
func TestFileHoleCopy(t *testing.T) {
+ t.Skip("TODO: find out why this fails on recent kernels")
+
// | hole | x | hole | x | hole |
// truncate -s 50000 foo && dd if=/dev/zero of=foo bs=1 seek=10000 count=1 conv=notrunc && dd if=/dev/zero of=foo bs=1 seek=30000 count=1 conv=notrunc
name := "c0"
diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go
index b335456..e4684df 100644
--- a/tests/reverse/correctness_test.go
+++ b/tests/reverse/correctness_test.go
@@ -295,6 +295,23 @@ func TestSeekData(t *testing.T) {
f.Close()
}
+// newWorkdir creates a new empty dir in dirA and returns the full path to it along
+// with the corresponding encrypted path in dirB
+func newWorkdir(t *testing.T) (workdirA, workdirB string) {
+ workdirA = dirA + "/" + t.Name()
+ if err := os.Mkdir(workdirA, 0700); err != nil {
+ t.Fatal(err)
+ }
+ // Find workdir in dirB (=encrypted view)
+ var st syscall.Stat_t
+ if err := syscall.Stat(workdirA, &st); err != nil {
+ t.Fatal(err)
+ }
+ workdirB = dirB + "/" + findIno(dirB, st.Ino)
+ t.Logf("newWorkdir: workdirA=%q workdirB=%q", workdirA, workdirB)
+ return
+}
+
// gocryptfs.longname.*.name of hardlinked files should not appear hardlinked (as the
// contents are different).
//
@@ -308,28 +325,22 @@ func TestHardlinkedLongname(t *testing.T) {
t.Skip()
}
- workdir := dirA + "/" + t.Name()
- if err := os.Mkdir(workdir, 0700); err != nil {
- t.Fatal(err)
- }
- long1 := workdir + "/" + strings.Repeat("x", 200)
+ workdirA, workdirB := newWorkdir(t)
+
+ long1 := workdirA + "/" + strings.Repeat("x", 200)
if err := ioutil.WriteFile(long1, []byte("hello"), 0600); err != nil {
t.Fatal(err)
}
- long2 := workdir + "/" + strings.Repeat("y", 220)
- if err := syscall.Link(long1, long2); err != nil {
+ var long1_stat syscall.Stat_t
+ if err := syscall.Stat(long1, &long1_stat); err != nil {
t.Fatal(err)
}
-
- // Find workdir in encrypted view
- var st syscall.Stat_t
- if err := syscall.Stat(workdir, &st); err != nil {
+ long2 := workdirA + "/" + strings.Repeat("y", 220)
+ if err := syscall.Link(long1, long2); err != nil {
t.Fatal(err)
}
- cWorkdir := dirB + "/" + findIno(dirB, st.Ino)
- t.Logf("workdir=%q cWorkdir=%q", workdir, cWorkdir)
- matches, err := filepath.Glob(cWorkdir + "/gocryptfs.longname.*.name")
+ matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
if err != nil {
t.Fatal(err)
}
@@ -352,3 +363,61 @@ func TestHardlinkedLongname(t *testing.T) {
t.Errorf("Files %q have the same inode number - that's wrong!", matches)
}
}
+
+// With inode number reuse and hard links, we could have returned
+// wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
+// (https://github.com/rfjakob/gocryptfs/issues/802).
+//
+// Now that this is fixed, ensure that rsync and similar tools pick up the new
+// correct files by advancing mtime and ctime by 10 seconds, which should be more
+// than any filesytems' timestamp granularity (FAT32 has 2 seconds).
+func TestMtimePlus10(t *testing.T) {
+ if plaintextnames {
+ t.Skip("plaintextnames mode does not have virtual files")
+ }
+
+ workdirA, workdirB := newWorkdir(t)
+
+ long := workdirA + "/" + strings.Repeat("x", 200)
+ if err := os.WriteFile(long, nil, 0600); err != nil {
+ t.Fatal(err)
+ }
+ long_stat, err := os.Stat(long)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ workdirA_stat, err := os.Stat(workdirA)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Find and check gocryptfs.longname.*.name
+ matches, err := filepath.Glob(workdirB + "/gocryptfs.longname.*.name")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(matches) != 1 {
+ t.Fatal(matches)
+ }
+ name_stat, err := os.Stat(matches[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if name_stat.ModTime().Unix() != long_stat.ModTime().Unix()+10 {
+ t.Errorf(".name file should show mtime+10")
+ }
+
+ // Check gocryptfs.diriv
+ if deterministic_names {
+ // No gocryptfs.diriv
+ return
+ }
+ diriv_stat, err := os.Stat(workdirB + "/gocryptfs.diriv")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diriv_stat.ModTime().Unix() != workdirA_stat.ModTime().Unix()+10 {
+ t.Errorf("diriv file should show mtime+10")
+ }
+}
diff --git a/tests/root_test/issue893_test.go b/tests/root_test/issue893_test.go
new file mode 100644
index 0000000..6ad8e6d
--- /dev/null
+++ b/tests/root_test/issue893_test.go
@@ -0,0 +1,99 @@
+//go:build linux
+
+package root_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+// gocryptfs v2.5.0 upgraded x/sys/unix and we found out that, since
+// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51 ,
+// unix.Setreuid() and friends now affect the whole process instead of only the
+// current thread, breaking allow_other: https://github.com/rfjakob/gocryptfs/issues/893
+//
+// Let's not have this happen again by testing it here.
+func TestConcurrentUserOps(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ var wg sync.WaitGroup
+
+ oneStressor := func(tid int) {
+ defer wg.Done()
+ err := asUser(10000+tid, 20000+tid, nil, func() (err error) {
+ for i := 0; i < 100; i++ {
+ d := fmt.Sprintf("%s/tid%d.i%d/foo/bar/baz", test_helpers.DefaultPlainDir, tid, i)
+ if err = os.MkdirAll(d, 0700); err != nil {
+ return
+ }
+ if err = ioutil.WriteFile(d+"/foo", nil, 0400); err != nil {
+ return
+ }
+ if err = ioutil.WriteFile(d+"/bar", []byte("aaaaaaaaaaaaaaaaaaaaa"), 0400); err != nil {
+ return
+ }
+ if err = syscall.Unlink(d + "/foo"); err != nil {
+ return
+ }
+ if err = os.Mkdir(d+"/foo", 0700); err != nil {
+ return
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ }
+
+ threads := 4
+ wg.Add(threads)
+ for tid := 0; tid < threads; tid++ {
+ go oneStressor(tid)
+ }
+ wg.Wait()
+}
+
+// Test that our root_test.asUser function works as expected under concurrency by
+// similating a long-runnig operation with sleep(10ms).
+// https://github.com/rfjakob/gocryptfs/issues/893
+func TestAsUserSleep(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+
+ var wg sync.WaitGroup
+ f := func(euid_want int) error {
+ euid_have := syscall.Geteuid()
+ if euid_want != euid_have {
+ return fmt.Errorf("wrong euid: want=%d have=%d", euid_want, euid_have)
+ }
+ time.Sleep(10 * time.Millisecond)
+ euid_have2 := syscall.Geteuid()
+ if euid_want != euid_have2 {
+ return fmt.Errorf("wrong euid: want=%d have2=%d", euid_want, euid_have2)
+ }
+ return nil
+ }
+ threads := 100
+ wg.Add(threads)
+ for i := 0; i < threads; i++ {
+ go func(i int) {
+ defer wg.Done()
+ err := asUser(10000+i, 20000+i, nil, func() error { return f(10000 + i) })
+ if err != nil {
+ t.Error(err)
+ }
+ }(i)
+ }
+ wg.Wait()
+}
diff --git a/tests/root_test/main_test.go b/tests/root_test/main_test.go
new file mode 100644
index 0000000..d6d5cc4
--- /dev/null
+++ b/tests/root_test/main_test.go
@@ -0,0 +1,20 @@
+//go:build linux
+
+package root_test
+
+import (
+ "os"
+ "testing"
+
+ "github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
+)
+
+func TestMain(m *testing.M) {
+ test_helpers.ResetTmpDir(true)
+ os.Chmod(test_helpers.DefaultCipherDir, 0777)
+ test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, "-zerokey", "-allow_other")
+ r := m.Run()
+ test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
+ os.RemoveAll(test_helpers.TmpDir)
+ os.Exit(r)
+}
diff --git a/tests/root_test/root_test.go b/tests/root_test/root_test.go
index 23b44d0..e432ce0 100644
--- a/tests/root_test/root_test.go
+++ b/tests/root_test/root_test.go
@@ -1,5 +1,4 @@
//go:build linux
-// +build linux
// Package root_test contains tests that need root
// permissions to run
@@ -17,8 +16,6 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
- "golang.org/x/sys/unix"
-
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)
@@ -26,36 +23,23 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- err := unix.Setgroups(supplementaryGroups)
+ err := syscallcompat.Setgroups(supplementaryGroups)
if err != nil {
return err
}
- defer func() {
- err = unix.Setgroups(nil)
- if err != nil {
- panic(err)
- }
- }()
- err = unix.Setregid(-1, gid)
+ defer syscallcompat.SetgroupsPanic(nil)
+
+ err = syscallcompat.Setregid(-1, gid)
if err != nil {
return err
}
- defer func() {
- err = unix.Setregid(-1, 0)
- if err != nil {
- panic(err)
- }
- }()
- err = unix.Setreuid(-1, uid)
+ defer syscallcompat.SetregidPanic(-1, 0)
+
+ err = syscallcompat.Setreuid(-1, uid)
if err != nil {
return err
}
- defer func() {
- err = unix.Setreuid(-1, 0)
- if err != nil {
- panic(err)
- }
- }()
+ defer syscallcompat.SetreuidPanic(-1, 0)
ret := f()
@@ -67,13 +51,13 @@ func asUser(uid int, gid int, supplementaryGroups []int, f func() error) error {
//
// How to check:
// ps -o tid,pid,euid,ruid,suid,egid,rgid,sgid,cmd -eL
- err = unix.Setresuid(0, 0, 0)
- if err != nil {
- panic(err)
+ _, _, errno := syscall.RawSyscall(syscall.SYS_SETRESUID, uintptr(0), uintptr(0), uintptr(0))
+ if errno != 0 {
+ panic(errno)
}
- err = unix.Setresgid(0, 0, 0)
- if err != nil {
- panic(err)
+ _, _, errno = syscall.RawSyscall(syscall.SYS_SETRESGID, uintptr(0), uintptr(0), uintptr(0))
+ if errno != 0 {
+ panic(errno)
}
return ret
@@ -83,16 +67,11 @@ func TestSupplementaryGroups(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", "-extpass=echo test")
- defer test_helpers.UnmountPanic(pDir)
// We need an unrestricted umask
syscall.Umask(0000)
- dir1 := pDir + "/dir1"
+ dir1 := test_helpers.DefaultPlainDir + "/dir1"
err := os.Mkdir(dir1, 0770)
if err != nil {
t.Fatal(err)
diff --git a/tests/stress_tests/fsstress-gocryptfs.bash b/tests/stress_tests/fsstress-gocryptfs.bash
index 30ea456..43b4222 100755
--- a/tests/stress_tests/fsstress-gocryptfs.bash
+++ b/tests/stress_tests/fsstress-gocryptfs.bash
@@ -27,11 +27,11 @@ MYNAME=$(basename "$0")
source ../fuse-unmount.bash
# fsstress binary
-FSSTRESS=/opt/fuse-xfstests/ltp/fsstress
+FSSTRESS=/var/lib/xfstests/ltp/fsstress
if [[ ! -x $FSSTRESS ]]
then
echo "$MYNAME: fsstress binary not found at $FSSTRESS"
- echo "Please clone and compile https://github.com/rfjakob/fuse-xfstests"
+ echo "Please build and \"sudo make install\" git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git"
exit 1
fi
@@ -62,9 +62,8 @@ if [[ $MYNAME = fsstress-loopback.bash ]]; then
$GOPATH/bin/loopback $OPTS "$MNT" "$DIR" &
disown
elif [[ $MYNAME = fsstress-gocryptfs.bash ]]; then
- echo "Recompile gocryptfs"
- cd "$GOPATH/src/github.com/rfjakob/gocryptfs"
- ./build.bash # also prints the version
+ echo "$MYNAME: using gocryptfs at $GOPATH/bin/gocryptfs"
+ $GOPATH/bin/gocryptfs --version
$GOPATH/bin/gocryptfs -q -init -extpass "echo test" -scryptn=10 "$DIR"
$GOPATH/bin/gocryptfs -q -extpass "echo test" -nosyslog -fusedebug="$DEBUG" "$DIR" "$MNT"
elif [[ $MYNAME = fsstress-encfs.bash ]]; then
diff --git a/tests/stress_tests/parallel_cp.sh b/tests/stress_tests/parallel_cp.sh
index cd08d31..f113248 100755
--- a/tests/stress_tests/parallel_cp.sh
+++ b/tests/stress_tests/parallel_cp.sh
@@ -2,15 +2,16 @@
#
# Simplified xfstests generic/273
#
-# Fails with
+# Used to fail with
#
# cp: cannot create regular file 'sub_49/file_773': No such file or directory
#
# If you cannot reproduce, try running this in the background:
#
-# while sleep 0.1 ; do echo 3 > /proc/sys/vm/drop_caches ; done"
+# while sleep 0.1 ; do echo 3 > /proc/sys/vm/drop_caches ; done
#
# See https://github.com/rfjakob/gocryptfs/issues/322 for details.
+# Fixed by https://github.com/hanwen/go-fuse/commit/d0fca860a5759d17592becfa1b8e5b1bd354b24a .
if [[ -z $TMPDIR ]]; then
TMPDIR=/var/tmp
@@ -24,6 +25,9 @@ source ../fuse-unmount.bash
# Set the GOPATH variable to the default if it is empty
GOPATH=$(go env GOPATH)
+echo "$MYNAME: using gocryptfs at $GOPATH/bin/gocryptfs"
+$GOPATH/bin/gocryptfs --version
+
# Backing directory
DIR=$(mktemp -d "$TMPDIR/$MYNAME.XXX")
$GOPATH/bin/gocryptfs -q -init -extpass "echo test" -scryptn=10 "$DIR"
diff --git a/tests/stress_tests/pingpong.bash b/tests/stress_tests/pingpong.bash
index 4b8346e..d0d21b3 100755
--- a/tests/stress_tests/pingpong.bash
+++ b/tests/stress_tests/pingpong.bash
@@ -1,7 +1,8 @@
#!/bin/bash
#
# Mounts two gocryptfs filesystems, "ping" and "pong" and moves the
-# linux-3.0 kernel tree back and forth between them.
+# linux-3.0 kernel tree back and forth between them, checking integrity
+# using md5sum.
#
# When called as "pingpong-rsync.bash" it uses "rsync --remove-source-files"
# for moving the files, otherwise plain "mv".
@@ -29,6 +30,9 @@ mkdir "$PING.mnt" "$PONG.mnt"
# Just ignore unmount errors.
trap "set +e ; cd /tmp; fuse-unmount -z $PING.mnt ; fuse-unmount -z $PONG.mnt ; rm -rf $PING $PONG $PING.mnt $PONG.mnt" EXIT
+echo "$MYNAME: using gocryptfs at $(command -v gocryptfs)"
+gocryptfs --version
+
gocryptfs -q -init -extpass="echo test" -scryptn=10 "$PING"
gocryptfs -q -init -extpass="echo test" -scryptn=10 "$PONG"