Skip to content

Client uploads 0-byte files overwriting server content after power loss / hard reset during sync (ext4 zero-length recovery treated as local edit) #10547

Description

@MagTheRose

Bug description

After a hard reset (GPU driver freeze → power button) while the desktop client was downloading files during a sync run, the client re-uploaded 447 files with empty content (0 bytes), overwriting the server-side originals that had been created by other users the same morning.

Root cause chain, fully evidenced by client + server logs:

  1. Client starts a sync and downloads new remote files. The downloads complete and the sync journal DB records them (path, size, mtime, checksum). The sync DB is fsync'd; the downloaded file data is still in the page cache.
  2. Machine hard-resets (in our case an amdgpu freeze; any power loss reproduces this). ext4 (ordered mode) journal replay preserves the file metadata (name, mtime) but the unflushed data is lost → files exist locally with 0 bytes and the original mtime.
  3. On the next start, discovery compares DB vs. local: same mtime, different size — and classifies this as a local modification:
[ info nextcloud.sync.discovery discovery.cpp:1892 ] Processing "…/invoice.pdf" | (db/local/remote) | valid: true/true/db | mtime: 1786005528/1786005528/0 | size: 197806/0/0 | etag: "f459…"//"" | checksum: "SHA1:ed47…"//""
[ info nextcloud.sync.discovery discovery.cpp:1893 ] discovered "…/invoice.pdf" CSyncEnums::CSYNC_INSTRUCTION_SYNC OCC::SyncFileItem::Up
[ info nextcloud.sync.propagator.upload.v1 propagateuploadv1.cpp:132 ] "/…/invoice.pdf" "SHA1:da39a3ee5e6b4b0d3255bfef95601890afd80709"   ← SHA1 of the empty string
[ info nextcloud.sync.networkjob.put propagateupload.cpp:89 ] PUT of "https://…/invoice.pdf" FINISHED WITH STATUS "OK" 204
  1. The server-side originals (created by other users into a group folder) were overwritten with 0 bytes. Because the upload preserves the local mtime (X-OC-Mtime), the damage is hard to spot: the files keep their plausible timestamps and only the size reveals the loss.

Notably, the client's own log files from the crashed boot were also zeroed by the same ext4 behaviour — confirming the mechanism.

This is the "upload" sibling of #9280 (which covers the deletion case for queued-but-not-downloaded files). Here the files had completed downloading before the crash, so the client had full knowledge (size + checksum) that the local 0-byte content did not match what it had itself written moments earlier.

Expected behaviour

When discovery finds local size == 0 while the sync DB records a non-zero size with an unchanged mtime, the client should not silently propagate the empty file. Options:

  • verify the content checksum against the DB before treating it as a local edit (the DB checksum was available: SHA1:ed47… vs. actual SHA1:da39…),
  • treat it as a conflict (keep server version, create a conflict file), or
  • at minimum warn, as is done for the "all files deleted" case.

A general "many files shrank to 0 bytes since last run" heuristic (analogous to the mass-deletion warning) would protect against the whole class of post-crash corruption.

Environment

  • Desktop client: 33.0.2 (Linux, Tuxedo build mirall/33.0.2~tux1), sync folder on ext4 (ordered)
  • OS: Tuxedo OS (Ubuntu-based), kernel 6.17
  • Server: Nextcloud 32.0.13, target was a group folder shared with ~dozens of users
  • Trigger: amdgpu freeze → hard reset ~80 s after boot, mid-sync

Steps to reproduce

  1. Let the client fully download a batch of files during a sync run on ext4.
  2. Cut power before the page cache is flushed (or echo b > /proc/sysrq-trigger right after the downloads finish).
  3. Boot again; observe zero-length local files with preserved mtimes.
  4. Client uploads the 0-byte files, overwriting server content (no conflict, no warning).

Impact

Silent, hard-to-detect data loss for other users' files (group folder), recoverable only via server-side versioning. 447 files were overwritten in one sync run in our incident.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions