Skip to content

[Bug]: Chunked upload to object-store (S3) primary fails with 413 "Insufficient space" for any user with a finite quota, regardless of free space #61488

@0x7d8

Description

@0x7d8

⚠️ This issue respects the following points: ⚠️

Bug description

On primary object storage (S3) with a finite per-user quota, uploading any file large enough to be chunked fails with HTTP 413 and:

<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
  <s:exception>OCA\DAV\Connector\Sabre\Exception\EntityTooLarge</s:exception>
  <s:message>Insufficient space</s:message>
</d:error>

The rejection is false: in my case a ~250 MB upload was refused while the user had ~8.4 GiB of free quota (10 GiB quota, 1.57 GiB used, verified consistent via occ user:info and an occ files:scan that reported zero changes). Two things confirm it is not a real space limit:

  • Reducing the chunk size (files.chunked_upload.max_size) has no effect, even down to 500 KiB.
  • Setting the user's quota to Unlimited makes the upload succeed immediately.

Root cause

The chunked-upload parts are written to the uploads/ path of the quota-wrapped home storage. In lib/private/Files/Storage/Wrapper/Quota.php:

  1. free_space() intentionally exempts uploads/ and cache/ paths by returning the wrapped storage's value (lines 82-83).
  2. On object-store primary, ObjectStoreStorage::free_space() returns FileInfo::SPACE_UNLIMITED (-3) when no total size limit is configured.
  3. writeStream() then runs (around lines 232-236):
if ($size !== null) {
    if ($size < $free) {              // e.g. 262144000 < -3  => false
        return parent::writeStream($path, $stream, $size);
    } else {
        throw new NotEnoughSpaceException();   // <-- wrongly thrown
    }
}

File::convertToSabreException() converts NotEnoughSpaceException into EntityTooLarge (413, "Insufficient space").

writeStream() is the only write method in this wrapper that omits the $free < 0 short-circuit. The siblings handle the negative sentinel correctly:

  • file_put_contents() line 104: if ($free < 0 || strlen($data) < $free)
  • copy() line 117: if ($free < 0 || $this->getSize($source) < $free)
  • copyFromStorage() line 171 and moveFromStorage() line 184: same $free < 0 || guard
  • fopen() only wraps the quota stream when $free >= 0
    The DAV-level QuotaPlugin::checkQuota() also treats negative free space as allowed (if ($freeSpace === false || $freeSpace < 0) { return true; }). Only Quota::writeStream() treats a negative "unlimited / unknown / not-computed" free space as "no space left".

This explains every observed symptom:

  • 413 "Insufficient space" with the user far below quota: the comparison is against -3, not against real remaining quota.
  • Chunk size is irrelevant (500 KiB still fails): any positive size is < -3 => false.
  • Unlimited quota works: hasQuota() returns false (line 56), so writeStream() short-circuits to the wrapped storage before reaching the faulty branch.
  • Object-store specific: on local primary, Local::free_space() for the uploads path returns a real positive number, so $size < $free passes; only object stores return the negative SPACE_UNLIMITED sentinel here.

Proposed fix

Mirror the guard used by the sibling methods in Quota::writeStream():

if ($size !== null) {
    if ($free < 0 || $size < $free) {
        return parent::writeStream($path, $stream, $size);
    }
    throw new NotEnoughSpaceException();
}

Steps to reproduce

  1. Configure Nextcloud with S3 (or any object store) as primary storage, with no objectstore total size limit.
  2. Set a finite quota on a user (e.g. 10 GB) and leave most of it free.
  3. From the web UI, upload a file larger than the chunk threshold (e.g. 250 MB) into that user's own files.
  4. Upload fails with HTTP 413 OCA\DAV\Connector\Sabre\Exception\EntityTooLarge / "Insufficient space".
  5. Lower files.chunked_upload.max_size (e.g. to 5 MB, then 500 KB): still fails.
  6. Set the user's quota to Unlimited: upload succeeds.

Expected behavior

A chunked upload into a folder where the user is well within quota should succeed. A free_space() value of SPACE_UNLIMITED / SPACE_UNKNOWN / SPACE_NOT_COMPUTED (any negative value) must be treated as "allowed", consistent with the other Quota wrapper methods and with QuotaPlugin::checkQuota().

Nextcloud Server version

34

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.4

Web server

Apache (supported)

Database engine version

MariaDB

Is this bug present after an update or on a fresh install?

No response

Are you using the Nextcloud Server Encryption module?

Encryption is Disabled

What user-backends are you using?

  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other

Configuration report

{
    "system": {
        "htaccess.RewriteBase": "\/",
        "memcache.local": "\\OC\\Memcache\\APCu",
        "apps_paths": [
            {
                "path": "\/var\/www\/html\/apps",
                "url": "\/apps",
                "writable": false
            },
            {
                "path": "\/var\/www\/html\/custom_apps",
                "url": "\/custom_apps",
                "writable": true
            }
        ],
        "upgrade.disable-web": true,
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "objectstore": {
            "class": "\\OC\\Files\\ObjectStore\\S3",
            "arguments": {
                "bucket": "16bb3dac",
                "region": "eu-central-1",
                "hostname": "manually censored",
                "port": "443",
                "storageClass": "",
                "objectPrefix": "urn:oid:",
                "autocreate": true,
                "use_ssl": true,
                "use_path_style": true,
                "legacy_auth": false,
                "key": "***REMOVED SENSITIVE VALUE***",
                "secret": "***REMOVED SENSITIVE VALUE***"
            }
        },
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": [
            "cloud.aux-systemhaus.de"
        ],
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "mysql",
        "version": "34.0.0.12",
        "overwrite.cli.url": "https:\/\/cloud.aux-systemhaus.de",
        "overwriteprotocol": "https",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "mysql.utf8mb4": true,
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "mail_smtpmode": "smtp",
        "mail_sendmailmode": "smtp",
        "mail_from_address": "***REMOVED SENSITIVE VALUE***",
        "mail_domain": "***REMOVED SENSITIVE VALUE***",
        "mail_smtphost": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpport": "465",
        "mail_smtptimeout": 30,
        "mail_smtpauth": true,
        "mail_smtpname": "***REMOVED SENSITIVE VALUE***",
        "mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpsecure": "ssl",
        "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
        "loglevel": 0,
        "maintenance": false,
        "simpleSignUpLink.shown": false,
        "files.chunked_upload.max_size": 20971520
    }
}

List of activated Apps

Enabled:
  - activity: 7.0.0
  - app_api: 34.0.0
  - appstore: 1.0.0
  - bruteforcesettings: 7.0.0
  - calendar: 6.4.2
  - circles: 34.0.0
  - cloud_federation_api: 1.18.0
  - comments: 1.24.0
  - contacts: 8.7.0
  - contactsinteraction: 1.15.0
  - dashboard: 7.14.0
  - dav: 1.39.0
  - federatedfilesharing: 1.24.0
  - federation: 1.24.0
  - files: 2.6.0
  - files_downloadlimit: 5.2.0-dev.0
  - files_lock: 34.0.0
  - files_pdfviewer: 7.0.0-dev.0
  - files_reminders: 1.7.0
  - files_sharing: 1.26.0
  - files_trashbin: 1.24.0
  - files_versions: 1.27.0
  - firstrunwizard: 7.0.0-dev.0
  - forms: 5.3.1
  - logreader: 7.0.0
  - lookup_server_connector: 1.22.0
  - mail: 5.9.2
  - nextcloud_announcements: 6.0.0
  - notes: 6.0.0
  - notifications: 7.0.0-dev.1
  - oauth2: 1.22.0
  - office: 1.0.0
  - password_policy: 6.0.0-dev.0
  - photos: 7.0.0
  - privacy: 6.0.0-dev.1
  - profile: 1.3.0
  - provisioning_api: 1.24.0
  - recommendations: 7.0.0-dev.0
  - related_resources: 5.0.0-dev.0
  - richdocuments: 11.0.0
  - richdocumentscode: 26.4.104
  - serverinfo: 6.0.0
  - settings: 1.17.0
  - sharebymail: 1.24.0
  - spreed: 24.0.0
  - support: 6.0.0
  - survey_client: 6.0.0-dev.0
  - systemtags: 1.24.0
  - text: 8.0.0
  - theming: 2.9.0
  - twofactor_backupcodes: 1.23.0
  - twofactor_totp: 16.0.0
  - updatenotification: 1.24.0
  - user_status: 1.14.0
  - viewer: 7.0.0-dev.0
  - weather_status: 1.14.0
  - webhook_listeners: 1.6.0
  - workflowengine: 2.16.0
Disabled:
  - admin_audit: 1.24.0
  - drawio: 4.2.3 (installed 4.2.3)
  - encryption: 2.22.0
  - files_external: 1.26.0
  - secrets: 3.0.3 (installed 3.0.3)
  - suspicious_login: 12.0.0-dev.0
  - twofactor_nextcloud_notification: 8.0.0
  - user_ldap: 1.25.0

Nextcloud Signing status

Technical information
=====================
The following list covers which files have failed the integrity check. Please read
the previous linked documentation to learn more about the errors and how to fix
them.

Results
=======
- core
	- INVALID_HASH
		- core/js/mimetypelist.js
	- EXTRA_FILE
		- core/img/filetypes/drawio.svg
		- core/img/filetypes/dwb.svg

Raw output
==========
Array
(
    [core] => Array
        (
            [INVALID_HASH] => Array
                (
                    [core/js/mimetypelist.js] => Array
                        (
                            [expected] => cb945c6402e12d9e7d42d0359acf95a6e9a9b0c1f3bd8528f598a7fb1694e5ae34c80cf44ef6c8901eac1bfdd152de3315fc7eac007efee0f33f09ed3e518b6a
                            [current] => 09779c15c6ca51e1dd07931ef2708756cbdc9a9adb41cd6a12eb177f57e005652e1ed3e1df18d46f80a4ad30637a6075e7a9cc11f34b44aeb66b3631b8ba1840
                        )

                )

            [EXTRA_FILE] => Array
                (
                    [core/img/filetypes/drawio.svg] => Array
                        (
                            [expected] => 
                            [current] => 92e0974cf869bf8ab969c3442dc2b80d55fde36441d22924db74916a06b407520aa2a9dc39336f9157195ebede697ffac0e639360879255ab91932d406e1897d
                        )

                    [core/img/filetypes/dwb.svg] => Array
                        (
                            [expected] => 
                            [current] => 43731dd5f17a048112ea5109b40b02ec019b3ee2324385a0f448e3bd2264cb13dc160ab018d893f92f8e2f168fd09009b51578c8c6b97a02a1617c67ac087701
                        )

                )

        )

)

Nextcloud Logs

Additional info

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. Needs triagePending check for reproducibility or if it fits our roadmap34-feedbackbug

    Type

    No fields configured for Bug.

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions