Skip to content

Fix stack OOB read in decode_tag_number_and_value() - #1299

Open
JorgeBarredo14 wants to merge 2 commits into
bacnet-stack:masterfrom
JorgeBarredo14:fix/bacdcode-oob-read
Open

Fix stack OOB read in decode_tag_number_and_value()#1299
JorgeBarredo14 wants to merge 2 commits into
bacnet-stack:masterfrom
JorgeBarredo14:fix/bacdcode-oob-read

Conversation

@JorgeBarredo14

Copy link
Copy Markdown

Summary

decode_tag_number_and_value() in src/bacnet/bacdcode.c reads up to 5 bytes past the caller buffer when the first APDU byte has IS_EXTENDED_VALUE set, because the function takes no buffer-length parameter.

Root cause

Lines 695-712: when IS_EXTENDED_VALUE(apdu[0]) is true, the function unconditionally reads apdu[1]. If that byte equals 0xFF, it calls decode_unsigned32 which reads four more bytes -- all without any length check.

Fix

Add an early-return guard before decode_tag_number() that rejects extended-value tags with BACNET_STATUS_ERROR. The existing code block becomes unreachable and can be removed in a follow-up cleanup. The length-aware variant bacnet_tag_number_and_value_decode() is unaffected.

Metadata

  • CWE: CWE-125 (Out-of-bounds Read)
  • Severity: Moderate (CVSS 5.3)
  • Advisory: GHSA-cr77-f6f9-494h
  • Found during: academic security research

The legacy decode_tag_number_and_value() function takes no buffer-length
parameter. When the first APDU byte has IS_EXTENDED_VALUE set, the function
reads apdu[1] unconditionally, causing an up-to-5-byte out-of-bounds read
on a 1-byte caller buffer.

Add an early-return guard that rejects extended-value tags before any
dereference past apdu[0]. The length-aware variant
bacnet_tag_number_and_value_decode() is unaffected and should be used by
all new code.

CWE-125 (Out-of-bounds Read)
Advisory: GHSA-cr77-f6f9-494h
Found during academic security research.
@JorgeBarredo14

Copy link
Copy Markdown
Author

Hi Steve,

The CI unittest failure (exit code 8 = 8 tests failed) is expected with this change.
The early-return guard rejects all extended-value tags, which breaks existing tests
that call decode_tag_number_and_value() with valid extended-value inputs in controlled
(safe) contexts.

There are two approaches to fix this:

Approach A — Update the tests (recommended)

Migrate the 8 failing test call-sites to use bacnet_tag_number_and_value_decode()
(the length-aware variant). This is the safest long-term solution since the legacy
function is inherently unsafe for untrusted input. Example diff for one test:

 // Before (unsafe, no bounds check):
 len = decode_tag_number_and_value(&apdu[0], &tag_number, &value);
 
 // After (safe, bounds-checked):
 len = bacnet_tag_number_and_value_decode(&apdu[0], sizeof(apdu), &tag_number, &value);

I'm happy to push this update to the PR if you prefer this approach.

Approach B — Narrow the guard
Instead of rejecting all extended-value tags, only return an error when the function
is called from external/untrusted paths (though this is hard to enforce at the function
level without a length parameter).

Let me know which direction you'd prefer and I'll update the PR accordingly.

@skarg

skarg commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator

Use approach A: Migrate the 8 failing test call-sites to use bacnet_tag_number_and_value_decode()
(the length-aware variant).

…de()

Replace 3 calls to the legacy decode_tag_number_and_value() in tests
that were not protected by BACNET_STACK_DEPRECATED_DISABLE guards.
These calls now use the length-aware bacnet_tag_number_and_value_decode()
variant, fixing the unittest failures caused by the extended-value
rejection guard added in the previous commit.
@JorgeBarredo14

Copy link
Copy Markdown
Author

Pushed a follow-up commit migrating the 3 unguarded test call-sites to bacnet_tag_number_and_value_decode() as discussed. CI should pass now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants