Skip to content

[Bug]: Creating/renaming a system tag with a multibyte name >64 bytes throws 500 — byte-based substr() splits UTF-8 characters in SystemTagManager #63712

Description

@kaynemo

⚠️ This issue respects the following points: ⚠️

  • This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
  • This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
  • I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
  • I agree to follow Nextcloud's Code of Conduct.
  • I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.

Bug description

OC\SystemTag\SystemTagManager truncates tag names to 64 bytes using the
byte-based substr($tagName, 0, 64). When the tag name contains multibyte
UTF-8 characters (e.g. Cyrillic, 2 bytes per character) and the 64th byte
falls inside a character, the truncated string ends with a dangling lead
byte (e.g. \xD0) — invalid UTF-8. MariaDB/MySQL in strict mode then rejects
the INSERT with error 1366, which surfaces to the user as an HTTP 500 on
POST /remote.php/dav/systemtags from the web UI.

There are three affected call sites (line numbers from v34.0.3):

Line Method Code
139 getTag() $truncatedTagName = substr($tagName, 0, 64);
184 createTag() $truncatedTagName = substr($tagName, 0, 64);
249 updateTag() $truncatedNewName = substr($newName, 0, 64);

Two distinct user-visible consequences:

  1. Hard failure (500): when the byte-64 cut splits a multibyte character
    → SQLSTATE 22007 / MySQL error 1366, unhandled, returned as 500. Any
    Cyrillic tag name of 33+ characters can trigger this. Users see a generic
    error and typically retry several times (visible in our access logs).
  2. Silent corruption: when the cut happens to land on a character
    boundary, a mangled, mid-word-truncated tag is created without any warning
    (real examples from our instance: 8 ерий alongside the intended
    8 серий; Для платформ, триллер, черная комед). This also creates
    near-duplicate tags that fragment file/tag associations.

Root cause:

substr() counts bytes and cannot know about UTF-8 character boundaries.
Since the limit here is a byte budget (the column is varchar(64)),
the correct function is mb_strcut(), which enforces the same byte limit
but backs off to the nearest character boundary and never emits an invalid
sequence.

Note: mb_substr() is not a correct replacement — it counts characters,
so 64 Cyrillic characters would be 128 bytes and overflow the column.

Proposed fix:

Replace all three occurrences:

// getTag(), createTag():
$truncatedTagName = mb_strcut($tagName, 0, 64, 'UTF-8');

// updateTag():
$truncatedNewName = mb_strcut($newName, 0, 64, 'UTF-8');

We have been running this patch in production since 2026-08-27: the 500 is
gone, over-long names are truncated at a clean character boundary, and
ASCII-only behavior is unchanged (mb_strcut is byte-identical to substr
for single-byte input). Happy to submit this as a PR if the approach is
acceptable.

A possible follow-up (out of scope for this fix): silently truncating user
input at all is questionable — returning a proper 400 with a meaningful
message, or letting the WebDAV endpoint reject over-long names, might be
preferable UX. The mb_strcut change is the minimal correctness fix either
way.

Steps to reproduce

  1. Use MySQL/MariaDB with strict mode (default) and utf8mb4 (default;
    oc_systemtag is utf8mb4_bin — charset configuration is not the
    issue, the payload is genuinely invalid UTF-8 after truncation).
  2. Run:
 

# 63 ASCII bytes + one 2-byte Cyrillic letter => byte 64 splits "б"
   sudo -u www-data php occ tag:add "$(python3 -c 'print("a"*63 + "б")')" public

Equivalently via the web UI: create a tag whose name is 63 a characters
followed by б (or any Cyrillic name where character 33 starts at
byte 64, e.g. many real-world descriptive Cyrillic tag names).

Expected behavior

Tag is created with the name truncated at a character boundary (≤64 bytes,
no split character) — or, arguably better, the request is rejected with a
clear validation error. In no case a 500.

Actual behavior:
An exception occurred while executing a query:
SQLSTATE[22007]: Invalid datetime format: 1366
Incorrect string value: '\xD0' for column <db>.oc_systemtag.name at row 1


`occ tag:add` throws the exception; the web UI request
`POST /remote.php/dav/systemtags` returns HTTP 500.

### Nextcloud Server version

34

### Operating system

Debian/Ubuntu

### PHP engine version

PHP 8.3

### Web server

Apache (supported)

### Database engine version

MariaDB

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

Updated from a MINOR version (ex. 32.0.1 to 32.0.2)

### Are you using the Nextcloud Server Encryption module?

None

### What user-backends are you using?

- [x] Default user-backend _(database)_
- [ ] LDAP/ Active Directory
- [ ] SSO - SAML
- [ ] Other

### Configuration report

```json
{
    "system": {
        "serverid": "1",
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "allow_local_remote_servers": true,
        "trusted_domains": [
            "10.0.5.79",
            "cd.m-pr.tv",
            "localhost",
            "10.0.4.72",
            "localhost",
            "talk.m-pr.tv"
        ],
        "push_server_url": "https:\/\/cd.m-pr.tv\/push",
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "mysql",
        "version": "34.0.3.2",
        "overwrite.cli.url": "https:\/\/cd.m-pr.tv",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "mysql.utf8mb4": true,
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "maintenance": false,
        "memcache.local": "\\OC\\Memcache\\APCu",
        "memcache.distributed": "\\OC\\Memcache\\Redis",
        "redis": {
            "host": "***REMOVED SENSITIVE VALUE***",
            "port": 6379
        },
        "memcache.locking": "\\OC\\Memcache\\Redis",
        "theme": "",
        "loglevel": 2,
        "overwriteprotocol": "https",
        "mail_smtpmode": "smtp",
        "mail_sendmailmode": "smtp",
        "mail_from_address": "***REMOVED SENSITIVE VALUE***",
        "mail_domain": "***REMOVED SENSITIVE VALUE***",
        "mail_smtphost": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpport": "587",
        "default_locale": "ru_RU",
        "default_phone_region": "RU",
        "app_install_overwrite": {
            "0": "documentserver_community",
            "2": "files_rightclick",
            "3": "metadata",
            "4": "files_archive"
        },
        "enable_previews": true,
        "preview_libreoffice_path": "\/usr\/bin\/libreoffice",
        "apps_paths": [
            {
                "path": "\/var\/www\/html\/nextcloud\/apps",
                "url": "\/apps",
                "writable": false
            },
            {
                "path": "\/var\/www\/html\/nextcloud\/custom_apps",
                "url": "\/custom_apps",
                "writable": true
            }
        ],
        "mail_smtpauth": true,
        "mail_smtpname": "***REMOVED SENSITIVE VALUE***",
        "mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
        "maintenance_window_start": 22,
        "collabora\/code": "https:\/\/cd.m-pr.tv",
        "fileuploadnew.chunk_size": "1G",
        "fileuploadnew.max_filesize": "25G",
        "memory_limit": "26G",
        "timeout": 36000,
        "enable_push": true,
        "has_internet_connection": true,
        "notify_push_base_endpoint": "https:\/\/cd.m-pr.tv\/push",
        "overwritewebroot": "",
        "mail_smtpauthtype": "LOGIN",
        "mail_smtpstreamoptions": {
            "ssl": {
                "allow_self_signed": false,
                "verify_peer": true,
                "verify_peer_name": true
            }
        },
        "logtimezone": "Europe\/Moscow",
        "mail_smtptimeout": 60,
        "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
        "overwritehost": "cd.m-pr.tv",
        "preview_imaginary_url": "***REMOVED SENSITIVE VALUE***",
        "enabledPreviewProviders": [
            "OC\\Preview\\Imaginary",
            "OC\\Preview\\ImaginaryPDF",
            "OC\\Preview\\TXT",
            "OC\\Preview\\MarkDown",
            "OC\\Preview\\OpenDocument",
            "OC\\Preview\\MSOffice2003",
            "OC\\Preview\\MSOfficeDoc",
            "OC\\Preview\\MSOffice2007",
            "OC\\Preview\\Photoshop",
            "OC\\Preview\\Font",
            "OC\\Preview\\MP3",
            "OC\\Preview\\Movie"
        ],
        "preview_max_x": 2048,
        "preview_max_y": 2048,
        "preview_concurrency_all": 8,
        "preview_concurrency_new": 4
    }
}

List of activated Apps

Enabled:
  - activity: 7.0.0
  - admin_audit: 1.24.0
  - admincockpit: 1.3.9
  - analytics: 6.8.0
  - announcementbanner: 2.7.0
  - appstore: 1.0.0
  - bruteforcesettings: 7.0.0
  - calendar: 6.5.4
  - cloud_federation_api: 1.18.0
  - comments: 1.24.0
  - contacts: 8.7.7
  - contactsinteraction: 1.15.0
  - dashboard: 7.14.0
  - dav: 1.40.0
  - deck: 1.18.3
  - external: 9.0.1
  - federatedfilesharing: 1.24.0
  - federation: 1.24.0
  - files: 2.6.0
  - files_accesscontrol: 5.0.0
  - files_automatedtagging: 5.0.0
  - files_downloadlimit: 5.2.0
  - files_external: 1.26.0
  - files_lock: 34.0.1
  - files_pdfviewer: 7.0.0-dev.0
  - files_reminders: 1.7.0
  - files_retention: 5.0.0
  - files_sharing: 1.26.0
  - files_trashbin: 1.24.0
  - files_versions: 1.27.0
  - flow_notifications: 5.0.0
  - forms: 5.3.5
  - guests: 4.9.0
  - logcleaner: 1.6.3
  - logreader: 7.0.0
  - lookup_server_connector: 1.22.0
  - nextcloud_announcements: 6.0.0
  - notes: 6.0.2
  - notifications: 7.0.0-dev.1
  - notify_push: 1.4.0
  - oauth2: 1.22.0
  - onlyoffice: 10.1.2
  - password_policy: 6.0.0-dev.0
  - previewgenerator: 5.14.0
  - privacy: 6.0.0-dev.1
  - profile: 1.3.0
  - provisioning_api: 1.24.0
  - recommendations: 7.0.0
  - related_resources: 5.0.0-dev.0
  - serverinfo: 6.0.0
  - settings: 1.17.0
  - sharebymail: 1.24.0
  - spreed: 24.0.4
  - support: 6.0.0
  - survey_client: 6.0.0-dev.0
  - systemtags: 1.24.0
  - tables: 2.3.0
  - tasks: 0.18.1
  - text: 8.0.0
  - theming: 2.9.0
  - thesearchpage: 2.4.5
  - 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
  - workflow_script: 5.0.0
  - workflowengine: 2.16.0
Disabled:
  - app_api: 34.0.0 (installed 32.0.0)
  - circles: 34.0.0 (installed 28.0.0)
  - encryption: 2.22.0
  - files_archive: 1.2.8 (installed 1.2.8)
  - files_zip: 3.0.0 (installed 3.0.0)
  - firstrunwizard: 7.0.0-dev.0 (installed 2.18.0)
  - localpdf: 1.0.0 (installed 1.0.0)
  - metadata: 0.24.0 (installed 0.24.0)
  - office: 1.0.0 (installed 1.0.0)
  - photos: 7.0.0 (installed 2.4.0)
  - side_menu: 6.0.1 (installed 6.0.1)
  - socialsharing_email: 4.1.0 (installed 4.1.0)
  - socialsharing_telegram: 4.1.0 (installed 4.1.0)
  - suspicious_login: 12.0.0-dev.0
  - testing: 1.23.0
  - twofactor_nextcloud_notification: 8.0.0
  - user_ldap: 1.25.0

Nextcloud Signing status

Nextcloud Logs

### Nextcloud Logs

{"Exception":"OC\\DB\\Exceptions\\DbalException","Message":"An exception occurred while executing a query: SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect string value: '\\xD0' for column `CloudDB`.`oc_systemtag`.`name` at row 1","Code":1366,"Trace":"#8 /var/www/html/nextcloud/lib/private/SystemTag/SystemTagManager.php(195): OC\\DB\\QueryBuilder\\QueryBuilder-&gt;executeStatement() #9 /var/www/html/nextcloud/apps/dav/lib/SystemTag/SystemTagPlugin.php(193): OC\\SystemTag\\SystemTagManager-&gt;createTag() #10 /var/www/html/nextcloud/apps/dav/lib/SystemTag/SystemTagPlugin.php(122): OCA\\DAV\\SystemTag\\SystemTagPlugin-&gt;createTag() #11 .../sabre/event/lib/WildcardEmitterTrait.php(89): OCA\\DAV\\SystemTag\\SystemTagPlugin-&gt;httpPost() ... #16 /var/www/html/nextcloud/remote.php(152)","CustomMessage":"Uncaught exception"}

Additional info

Additional info

  • The misleading "Invalid datetime format" prefix is just Doctrine's generic
    label for SQLSTATE class 22007; the actual error is MySQL 1366
    (invalid string value).
  • PostgreSQL and SQLite users are likely affected differently (PostgreSQL
    will also reject invalid UTF-8; SQLite may silently store the broken
    bytes), but I have only verified MariaDB.
  • The silent-corruption variant (cut lands on a character boundary) means
    instances that have had long multibyte tag names for a while probably
    already contain mangled duplicate tags in oc_systemtag.

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions