Skip to content

Possible out-of-bounds read in PeripheralWrapper::Unsubscribe #17

Description

@OvOhao

Possible out-of-bounds read in PeripheralWrapper::Unsubscribe

I found a possible OOB read (CWE-125) in PeripheralWrapper::Unsubscribe. The function copies a
fixed number of bytes (SIMPLEBLE_UUID_STR_LEN, == 37) out of the caller-supplied service and
characteristic strings with memcpy, regardless of the actual string length. When JavaScript passes
a UUID string shorter than 37 bytes (e.g. a 4-character 16-bit UUID like "180d"), serviceStr.c_str()
points to a heap buffer only strlen+1 bytes long, so the memcpy reads up to ~32 bytes past the end
of the std::string's storage into the destination simpleble_uuid_t. The over-read bytes are then
handed to the C library as a UUID.

File: lib/peripheral.cpp

Function: PeripheralWrapper::Unsubscribe

const std::string serviceStr = info[1].As<Napi::String>();
const std::string charStr = info[2].As<Napi::String>();
simpleble_uuid_t service;
simpleble_uuid_t characteristic;

memcpy(service.value, serviceStr.c_str(), SIMPLEBLE_UUID_STR_LEN);        // reads 37 bytes from serviceStr
memcpy(characteristic.value, charStr.c_str(), SIMPLEBLE_UUID_STR_LEN);    // reads 37 bytes from charStr
  1. simpleble_uuid_t.value is char[SIMPLEBLE_UUID_STR_LEN] with SIMPLEBLE_UUID_STR_LEN == 37
    (SimpleBLE types_c.h).
  2. serviceStr / charStr are arbitrary JS strings; only their type is checked, never their length.
  3. memcpy(..., ..., SIMPLEBLE_UUID_STR_LEN) unconditionally reads 37 source bytes. If the source
    string is shorter than 37 bytes, this reads past the end of the std::string heap buffer — an
    out-of-bounds read that can crash (ASAN/hardened allocators) or splice adjacent heap contents into
    the UUID passed to simpleble_peripheral_unsubscribe.
  4. The rest of this wrapper deliberately avoids this: the sibling C wrapper (lib/wrapper.cpp) uses
    bounded strncpy(..., SIMPLEBLE_UUID_STR_LEN) / strnlen(...), confirming Unsubscribe's raw
    memcpy with a fixed length is the outlier bug.

JS trigger:

const ble = require('node-simpleble'); // obtain a connected peripheral handle first
// short (16-bit) UUIDs -> c_str() buffers are ~5 bytes, memcpy reads 37
addon.simpleble_peripheral_unsubscribe(handle, '180d', '2a37');

Suggested fix: copy only the bytes that exist and NUL-terminate, e.g.
snprintf(service.value, SIMPLEBLE_UUID_STR_LEN, "%s", serviceStr.c_str()); or
strncpy(service.value, serviceStr.c_str(), SIMPLEBLE_UUID_STR_LEN - 1) with an explicit terminator,
mirroring the bounded copies already used in lib/wrapper.cpp.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions