#2808·s3fs-fuse

urlDecode() silently produces NUL bytes on malformed input

Author: CarstenGrohmannCreated Feb 23, 2026Updated Feb 25, 2026

urlDecode() converts invalid %XX sequences (e.g. %GG) into NUL bytes instead of rejecting or preserving them. A truncated % at end of string is silently dropped. Malformed input can originate from buggy S3 clients, tampered object metadata, or an untrusted S3 backend.

Root Cause: The hex-decoding ternary in src/string_util.cpp:206,211 falls through to 0x00 for non-hex characters. A truncated % at end of string causes a silent break, dropping the incomplete sequence.

https://github.com/s3fs-fuse/s3fs-fuse/blob/58b3dd6b16731303e18cad29dd36138f68e44867/src/string_util.cpp#L195-L216

Reproduction:

urlDecode("%GG");  // returns "\x00", expected: error or literal "%GG"
urlDecode("a%2");  // returns "a", silently drops "%2"
urlDecode("a%");   // returns "a", silently drops "%"

Proposed Fix:

Two options — feedback welcome:

  1. Replace with curl_easy_unescape() — libcurl is already a required dependency and passes invalid sequences through literally. Needs a CURL* handle from the existing.
  2. Fix urlDecode() in place — add proper error signaling (bool return) and pass invalid %XX through literally. No handle needed, but keeps custom code to maintain.

Which approach would you prefer?