guard eetf_to_json reads against end of input#2659
Conversation
|
One of the tests failed for b87a7ba. @admin check logs https://download.copr.fedorainfracloud.org/results/packit/stephenberry-glaze-2659/fedora-rawhide-ppc64le/10619779-glaze/builder-live.log, packit dashboard https://dashboard.packit.dev/jobs/copr/3678800 and external service dashboard https://copr.fedorainfracloud.org/coprs/build/10619779/ |
|
One of the tests failed for b87a7ba. @admin check logs https://download.copr.fedorainfracloud.org/results/packit/stephenberry-glaze-2659/fedora-rawhide-s390x/10619779-glaze/builder-live.log, packit dashboard https://dashboard.packit.dev/jobs/copr/3678799 and external service dashboard https://copr.fedorainfracloud.org/coprs/build/10619779/ |
1 similar comment
|
One of the tests failed for b87a7ba. @admin check logs https://download.copr.fedorainfracloud.org/results/packit/stephenberry-glaze-2659/fedora-rawhide-s390x/10619779-glaze/builder-live.log, packit dashboard https://dashboard.packit.dev/jobs/copr/3678799 and external service dashboard https://copr.fedorainfracloud.org/coprs/build/10619779/ |
The list-tail and map-key guards added in this branch only prove one byte (the tag) is present, but get_type -> ei_get_type then reads a 2-4 byte length header off the raw pointer for header-bearing tags, over-reading past end when the tag is the final byte (verified with a guard page: SIGSEGV on a truncated improper tail or map key). Read the tag with a single-byte peek instead: a proper list tail only accepts ERL_NIL_EXT, and is_string/is_atom classify the raw map-key tag while term_to_json_value re-reads and bounds-checks the full key. Also fix decode_number casting the scratch value through the forwarding-reference type T instead of the decayed value type V; static_cast<T> forms a reference cast that fails to compile where int64_t is long long while long is the same width (macOS/LLP64). Matches the existing float branch. Add regression tests: empty buffer -> no_read_input; truncated map header and truncated key tag -> unexpected_end; list missing its NIL tail -> unexpected_end (with a valid-list counterpart); improper list tail -> array_element_not_found.
term_to_json_value reads each map key and the list tail through get_type, which forwards the raw iterator to ei_get_type with no bounds check. The list and tuple paths run through write_sequence, which calls invalid_end right after its advance, but the ERL_MAP_EXT branch and the ERL_LIST_EXT tail read skip that check, and eetf_to_json calls decode_version before confirming the buffer is non-empty. A truncated map whose header declares an arity but carries no entries, a list missing its NIL tail, or an empty buffer each leave the iterator at end and the next ei_* read runs past it.
Before, those inputs returned whatever error the over-read happened to produce (or faulted against an unmapped page); after, each ei read is gated on it < end and they return unexpected_end / no_read_input deterministically. The guards sit in eetf_to_json next to the reads they protect rather than in the shared ei wrappers, because ei_get_type and ei_decode_version must read the tag byte to do their work and cannot validate end on their own. read_eetf already rejects empty input upstream, so only the eetf_to_json entry point needed the version guard.