Skip to content

Lock shared storage rebase2#1019

Open
rfjakob wants to merge 19 commits into
masterfrom
LockSharedStorage_rebase2
Open

Lock shared storage rebase2#1019
rfjakob wants to merge 19 commits into
masterfrom
LockSharedStorage_rebase2

Conversation

@rfjakob

@rfjakob rfjakob commented Jul 16, 2026

Copy link
Copy Markdown
Owner

No description provided.

rfjakob added 17 commits July 16, 2026 19:56
Multiple host writing to the same empty file at the same time
could have overwritten each other's newly created file header,
leading to data corruption.

Fix the race by placing a byte-range lock on the file when
creating the file header.
This exercises the byte-range locks we just added.
This fails right now:

1 jakob@brikett:~/go/src/github.com/rfjakob/gocryptfs/tests/cluster$ go test -run TestOpenTruncate -v
=== RUN   TestOpenTruncate
    cluster_test.go:235: POSIX compliance issue: non-exlusive create failed with err=file exists
doWrite 2898550: corrupt header: ParseHeader: header is all-zero. Header hexdump: 000000000000000000000000000000000000
    cluster_test.go:240: iteration 1: WriteAt: write /var/tmp/gocryptfs-test-parent-1026/1896094179/TestOpenTruncate.4202105280.mnt2/foo: input/output error
 --- FAIL: TestOpenTruncate (0.10s)
FAIL
exit status 1
FAIL	github.com/rfjakob/gocryptfs/v2/tests/cluster	0.099s
1 jakob@brikett:~/go/src/github.com/rfjakob/gocryptfs/tests/cluster$ go test -run TestOpenTruncate -v
=== RUN   TestOpenTruncate
    cluster_test.go:235: POSIX compliance issue: non-exlusive create failed with err=file exists
doRead 2898565: corrupt block #0: cipher: message authentication failed
ino2898565 fh9: RMW read failed: errno=5
    cluster_test.go:240: iteration 8: WriteAt: write /var/tmp/gocryptfs-test-parent-1026/652691834/TestOpenTruncate.281532388.mnt1/foo: input/output error
 --- FAIL: TestOpenTruncate (0.09s)
FAIL
exit status 1
FAIL	github.com/rfjakob/gocryptfs/v2/tests/cluster	0.095s
With -sharedstorage, we keep the on-disk file header.
Other mounts may have the file ID cached so we cannot mess with it.

This makes TestOpenTruncate pass.
Scary. But explains why TestConcurrentCreate fails.

gocryptfs/tests/cluster$ go test -run TestPoCTornWrite
 --- FAIL: TestPoCTornWrite (0.00s)
    poc_test.go:210: iteration 214: inconsistent block: d6d6d6d6d6d6d6d6d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1
FAIL
As we must write complete ciphertext blocks (except at EOF), non-overlapping
plaintext writes can overlap in the ciphertext.
And because overlapping writes can turn the data into data soup (see
TestPoCTornWrite) we serialize them using fcntl locking.
Add the -sharedstorage flag to the mounts and make it more
severe by adjusting the I/O and file sizes to arbitrary unaligned
values.

It passes *most of the time*.

If it fails, it's like this, and the reason is that there is no
fcntl locks in the READ path yet.

gocryptfs/tests/cluster$ go test -run TestClusterConcurrentRW -v
=== RUN   TestClusterConcurrentRW
doRead 2895433: corrupt block #0: cipher: message authentication failed
doRead 2895433: corrupt block #0: cipher: message authentication failed
doRead 2895433: corrupt block #0: cipher: message authentication failed
    cluster_test.go:98: readThread iteration 9165: ReadAt failed: read /var/tmp/gocryptfs-test-parent-1026/3021427391/TestClusterConcurrentRW.366469887.mnt1/foo: input/output error
 --- FAIL: TestClusterConcurrentRW (0.40s)
FAIL
exit status 1
FAIL	github.com/rfjakob/gocryptfs/v2/tests/cluster	0.406s
With -sharedstorage, when we get a decryption error, we lock the
byte range and try again.

This makes concurrent R/W safe agains torn writes.

#754
Complain loudly when the underlying storage does not support
byte-range locks.

#754
Prevent reads and writes concurrent with the truncate operation. It's
racy on tmpfs and ext4 ( https://lore.kernel.org/all/18e9fa0f-ec31-9107-459c-ae1694503f87@gmail.com/t/ )
as evident by TestOpenTruncate test failures:

  === RUN   TestOpenTruncate
      cluster_test.go:209: POSIX compliance issue: non-exlusive create failed with err=file exists
  doRead 16384215: corrupt block #0: cipher: message authentication failed
  ino16384215 fh8: RMW read failed: errno=5
      cluster_test.go:214: iteration 1: WriteAt: write /tmp/gocryptfs-test-parent-1026/1358464214/TestOpenTruncate.1788296708.mnt2/foo: input/output error
  --- FAIL: TestOpenTruncate (0.06s)
  FAIL
  exit status 1
  FAIL	github.com/rfjakob/gocryptfs/v2/tests/cluster	7.880s

Relates-to: #56
@rfjakob
rfjakob force-pushed the LockSharedStorage_rebase2 branch from e090b83 to 6a09870 Compare July 16, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant