nextcloud / nextcloud/server

Wrong unencrypted_size for legacy base64 files: block size is read before the file header is applied

Open
#63,662 9 comments 8 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage 33-feedback bug feature: encryption (server-side) regression
Dominant language
PHP
Stars
36.9k
Forks
5.2k
Avg merge
2d 3h
Merged PRs (30d)
713

Description

fixUnencryptedSize() in lib/private/Files/Storage/Wrapper/Encryption.php calls getUnencryptedBlockSize() before it calls begin(). A file's encoding (binary or legacy base64) is only known once begin() has parsed the header, so the block size comes from the module default instead of from the file.

For files still stored in base64 that gives 8096 instead of 6072 bytes per block. The recalculated size ends up 4/3 too large, Content-Length is wrong, and the download breaks.

Code

apps/encryption/lib/Crypto/Encryption.php:

private bool $useLegacyBase64Encoding = false;

public function getUnencryptedBlockSize($signed = false) {
    if ($this->useLegacyBase64Encoding) {
        return $signed ? 6072 : 6126;
    }
    return $signed ? 8096 : 8168;
}

The flag is set in begin():

$this->useLegacyBase64Encoding = true;
if (isset($header['encoding'])) {
    $this->useLegacyBase64Encoding = $header['encoding'] !== Crypt::BINARY_ENCODING_FORMAT;
}

lib/private/Files/Storage/Wrapper/Encryption.php, in fixUnencryptedSize():

$signed = isset($header['signed']) && $header['signed'] === 'true';
$unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);

// ... last chunk is read ...

$encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);

The module is constructed fresh on every call, so the value is always the default and not something left over from another file. Same order in master.

Steps to reproduce
  1. Server-side encryption, with files written before binary encoding became the default (header has signed:true and no encoding:binary).
  2. Trigger a recalculation, for example with an unencrypted_size of 0 in oc_filecache. verifyUnencryptedSize() also triggers on a negative value, on a value equal to the on-disk size, and on a value larger than it.
  3. unencrypted_size is stored 4/3 too large.
  4. The download announces more Content-Length than there is data.
Expected

The block size follows the encoding recorded in the file's own header.

Fix

Call begin() before getUnencryptedBlockSize(). begin() does not touch the stream, it only resolves file key, cipher, version and encoding.

Notes from a live instance

34.0.3, per-user keys, 313775 encrypted files, 81277 of them base64. I recalculated the plaintext sizes from the block structure on disk, independently of Nextcloud. Every affected file was off by exactly 4/3, for example 383550 stored against 288422 real.

With per-user keys fixUnencryptedSize() cannot decrypt from cron at all and only logs Couldn't re-calculate unencrypted size for .... Those files stay at unencrypted_size = 0 and show as 0 B. The first access from a logged-in session then replaces the 0 with the inflated value instead of the correct one.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lib/private/Files/Storage/Wrapper/Encryption.php at fixUnencryptedSize(), then read begin() and getUnencryptedBlockSize() in apps/encryption/lib/Crypto/Encryption.php. Verify the header is applied before the block size is read, and confirm legacy base64 files use the recorded encoding so recalculated unencrypted_size and Content-Length match the actual data.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.