Skip to content

Keep converted object keys alive while writing UBJSON and BJData - #5791

Merged
nlohmann merged 3 commits into
developfrom
fix/ubjson-converted-key-dangling
Oct 9, 2026
Merged

nlohmann merged 3 commits into
developfrom
fix/ubjson-converted-key-dangling

Conversation

@nlohmann

@nlohmann nlohmann commented Oct 9, 2026

Copy link
Copy Markdown
Owner

What

Since #5746, write_ubjson (recursive) and write_ubjson_iterative (used past detail::recursion_depth_limit(), #5781) write each object key with

string_t storage;
const string_t& key = sanitize_utf8_for_write(el.first, j, storage);

When object_t::key_type is not string_t but converts to it implicitly, el.first becomes a temporary string_t that is destroyed at the end of this statement. sanitize_utf8_for_write returns a reference to its argument in every case except a sanitized replace/ignore copy, so key then refers to a dead object. AddressSanitizer reports stack-use-after-scope in write_ubjson for to_ubjson and to_bjdata with such a key type.

Fix

  • New private alias binary_writer::object_key_string_t: const string_t& when key_type is string_t, string_t otherwise. Both UBJSON sites bind the key to it before sanitizing, so the default types get no extra copy.
  • A deleted template overload sanitize_utf8_for_write(const T&, ...) for any T other than string_t makes passing a converting key a compile error instead of a dangling reference. The doc comment explains this.

Tests

New test case in tests/src/unit-binary_utf8_error_handler.cpp using a local key type that converts to std::string. Keys are longer than the small-string buffer.

  • Well-formed keys under every error_handler, for UBJSON and BJData draft2/draft3, with all use_count/use_type combinations.
  • Ill-formed keys: strict throws, replace/ignore match dump()+parse(), keep passes the bytes through.
  • 200 nested objects, so the iterative writer is covered as well.

Verified locally with -fsanitize=address,undefined:

  • develop headers: the new test crashes with stack-use-after-scope in write_ubjson.
  • Iterative site alone: with only the iterative site left unfixed, the nesting section crashes in write_ubjson_iterative.
  • With the fix: all 490 assertions pass in C++11 (include/) and C++17 with JSON_DIAGNOSTICS=1 (single_include/).
  • Guard: reverting one site to sanitize_utf8_for_write(el.first, ...) fails to compile for the custom key type with "call to deleted member function", while json/ordered_json still compile.

Public API

No breaking changes. The alias and the deleted overload are private members of detail::binary_writer. All public entry points compile for exactly the same basic_json specializations as before; the only behavior change is that the affected custom key types no longer read freed memory.

🤖 Generated with Claude Code

Since #5746, write_ubjson and write_ubjson_iterative pass each object key
to sanitize_utf8_for_write and keep the returned reference. When
object_t::key_type is not string_t but converts to it, the argument is a
temporary that is destroyed at the end of the statement, and the
function returns a reference to it in every case but a sanitized copy,
so the key bytes are read from a dead object (AddressSanitizer:
stack-use-after-scope). Default json and ordered_json are unaffected.

Bind the key to a named object_key_string_t first: a reference when
key_type is string_t, so no copy is added there, and a converted copy
otherwise. A deleted overload of sanitize_utf8_for_write for anything
other than string_t turns a recurrence into a compile error.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@github-actions github-actions Bot added aspect: binary formats BSON, CBOR, MessagePack, UBJSON L tests labels Oct 9, 2026
converting_key::data() is only called when JSON_DIAGNOSTICS is enabled.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann nlohmann added this to the Release 3.13.0 milestone Oct 9, 2026
@nlohmann nlohmann added the 🚀 ready to merge Ready to merge - just waiting for CI to complete. label Oct 9, 2026
Suppress hicpp/modernize-use-equals-delete on the deleted
sanitize_utf8_for_write overload: it guards a private helper and must stay
private. Replace the C-style array in the new test with std::array.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann
nlohmann merged commit 374dfe4 into develop Oct 9, 2026
4 of 168 checks passed
@nlohmann
nlohmann deleted the fix/ubjson-converted-key-dangling branch October 9, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aspect: binary formats BSON, CBOR, MessagePack, UBJSON L 🚀 ready to merge Ready to merge - just waiting for CI to complete. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants