Back to Blog
critical SEVERITY6 min read

How buffer overflow via unchecked memcpy offset happens in C++ PCL point cloud parsing and how to fix it

A critical out-of-bounds read vulnerability was discovered in `pcpatch_pcl.cpp` where the `readFloat` lambda performed a `memcpy` operation using an untrusted offset value without validating buffer boundaries. An attacker could craft malicious PCD point cloud files with large offset values to read memory outside allocated buffers, potentially leaking sensitive data or causing crashes. The fix adds a bounds check ensuring `f->offset + sizeof(float)` stays within the row buffer before any memory c

O
By Orbis AppSec
Published July 6, 2026Reviewed July 6, 2026

Answer Summary

This is a buffer over-read vulnerability (CWE-125) in C++ point cloud parsing code where `memcpy()` used an untrusted `PCLPointField.offset` value without bounds validation. Attackers could supply malicious PCD files with crafted offset values to read beyond allocated memory. The fix adds a bounds check comparing `f->offset + sizeof(float)` against `cloud2.point_step` before the `memcpy` call, throwing a `std::runtime_error` if the offset exceeds buffer boundaries.

Vulnerability at a Glance

cweCWE-125 (Out-of-bounds Read)
fixAdd bounds check before memcpy comparing offset against cloud2.point_step
riskMemory disclosure, information leakage, denial of service via crafted PCD files
languageC++
root causeMissing validation that PCLPointField.offset + sizeof(float) fits within row buffer
vulnerabilityOut-of-bounds read via unchecked memcpy offset

Introduction

In the PostgreSQL Pointcloud extension's PCL integration module, we discovered a critical out-of-bounds read vulnerability in contrib/pcl/src/pcpatch_pcl.cpp at line 400. The readFloat lambda function—used to extract floating-point values from point cloud row data—performed a memcpy operation using an offset value directly from PCLPointField structures without any validation that the read would stay within buffer boundaries.

This vulnerability is particularly dangerous because point cloud data files (PCD format) are often processed from external sources. An attacker who can supply a crafted PCD file with malicious PCLPointField structures containing oversized offset values could trigger reads beyond allocated memory, potentially leaking sensitive information or crashing the application.

The Vulnerability Explained

The vulnerable code resided in the pcpatch_from_pcd function, which converts PCL point cloud data into PostgreSQL's internal format. Here's the problematic lambda:

auto readFloat = [](const pcl::PCLPointField* f, const uint8_t* row) -> float {
    float v;
    std::memcpy(&v, row + f->offset, sizeof(float));
    return v;
};

The issue is straightforward but critical: f->offset comes from the parsed PCLPointField structure, which is populated from the input PCD file. The code blindly trusts this offset value and uses it to calculate the source address for memcpy.

How the Attack Works

Consider this attack scenario:

  1. An attacker creates a malicious PCD file where the PCLPointField structure for the "x" coordinate has an offset value of 0xFFFFFF00 (a very large number)
  2. When pcpatch_from_pcd processes this file, it calls findField("x") which returns the malicious field structure
  3. The readFloat lambda is invoked with this field and a legitimate row buffer (say, 32 bytes)
  4. The calculation row + f->offset produces a pointer far beyond the allocated buffer
  5. memcpy reads 4 bytes from this invalid memory location

The consequences depend on what memory lies at that address:
- Information disclosure: Reading adjacent heap data could leak sensitive information like encryption keys, authentication tokens, or other users' data
- Denial of service: Reading unmapped memory triggers a segmentation fault, crashing the PostgreSQL backend
- Heap metadata corruption: In some scenarios, the read could interact with heap management structures

Why This Pattern is Dangerous

The PR notes that similar patterns exist at lines 405 and 432, suggesting this was a systemic issue in how the codebase handled field offsets. Any time you're computing memory addresses using values from external input, you must validate those values first.

The Fix

The fix adds a crucial bounds check to the readFloat lambda. Here's the before and after comparison:

Before (vulnerable):

auto readFloat = [](const pcl::PCLPointField* f, const uint8_t* row) -> float {
    float v;
    std::memcpy(&v, row + f->offset, sizeof(float));
    return v;
};

After (secure):

auto readFloat = [&cloud2](const pcl::PCLPointField* f, const uint8_t* row) -> float {
    if (f->offset + sizeof(float) > cloud2.point_step)
        throw std::runtime_error("PCLPointField offset out of row buffer bounds");
    float v;
    std::memcpy(&v, row + f->offset, sizeof(float));
    return v;
};

Key Changes Explained

  1. Lambda capture modification: The lambda now captures cloud2 by reference ([&cloud2]) to access the authoritative buffer size information

  2. Bounds validation: Before any memory access, the code checks that f->offset + sizeof(float) doesn't exceed cloud2.point_step. The point_step field represents the actual size of each point's data row in bytes

  3. Fail-safe behavior: If the bounds check fails, the code throws a std::runtime_error with a descriptive message, preventing the dangerous memory access entirely

This three-line change transforms a critical memory safety vulnerability into a handled error condition. The fix is minimal, targeted, and doesn't impact performance for legitimate inputs since the check is a simple integer comparison.

Prevention & Best Practices

Defensive Coding for Memory Operations

  1. Always validate offsets before use: Any offset value from external input must be validated against known buffer sizes before being used in pointer arithmetic

  2. Use the pattern: offset + size <= buffer_length: This is the canonical bounds check. Note the use of <= for the comparison—if offset + size equals buffer_length exactly, you're reading the last valid bytes

  3. Watch for integer overflow: When adding offset and size, ensure the addition itself doesn't overflow. For very large offset values, offset + sizeof(float) could wrap around to a small number, bypassing the check. Consider using safe integer arithmetic libraries

Code Review Checklist

When reviewing code that handles external data formats:

  • [ ] Are all offset/index values validated before use?
  • [ ] Is the buffer size known and accessible at the point of validation?
  • [ ] Could integer overflow bypass the bounds check?
  • [ ] Are similar patterns used elsewhere in the codebase that need the same fix?

Static Analysis Integration

Tools that can detect this vulnerability pattern:
- Clang Static Analyzer: Can identify some unchecked buffer access patterns
- Coverity: Excellent at finding buffer overflows in C/C++ code
- AddressSanitizer (ASan): Runtime detection during testing
- Semgrep: Custom rules can match the memcpy(dest, src + untrusted_offset, ...) pattern

Key Takeaways

  • Never trust offset values from parsed file formats—the PCLPointField.offset came directly from user-supplied PCD files and should have been validated immediately
  • The readFloat lambda at line 400 was a ticking time bomb—any PCD file with a crafted offset could trigger memory corruption
  • Similar vulnerable patterns at lines 405 and 432 indicate systemic issues—when you find one instance of a bug pattern, search for others
  • The fix leverages existing metadata (cloud2.point_step) rather than introducing new tracking—always look for authoritative size information already available in your data structures
  • Throwing an exception on invalid input is the right choice—it's better to fail loudly than to silently corrupt memory

How Orbis AppSec Detected This

  • Source: Untrusted PCLPointField.offset value parsed from external PCD point cloud files
  • Sink: std::memcpy(&v, row + f->offset, sizeof(float)) in contrib/pcl/src/pcpatch_pcl.cpp:400
  • Missing control: No validation that offset + sizeof(float) falls within the row buffer bounds defined by cloud2.point_step
  • CWE: CWE-125 (Out-of-bounds Read)
  • Fix: Added bounds check comparing f->offset + sizeof(float) against cloud2.point_step before the memcpy operation, throwing std::runtime_error on violation

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates a fundamental principle of secure C++ programming: never use values from external input to calculate memory addresses without validation. The readFloat lambda's direct use of f->offset in pointer arithmetic created a critical security flaw that could be exploited through crafted PCD files.

The fix is elegant in its simplicity—a three-line change that validates the offset against the known row size before any memory access occurs. This pattern of "validate before use" should be applied consistently whenever handling structured data from untrusted sources.

For developers working with binary file formats, point cloud data, or any code that parses structured external input: audit your offset and size handling carefully. The memory safety guarantees of modern C++ don't protect you when you're doing raw pointer arithmetic with untrusted values.

References

Frequently Asked Questions

What is an out-of-bounds read vulnerability?

An out-of-bounds read occurs when code reads memory beyond the allocated buffer boundaries, potentially exposing sensitive data from adjacent memory regions or causing crashes when accessing unmapped memory.

How do you prevent out-of-bounds reads in C++?

Always validate that offsets and sizes are within buffer bounds before memory operations like memcpy. Use bounds-checked containers like std::vector with .at(), and validate all untrusted input that affects memory access calculations.

What CWE is out-of-bounds read?

CWE-125 (Out-of-bounds Read) covers this vulnerability class. Related entries include CWE-126 (Buffer Over-read) and CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is using sizeof() enough to prevent buffer overflows?

No, sizeof() only tells you the size of a type—you must also validate that the offset plus that size doesn't exceed your actual buffer length. Both the offset and the buffer size must be checked together.

Can static analysis detect out-of-bounds memcpy vulnerabilities?

Yes, static analyzers can detect many patterns where memcpy uses unchecked offsets. Tools like Coverity, PVS-Studio, and Clang's AddressSanitizer are effective at finding these issues, though complex data flows may require taint analysis.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1314

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.