aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml16
-rw-r--r--Documentation/MANPAGE.md10
-rw-r--r--Documentation/file-format.md53
-rw-r--r--README.md26
-rwxr-xr-xbuild-without-openssl.bash5
-rwxr-xr-xbuild.bash3
-rw-r--r--cli_args.go5
-rwxr-xr-xcrossbuild.bash10
-rw-r--r--go.mod12
-rw-r--r--go.sum30
-rw-r--r--internal/fusefrontend/file.go2
-rw-r--r--internal/fusefrontend/file_dir_ops.go177
-rw-r--r--internal/fusefrontend/node.go32
-rw-r--r--internal/fusefrontend/node_api_check.go1
-rw-r--r--internal/fusefrontend/node_dir_ops.go86
-rw-r--r--internal/fusefrontend/node_helpers.go5
-rw-r--r--internal/fusefrontend/node_prepare_syscall.go3
-rw-r--r--internal/fusefrontend/root_node.go5
-rw-r--r--internal/fusefrontend_reverse/root_node.go6
-rw-r--r--internal/inomap/inomap.go11
-rw-r--r--internal/openfiletable/open_file_table.go6
-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
-rw-r--r--mount.go16
-rwxr-xr-xpackage-release-tarballs.bash2
-rwxr-xr-xtest.bash10
-rw-r--r--tests/defaults/getdents_linux.go9
-rw-r--r--tests/defaults/getdents_other.go11
-rw-r--r--tests/defaults/main_test.go49
-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
-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.go87
-rwxr-xr-xtests/stress_tests/fsstress-gocryptfs.bash11
-rwxr-xr-xtests/stress_tests/parallel_cp.sh8
-rwxr-xr-xtests/stress_tests/pingpong.bash6
45 files changed, 951 insertions, 420 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 c7a1c03..64bbaa8 100644
--- a/Documentation/MANPAGE.md
+++ b/Documentation/MANPAGE.md
@@ -208,6 +208,16 @@ Show all invalid filenames:
-badname '*'
+#### -context string
+Set the SELinux context. See mount(8) for details.
+
+This option was added for compatibility with xfstests which sets
+this option via `-o context="system_u:object_r:root_t:s0"`.
+
+Only works when mounting as root, otherwise you get this error from fusermount3:
+
+ fusermount3: unknown option 'context="system_u:object_r:root_t:s0"'
+
#### -ctlsock string
Create a control socket at the specified location. The socket can be
used to decrypt and encrypt paths inside the filesystem. When using
diff --git a/Documentation/file-format.md b/Documentation/file-format.md
index 7cce72c..7c2e2c8 100644
--- a/Documentation/file-format.md
+++ b/Documentation/file-format.md
@@ -1,43 +1,67 @@
File Format
===========
+Empty files are stored as empty files.
+
+Non-empty files contain a *Header* and one or more *Data blocks*.
+
Header
+------
2 bytes header version (big endian uint16, currently 2)
16 bytes file id
Data block, default AES-GCM mode
+--------------------------------
16 bytes GCM IV (nonce)
1-4096 bytes encrypted data
16 bytes GHASH
-Data block, AES-SIV mode (used in reverse mode, or when explicitly enabled with `-init -aessiv`)
+Overhead = (16+16)/4096 = 1/128 = 0.78125 %
+
+Data block, AES-SIV mode
+------------------------
+
+AES-SIV is used in reverse mode, or when explicitly enabled with `-init -aessiv`.
16 bytes nonce
16 bytes SIV
1-4096 bytes encrypted data
-Data block, XChaCha20-Poly1305 (enabled via `-init -xchacha`)
+Overhead = (16+16)/4096 = 1/128 = 0.78125 %
+
+Data block, XChaCha20-Poly1305
+------------------------------
+
+Enabled via `-init -xchacha`
24 bytes nonce
1-4096 bytes encrypted data
16 bytes Poly1305 tag
-Full block overhead (AES-GCM and AES-SIV mode) = 32/4096 = 1/128 = 0.78125 %
+Overhead = (24+16)/4096 = 0.98 %
-Full block overhead (XChaCha20-Poly1305 mode) = 40/4096 = \~1 %
+Examples
+========
-Example: 1-byte file, AES-GCM and AES-SIV mode
-----------------------------------------------
+0-byte file (all modes)
+-----------------------
+
+ (empty)
+
+Total: 0 bytes
+
+1-byte file, AES-GCM and AES-SIV mode
+-------------------------------------
Header 18 bytes
Data block 33 bytes
Total: 51 bytes
-Example: 5000-byte file, , AES-GCM and AES-SIV mode
----------------------------------------------------
+5000-byte file, , AES-GCM and AES-SIV mode
+------------------------------------------
Header 18 bytes
Data block 4128 bytes
@@ -45,19 +69,24 @@ Example: 5000-byte file, , AES-GCM and AES-SIV mode
Total: 5082 bytes
-Example: 1-byte file, XChaCha20-Poly1305 mode
-----------------------------------------------
+1-byte file, XChaCha20-Poly1305 mode
+------------------------------------
Header 18 bytes
Data block 41 bytes
Total: 59 bytes
-Example: 5000-byte file, XChaCha20-Poly1305 mode
-----------------------------------------------
+5000-byte file, XChaCha20-Poly1305 mode
+---------------------------------------
Header 18 bytes
Data block 4136 bytes
Data block 944 bytes
Total: 5098 bytes
+
+See Also
+========
+
+https://nuetzlich.net/gocryptfs/forward_mode_crypto/ / https://github.com/rfjakob/gocryptfs-website/blob/master/docs/forward_mode_crypto.md
diff --git a/README.md b/README.md
index 3776c01..63addd8 100644
--- a/README.md
+++ b/README.md
@@ -50,8 +50,14 @@ of macOS support but please create a new ticket if you hit a problem.
For Windows, an independent C++ reimplementation can be found here:
[cppcryptfs](https://github.com/bailey27/cppcryptfs)
-A standalone Python tool that can decrypt files & file names is here:
+Standalone tools:
+
[gocryptfs-inspect](https://github.com/slackner/gocryptfs-inspect)
+is Python tool that can decrypt files & file names without
+using FUSE.
+
+[gocryptfs-create-folder](https://codeberg.org/LGLQ/gocryptfs-create-folder)
+is a Python tool can encrypt a directory without using FUSE.
Installation
------------
@@ -195,8 +201,24 @@ 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 `-allow_other`
+* **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))**
diff --git a/build-without-openssl.bash b/build-without-openssl.bash
index d5dc218..e965951 100755
--- a/build-without-openssl.bash
+++ b/build-without-openssl.bash
@@ -3,8 +3,3 @@
cd "$(dirname "$0")"
CGO_ENABLED=0 source ./build.bash -tags without_openssl
-
-if ldd gocryptfs 2> /dev/null ; then
- echo "build-without-openssl.bash: error: compiled binary is not static"
- exit 1
-fi
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/cli_args.go b/cli_args.go
index 2e9e796..4101b86 100644
--- a/cli_args.go
+++ b/cli_args.go
@@ -35,9 +35,9 @@ type argContainer struct {
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
masterkey, mountpoint, cipherdir, cpuprofile,
- memprofile, ko, ctlsock, fsname, force_owner, trace string
+ memprofile, ko, ctlsock, fsname, force_owner, trace, context string
// FIDO2
- fido2 string
+ fido2 string
fido2_assert_options []string
// -extpass, -badname, -passfile can be passed multiple times
extpass, badname, passfile []string
@@ -211,6 +211,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
flagSet.StringVar(&args.fido2, "fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
+ flagSet.StringVar(&args.context, "context", "", "Set SELinux context (see mount(8) for details)")
flagSet.StringArrayVar(&args.fido2_assert_options, "fido2-assert-option", nil, "Options to be passed with `fido2-assert -t`")
// Exclusion options
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 0761a16..852ebb9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,14 +3,14 @@ module github.com/rfjakob/gocryptfs/v2
go 1.19
require (
- github.com/aperturerobotics/jacobsa-crypto v1.0.2
- github.com/hanwen/go-fuse/v2 v2.5.0
- github.com/moby/sys/mountinfo v0.6.2
+ github.com/aperturerobotics/jacobsa-crypto v1.1.0
+ github.com/hanwen/go-fuse/v2 v2.7.3-0.20250306214706-e3463465126a
+ github.com/moby/sys/mountinfo v0.7.2
github.com/pkg/xattr v0.4.9
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.0.0-20210817164053-32db794688a5
- golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
- golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
+ 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 13baeb9..937896a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,16 @@
-github.com/aperturerobotics/jacobsa-crypto v1.0.2 h1:tNvVy1rev9FagnOyBmTcI6d23FfNceG9IujZROTRtlc=
-github.com/aperturerobotics/jacobsa-crypto v1.0.2/go.mod h1:buWU1iY+FjIcfpb1aYfFJZfl07WlS7O30lTyC2iwjv8=
+github.com/aperturerobotics/jacobsa-crypto v1.1.0 h1:0hig54FMzU80OHrqSfqmj/W8HydRymVdz2K6D9Guffs=
+github.com/aperturerobotics/jacobsa-crypto v1.1.0/go.mod h1:buWU1iY+FjIcfpb1aYfFJZfl07WlS7O30lTyC2iwjv8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/hanwen/go-fuse/v2 v2.5.0 h1:JSJcwHQ1V9EGRy6QsosoLDMX6HaLdzyLOJpKdPqDt9k=
-github.com/hanwen/go-fuse/v2 v2.5.0/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
+github.com/hanwen/go-fuse/v2 v2.7.3-0.20250306214706-e3463465126a h1:Q+A/Qcj02oRubB/7+18SGNxAG/GtnoXxf0UKRJ2/ZE4=
+github.com/hanwen/go-fuse/v2 v2.7.3-0.20250306214706-e3463465126a/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI=
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA=
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw=
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 h1:BMb8s3ENQLt5ulwVIHVDWFHp8eIXmbfSExkvdn9qMXI=
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb h1:uSWBjJdMf47kQlXMwWEfmc864bA1wAC+Kl3ApryuG9Y=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
-github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
-github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
+github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -25,15 +24,14 @@ 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.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
-golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+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/file.go b/internal/fusefrontend/file.go
index 0e25de3..103c217 100644
--- a/internal/fusefrontend/file.go
+++ b/internal/fusefrontend/file.go
@@ -50,6 +50,8 @@ type File struct {
lastOpCount uint64
// Parent filesystem
rootNode *RootNode
+ // If this open file is a directory, dirHandle will be set, otherwise it's nil.
+ dirHandle *DirHandle
}
// NewFile returns a new go-fuse File instance based on an already-open file
diff --git a/internal/fusefrontend/file_dir_ops.go b/internal/fusefrontend/file_dir_ops.go
new file mode 100644
index 0000000..b69e7bc
--- /dev/null
+++ b/internal/fusefrontend/file_dir_ops.go
@@ -0,0 +1,177 @@
+package fusefrontend
+
+import (
+ "context"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+ "github.com/rfjakob/gocryptfs/v2/internal/configfile"
+ "github.com/rfjakob/gocryptfs/v2/internal/nametransform"
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+ "github.com/rfjakob/gocryptfs/v2/internal/tlog"
+)
+
+func (n *Node) OpendirHandle(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+ var fd int = -1
+ var fdDup int = -1
+ var file *File
+ var dirIV []byte
+ var ds fs.DirStream
+ rn := n.rootNode()
+
+ dirfd, cName, errno := n.prepareAtSyscallMyself()
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(dirfd)
+
+ // Open backing directory
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+
+ // NewLoopbackDirStreamFd gets its own fd to untangle Release vs Releasedir
+ fdDup, err = syscall.Dup(fd)
+
+ if err != nil {
+ errno = fs.ToErrno(err)
+ goto err_out
+ }
+
+ ds, errno = fs.NewLoopbackDirStreamFd(fdDup)
+ if errno != 0 {
+ goto err_out
+ }
+
+ if !rn.args.PlaintextNames {
+ // Read the DirIV from disk
+ dirIV, err = rn.nameTransform.ReadDirIVAt(fd)
+ if err != nil {
+ tlog.Warn.Printf("OpendirHandle: could not read %s: %v", nametransform.DirIVFilename, err)
+ errno = syscall.EIO
+ goto err_out
+ }
+ }
+
+ file, _, errno = NewFile(fd, cName, rn)
+ if errno != 0 {
+ goto err_out
+ }
+
+ file.dirHandle = &DirHandle{
+ ds: ds,
+ dirIV: dirIV,
+ isRootDir: n.IsRoot(),
+ }
+
+ return file, fuseFlags, errno
+
+err_out:
+ if fd >= 0 {
+ syscall.Close(fd)
+ }
+ if fdDup >= 0 {
+ syscall.Close(fdDup)
+ }
+ if errno == 0 {
+ tlog.Warn.Printf("BUG: OpendirHandle: err_out called with errno == 0")
+ errno = syscall.EIO
+ }
+ return nil, 0, errno
+}
+
+type DirHandle struct {
+ // Content of gocryptfs.diriv. nil if plaintextnames is used.
+ dirIV []byte
+
+ isRootDir bool
+
+ // fs.loopbackDirStream with a private dup of the file descriptor
+ ds fs.FileHandle
+}
+
+var _ = (fs.FileReleasedirer)((*File)(nil))
+
+func (f *File) Releasedir(ctx context.Context, flags uint32) {
+ // Does its own locking
+ f.dirHandle.ds.(fs.FileReleasedirer).Releasedir(ctx, flags)
+ // Does its own locking
+ f.Release(ctx)
+}
+
+var _ = (fs.FileSeekdirer)((*File)(nil))
+
+func (f *File) Seekdir(ctx context.Context, off uint64) syscall.Errno {
+ return f.dirHandle.ds.(fs.FileSeekdirer).Seekdir(ctx, off)
+}
+
+var _ = (fs.FileFsyncdirer)((*File)(nil))
+
+func (f *File) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
+ return f.dirHandle.ds.(fs.FileFsyncdirer).Fsyncdir(ctx, flags)
+}
+
+var _ = (fs.FileReaddirenter)((*File)(nil))
+
+// This function is symlink-safe through use of openBackingDir() and
+// ReadDirIVAt().
+func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno syscall.Errno) {
+ f.fdLock.RLock()
+ defer f.fdLock.RUnlock()
+
+ for {
+ entry, errno = f.dirHandle.ds.(fs.FileReaddirenter).Readdirent(ctx)
+ if errno != 0 || entry == nil {
+ return
+ }
+
+ cName := entry.Name
+ if cName == "." || cName == ".." {
+ // We want these as-is
+ return
+ }
+ if f.dirHandle.isRootDir && cName == configfile.ConfDefaultName {
+ // silently ignore "gocryptfs.conf" in the top level dir
+ continue
+ }
+ if f.rootNode.args.PlaintextNames {
+ return
+ }
+ if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename {
+ // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
+ continue
+ }
+ // Handle long file name
+ isLong := nametransform.LongNameNone
+ if f.rootNode.args.LongNames {
+ isLong = nametransform.NameType(cName)
+ }
+ if isLong == nametransform.LongNameContent {
+ cNameLong, err := nametransform.ReadLongNameAt(f.intFd(), cName)
+ if err != nil {
+ tlog.Warn.Printf("Readdirent: incomplete entry %q: Could not read .name: %v",
+ cName, err)
+ f.rootNode.reportMitigatedCorruption(cName)
+ continue
+ }
+ cName = cNameLong
+ } else if isLong == nametransform.LongNameFilename {
+ // ignore "gocryptfs.longname.*.name"
+ continue
+ }
+ name, err := f.rootNode.nameTransform.DecryptName(cName, f.dirHandle.dirIV)
+ if err != nil {
+ tlog.Warn.Printf("Readdirent: could not decrypt entry %q: %v",
+ cName, err)
+ f.rootNode.reportMitigatedCorruption(cName)
+ continue
+ }
+ // Override the ciphertext name with the plaintext name but reuse the rest
+ // of the structure
+ entry.Name = name
+ return
+ }
+}
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 687a386..95be48d 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -71,28 +71,54 @@ 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)
+ }
}
+ rn := n.rootNode()
+ var st *syscall.Stat_t
+ var err error
dirfd, cName, errno := n.prepareAtSyscallMyself()
+ // Hack for deleted fifos. As OPEN on a fifo does not reach
+ // the filesystem, we have no fd to access it. To make "cat" and git's
+ // t9300-fast-import.sh happy, we fake it as best as we can.
+ // https://github.com/rfjakob/gocryptfs/issues/929
+ if errno == syscall.ENOENT && n.StableAttr().Mode == syscall.S_IFIFO {
+ out.Mode = syscall.S_IFIFO
+ out.Ino = n.StableAttr().Ino
+ // cat looks at this to determine the optimal io size. Seems to be always 4096 for fifos.
+ out.Blksize = 4096
+ // We don't know what the owner was. Set it to nobody which seems safer
+ // than leaving it at 0 (root).
+ out.Owner.Gid = 65534
+ out.Owner.Uid = 65534
+ // All the other fields stay 0. This is what cat sees (strace -v log):
+ //
+ // fstat(1, {st_dev=makedev(0, 0x4d), st_ino=3838227, st_mode=S_IFIFO|000, st_nlink=0,
+ // st_uid=65534, st_gid=65534, st_blksize=4096, st_blocks=0, st_size=0, st_atime=0,
+ // st_atime_nsec=0, st_mtime=0, st_mtime_nsec=0, st_ctime=0, st_ctime_nsec=0}) = 0
+ goto out
+ }
if errno != 0 {
return
}
+
defer syscall.Close(dirfd)
- st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
+ st, err = syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return fs.ToErrno(err)
}
// Fix inode number
- rn := n.rootNode()
rn.inoMap.TranslateStat(st)
out.Attr.FromStat(st)
// Translate ciphertext size in `out.Attr.Size` to plaintext size
n.translateSize(dirfd, cName, &out.Attr)
+out:
if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner
}
diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go
index 0f60c74..37d4293 100644
--- a/internal/fusefrontend/node_api_check.go
+++ b/internal/fusefrontend/node_api_check.go
@@ -7,7 +7,6 @@ import (
// Check that we have implemented the fs.Node* interfaces
var _ = (fs.NodeGetattrer)((*Node)(nil))
var _ = (fs.NodeLookuper)((*Node)(nil))
-var _ = (fs.NodeReaddirer)((*Node)(nil))
var _ = (fs.NodeCreater)((*Node)(nil))
var _ = (fs.NodeMkdirer)((*Node)(nil))
var _ = (fs.NodeRmdirer)((*Node)(nil))
diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go
index 97e4caa..11ff83d 100644
--- a/internal/fusefrontend/node_dir_ops.go
+++ b/internal/fusefrontend/node_dir_ops.go
@@ -12,7 +12,6 @@ import (
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
- "github.com/rfjakob/gocryptfs/v2/internal/configfile"
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
@@ -159,91 +158,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
return ch, 0
}
-// Readdir - FUSE call.
-//
-// This function is symlink-safe through use of openBackingDir() and
-// ReadDirIVAt().
-func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
- parentDirFd, cDirName, errno := n.prepareAtSyscallMyself()
- if errno != 0 {
- return nil, errno
- }
- defer syscall.Close(parentDirFd)
-
- // Read ciphertext directory
- fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
- if err != nil {
- return nil, fs.ToErrno(err)
- }
- defer syscall.Close(fd)
- cipherEntries, specialEntries, err := syscallcompat.GetdentsSpecial(fd)
- if err != nil {
- return nil, fs.ToErrno(err)
- }
- // Get DirIV (stays nil if PlaintextNames is used)
- var cachedIV []byte
- rn := n.rootNode()
- if !rn.args.PlaintextNames {
- // Read the DirIV from disk
- cachedIV, err = rn.nameTransform.ReadDirIVAt(fd)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err)
- return nil, syscall.EIO
- }
- }
- // Decrypted directory entries
- var plain []fuse.DirEntry
- // Add "." and ".."
- plain = append(plain, specialEntries...)
- // Filter and decrypt filenames
- for i := range cipherEntries {
- cName := cipherEntries[i].Name
- if n.IsRoot() && cName == configfile.ConfDefaultName {
- // silently ignore "gocryptfs.conf" in the top level dir
- continue
- }
- if rn.args.PlaintextNames {
- plain = append(plain, cipherEntries[i])
- continue
- }
- if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename {
- // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
- continue
- }
- // Handle long file name
- isLong := nametransform.LongNameNone
- if rn.args.LongNames {
- isLong = nametransform.NameType(cName)
- }
- if isLong == nametransform.LongNameContent {
- cNameLong, err := nametransform.ReadLongNameAt(fd, cName)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
- cDirName, cName, err)
- rn.reportMitigatedCorruption(cName)
- continue
- }
- cName = cNameLong
- } else if isLong == nametransform.LongNameFilename {
- // ignore "gocryptfs.longname.*.name"
- continue
- }
- name, err := rn.nameTransform.DecryptName(cName, cachedIV)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
- cDirName, cName, err)
- rn.reportMitigatedCorruption(cName)
- continue
- }
- // Override the ciphertext name with the plaintext name but reuse the rest
- // of the structure
- cipherEntries[i].Name = name
- plain = append(plain, cipherEntries[i])
- }
-
- return fs.NewListDirStream(plain), 0
-}
-
// Rmdir - FUSE call.
//
// Symlink-safe through Unlinkat() + AT_REMOVEDIR.
diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go
index f5dfeb6..e8fca80 100644
--- a/internal/fusefrontend/node_helpers.go
+++ b/internal/fusefrontend/node_helpers.go
@@ -2,7 +2,6 @@ package fusefrontend
import (
"context"
- "sync/atomic"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
@@ -91,12 +90,12 @@ func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.Entry
if rn.args.SharedStorage || rn.quirks&syscallcompat.QuirkDuplicateIno1 != 0 {
// Make each directory entry a unique node by using a unique generation
// value - see the comment at RootNode.gen for details.
- gen = atomic.AddUint64(&rn.gen, 1)
+ gen = rn.gen.Add(1)
}
// Create child node
id := fs.StableAttr{
- Mode: uint32(st.Mode),
+ Mode: uint32(st.Mode), // go-fuse masks this with syscall.S_IFMT
Gen: gen,
Ino: st.Ino,
}
diff --git a/internal/fusefrontend/node_prepare_syscall.go b/internal/fusefrontend/node_prepare_syscall.go
index 2a4d6ab..9021350 100644
--- a/internal/fusefrontend/node_prepare_syscall.go
+++ b/internal/fusefrontend/node_prepare_syscall.go
@@ -1,7 +1,6 @@
package fusefrontend
import (
- "sync/atomic"
"syscall"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
@@ -24,7 +23,7 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy
// All filesystem operations go through here, so this is a good place
// to reset the idle marker.
- atomic.StoreUint32(&rn.IsIdle, 0)
+ rn.IsIdle.Store(false)
if n.IsRoot() && rn.isFiltered(child) {
return -1, "", syscall.EPERM
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index ac814ad..8464c5f 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -4,6 +4,7 @@ import (
"os"
"strings"
"sync"
+ "sync/atomic"
"syscall"
"time"
@@ -44,7 +45,7 @@ type RootNode struct {
// (uint32 so that it can be reset with CompareAndSwapUint32).
// When -idle was used when mounting, idleMonitor() sets it to 1
// periodically.
- IsIdle uint32
+ IsIdle atomic.Bool
// dirCache caches directory fds
dirCache dirCache
// inoMap translates inode numbers from different devices to unique inode
@@ -55,7 +56,7 @@ type RootNode struct {
// This makes each directory entry unique (even hard links),
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back.
- gen uint64
+ gen atomic.Uint64
// quirks is a bitmap that enables workaround for quirks in the filesystem
// backing the cipherdir
quirks uint64
diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go
index cb04151..9c2de28 100644
--- a/internal/fusefrontend_reverse/root_node.go
+++ b/internal/fusefrontend_reverse/root_node.go
@@ -21,7 +21,7 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
- "github.com/sabhiram/go-gitignore"
+ ignore "github.com/sabhiram/go-gitignore"
)
// RootNode is the root directory in a `gocryptfs -reverse` mount
@@ -50,7 +50,7 @@ type RootNode struct {
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back,
// like this one: https://github.com/rfjakob/gocryptfs/issues/802
- gen uint64
+ gen atomic.Uint64
// rootIno is the inode number that we report for the root node on mount
rootIno uint64
}
@@ -175,7 +175,7 @@ func (rn *RootNode) uniqueStableAttr(mode uint32, ino uint64) fs.StableAttr {
Ino: ino,
// Make each directory entry a unique node by using a unique generation
// value. Also see the comment at RootNode.gen for details.
- Gen: atomic.AddUint64(&rn.gen, 1),
+ Gen: rn.gen.Add(1),
}
}
diff --git a/internal/inomap/inomap.go b/internal/inomap/inomap.go
index b4dbf27..5749202 100644
--- a/internal/inomap/inomap.go
+++ b/internal/inomap/inomap.go
@@ -45,7 +45,7 @@ type InoMap struct {
// spill is used once the namespaces map is full
spillMap map[QIno]uint64
// spillNext is the next free inode number in the spill map
- spillNext uint64
+ spillNext atomic.Uint64
}
// New returns a new InoMap.
@@ -57,8 +57,9 @@ func New(rootDev uint64) *InoMap {
namespaceMap: make(map[namespaceData]uint16),
namespaceNext: 0,
spillMap: make(map[QIno]uint64),
- spillNext: spillSpaceStart,
}
+ m.spillNext.Store(spillSpaceStart)
+
if rootDev > 0 {
// Reserve namespace 0 for rootDev
m.namespaceMap[namespaceData{rootDev, 0}] = 0
@@ -74,10 +75,10 @@ var spillWarn sync.Once
// Reverse mode NextSpillIno() for gocryptfs.longname.*.name files where a stable
// mapping is not needed.
func (m *InoMap) NextSpillIno() (out uint64) {
- if m.spillNext == math.MaxUint64 {
- log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext)
+ if m.spillNext.Load() == math.MaxUint64 {
+ log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext.Load())
}
- return atomic.AddUint64(&m.spillNext, 1) - 1
+ return m.spillNext.Add(1) - 1
}
func (m *InoMap) spill(in QIno) (out uint64) {
diff --git a/internal/openfiletable/open_file_table.go b/internal/openfiletable/open_file_table.go
index ce8df76..420d070 100644
--- a/internal/openfiletable/open_file_table.go
+++ b/internal/openfiletable/open_file_table.go
@@ -29,7 +29,7 @@ type table struct {
// The variable is accessed without holding any locks so atomic operations
// must be used. It must be the first element of the struct to guarantee
// 64-bit alignment.
- writeOpCount uint64
+ writeOpCount atomic.Uint64
// Protects map access
sync.Mutex
// Table entries
@@ -85,13 +85,13 @@ type countingMutex struct {
func (c *countingMutex) Lock() {
c.RWMutex.Lock()
- atomic.AddUint64(&t.writeOpCount, 1)
+ t.writeOpCount.Add(1)
}
// WriteOpCount returns the write lock counter value. This value is incremented
// each time writeLock.Lock() on a file table entry is called.
func WriteOpCount() uint64 {
- return atomic.LoadUint64(&t.writeOpCount)
+ return t.writeOpCount.Load()
}
// CountOpenFiles returns how many entries are currently in the table
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/mount.go b/mount.go
index 0eaa3dd..5c0f064 100644
--- a/mount.go
+++ b/mount.go
@@ -13,7 +13,6 @@ import (
"runtime"
"runtime/debug"
"strings"
- "sync/atomic"
"syscall"
"time"
@@ -181,7 +180,7 @@ func idleMonitor(idleTimeout time.Duration, fs *fusefrontend.RootNode, srv *fuse
}
for {
// Atomically check whether the flag is 0 and reset it to 1 if so.
- isIdle := !atomic.CompareAndSwapUint32(&fs.IsIdle, 0, 1)
+ isIdle := !fs.IsIdle.CompareAndSwap(false, true)
// Any form of current or recent access resets the idle counter.
openFileCount := openfiletable.CountOpenFiles()
if !isIdle || openFileCount > 0 {
@@ -316,9 +315,13 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
}
}
}
- // If allow_other is set and we run as root, try to give newly created files to
- // the right user.
- if args.allow_other && os.Getuid() == 0 {
+ // If allow_other is set and we run as root, create files as the accessing
+ // user.
+ // Except when -force_owner is set, because in this case the user may
+ // not have write permissions. And the point of -force_owner is to map uids,
+ // so we want the files on the backing dir to get the uid the gocryptfs process
+ // is running as.
+ if args.allow_other && os.Getuid() == 0 && args._forceOwner == nil {
frontendArgs.PreserveOwner = true
}
@@ -469,6 +472,9 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {
} else if args.exec {
opts["exec"] = ""
}
+ if args.context != "" {
+ opts["context"] = args.context
+ }
// Add additional mount options (if any) after the stock ones, so the user has
// a chance to override them.
if args.ko != "" {
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/defaults/getdents_linux.go b/tests/defaults/getdents_linux.go
new file mode 100644
index 0000000..57956ce
--- /dev/null
+++ b/tests/defaults/getdents_linux.go
@@ -0,0 +1,9 @@
+package defaults
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+func getdents(fd int, buf []byte) (int, error) {
+ return unix.Getdents(fd, buf)
+}
diff --git a/tests/defaults/getdents_other.go b/tests/defaults/getdents_other.go
new file mode 100644
index 0000000..c0552a8
--- /dev/null
+++ b/tests/defaults/getdents_other.go
@@ -0,0 +1,11 @@
+//go:build !linux
+
+package defaults
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+func getdents(fd int, buf []byte) (int, error) {
+ return unix.Getdirentries(fd, buf, nil)
+}
diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go
index d0210e2..75a5dae 100644
--- a/tests/defaults/main_test.go
+++ b/tests/defaults/main_test.go
@@ -3,6 +3,7 @@ package defaults
import (
"bytes"
+ "fmt"
"io"
"io/ioutil"
"os"
@@ -506,3 +507,51 @@ func TestForceOwner(t *testing.T) {
t.Errorf("LOOKUP returned uid or gid != 1234: %#v", st)
}
}
+
+func TestSeekDir(t *testing.T) {
+ wd := test_helpers.DefaultPlainDir + "/" + t.Name()
+ err := os.Mkdir(wd, 0700)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < 10; i++ {
+ path := fmt.Sprintf(wd+"/%3d", i)
+ err = os.WriteFile(path, nil, 0600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ fd, err := syscall.Open(wd, syscall.O_DIRECTORY|syscall.O_RDONLY, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer syscall.Close(fd)
+
+ buf := make([]byte, 1000)
+ n, err := getdents(fd, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("1st getdents returned %d bytes", n)
+
+ n, err = getdents(fd, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("2nd getdents returned %d bytes", n)
+
+ _, err = unix.Seek(fd, 0, 0)
+ if err != nil {
+ t.Error(err)
+ }
+
+ n, err = getdents(fd, buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("3rd getdents (after seek) returned %d bytes", n)
+ if n == 0 {
+ t.Error("Seek did not have any effect")
+ }
+}
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/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..9ce1f4b 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)
@@ -400,3 +379,39 @@ func TestOverlay(t *testing.T) {
}
defer syscall.Unmount(ovlMnt, 0)
}
+
+// Check that mkdir and file create works with force_owner and runnung as root
+// https://github.com/rfjakob/gocryptfs/issues/783
+func TestRootForceOwner(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("must run as root")
+ }
+ cDir := test_helpers.InitFS(t)
+ pDir := cDir + ".mnt"
+ test_helpers.MountOrFatal(t, cDir, pDir, "-allow_other", "-extpass=echo test", "-force_owner=1234:1234")
+ defer test_helpers.UnmountPanic(pDir)
+
+ err := asUser(1234, 1234, nil, func() error {
+ return os.Mkdir(pDir+"/dir1", 0700)
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ err = asUser(1234, 1234, nil, func() error {
+ f, err := os.Create(pDir + "/file1")
+ if err == nil {
+ f.Close()
+ }
+ return err
+ })
+ if err != nil {
+ t.Error(err)
+ }
+ err = asUser(1234, 1234, nil, func() error {
+ sock := pDir + "/sock"
+ return syscall.Mknod(sock, syscall.S_IFSOCK|0600, 0)
+ })
+ if err != nil {
+ t.Errorf("mknod: %v", err)
+ }
+}
diff --git a/tests/stress_tests/fsstress-gocryptfs.bash b/tests/stress_tests/fsstress-gocryptfs.bash
index 30ea456..e6c3281 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
@@ -84,7 +83,7 @@ done
echo " ok: $MNT"
# Cleanup trap
-trap "kill %1 ; cd / ; fuse-unmount -z $MNT ; rm -rf $DIR $MNT" EXIT
+trap "echo ' cleaning up...' ; kill %1 ; cd / ; fuse-unmount -z $MNT ; rm -rf $DIR $MNT" EXIT
echo "Starting fsstress loop"
N=1
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"