#1106·oatpp

Undefined behaviour in Z__PROPERTY_OFFSET_*'

Author: 2rr0r4o3Created Aug 24, 2026Updated Aug 24, 2026

Summary

DTO_FIELD macro computes each field's offset by declaring a raw char buffer, casting it to the DTO type, and taking the address of a member through that pointer. No object is ever constructed in the buffer, so no vptr is written, yet every DTO is polymorphic. UBSan's vptr check reports it as dynamic-type-mismatch. Every field of every DTO emits one of these functions and they all run while the field-offset table is built on first construction of that DTO type, so any application that declares a DTO executes this.

Ithe computed offset is the intended value on every mainstream ABI, but it fires before anything else and, under -fno-sanitize-recover, aborts the process. It masks every other memory safety finding in this library.

Details

src/oatpp/codegen/dto/base_define.hpp:66-71 (an identical copy is at :98-103):

bash
66     static v_int64 Z__PROPERTY_OFFSET_##NAME() { \
67       char buffer[sizeof(Z__CLASS)]; \
68       auto obj = static_cast<Z__CLASS*>(reinterpret_cast<void*>(buffer)); \
69       auto ptr = &obj->NAME; \
70       return reinterpret_cast<v_int64>(ptr) - reinterpret_cast<v_int64>(buffer); \
71     } \

buffer is uninitialised automatic storage. Nothing constructs a Z__CLASS in it. Line 68 casts the array to Z__CLASS* and line 69 performs a class member access through that pointer.

Every DTO is polymorphic:

  • class BaseObject : public oatpp::base::Countable (src/oatpp/data/type/Object.hpp:49)
  • virtual ~Countable(); (src/oatpp/base/Countable.hpp:53). A member access on a polymorphic type requires the glvalue to designate an object of that type. Here it designates no object at all.

The call chain runs at DTO construction:

MinimalDto::MinimalDto()
  -> Z__PROPERTY_INITIALIZER_PROXY_str()
  -> Z__PROPERTY_INIT_str(...)
  -> Z__PROPERTY_SINGLETON_str()
  -> Z__PROPERTY_OFFSET_str()          <- member access on the fake object

Running oatpp's own test suite against a sanitizer build produces 167 reports, of which 161 are this one, covering every DTO the suite defines (TestDto, Test1, PrimitivesDto, PointDto, LineDto, and others).

The offset it computes is correct in practice. For a single inheritance polymorphic class a member offset does not depend on the vptr having been initialised, so on Itanium and MSVC the value matches what offsetof would give.

The reason to fix it first is different.

  • under -fno-sanitize-recover=all this aborts on the first DTO construction
    • An initial whole-suite sweep died after roughly ten of fifty-three tests for this reason.
  • other finding in this library has to be reproduced against a -fsanitize-recover=all build to get past it.

Fix:

bash
66     static v_int64 Z__PROPERTY_OFFSET_##NAME() { \
67       return static_cast<v_int64>(offsetof(Z__CLASS, NAME)); \
68     } \

offsetof on a non standard layout class is conditionally supported and warns under -Winvalid-offsetof. If that is unacceptable, the other UB free option is to measure the offset from a real, fully constructed instance.

PoC

poc_Z__PROPERTY_OFFSET.cpp:

cpp
class MinimalDto : public oatpp::DTO {
  DTO_INIT(MinimalDto, DTO)
  DTO_FIELD(String, str);
};

auto dto = MinimalDto::createShared();   // first construction builds the offset table
dto->str = "hello";

Build and run:

bash
clang++-22 -fsanitize=address,undefined,vptr -fno-sanitize-recover=all \
  -fno-omit-frame-pointer -frtti -g -O1 -std=c++17 -I<oatpp>/src \
  poc_Z__PROPERTY_OFFSET.cpp liboatpp.a -lpthread -o poc_Z__PROPERTY_OFFSET
UBSAN_OPTIONS=print_stacktrace=1:report_error_type=1 ./poc_Z__PROPERTY_OFFSET

UBSan output :

poc_Z__PROPERTY_OFFSET.cpp:49:3: runtime error: member access within address 0x... which does not
  point to an object of type 'Z__CLASS' (aka 'MinimalDto')
0x...: note: object has invalid vptr
    #0 in MinimalDto::Z__PROPERTY_OFFSET_str()
    #1 in MinimalDto::Z__PROPERTY_SINGLETON_str()
    #2 in MinimalDto::Z__PROPERTY_INIT_str(...)
    #3 in MinimalDto::Z__PROPERTY_INITIALIZER_PROXY_str()
    #4 in MinimalDto::MinimalDto()
SUMMARY: UndefinedBehaviorSanitizer: dynamic-type-mismatch

Impact

No memory safety issues occur in the mainstream ABI, but all users of the framework will experience undefined behavior. It’s not particularly unusual or dangerous, but it’s a bit of a hassle when using UBSAN...