Summary
Three memory safety vulnerabilities were found via manual source code audit of cb0r.c.
Bug 1: Out-of-Bounds Read on Multi-Byte CBOR Headers (CWE-125) — High
Location: cb0r(), multiple locations (lines 73-98, 101-114, 128-138, and 186-198)
uint8_t *end = start + 1;
if(end > stop) { // Only checks first byte is in bounds
if(result) result->type = CB0R_ERR;
return stop;
}
// ...
goto *go[*start]; // Dispatch based on type byte
l_int8:
end += 4; // No bounds check for start[1..8]
l_int4:
end += 2;
l_int2:
end += 1;
l_int1:
end += 1;
l_int:
goto l_finish;
Issue: The parser only checks that the first byte (start[0]) is within the [start, stop) range. For multi-byte headers (int with 1/2/4/8 byte additional, byte/string with 1/2/4 byte length, array/map with 1/2/4 byte count, tag with 1/2/4/8 byte value), the additional bytes are accessed directly via start[N] without any bounds check.
Affected code paths:
- l_int1/2/4/8 (lines 73-82):
end is advanced past the header, but bytes are read at lines 186-198 via start[size - 7] through start[size] without checking bounds
- l_byte1/2/4 (lines 85-95):
start[1], start[2], start[size] accessed directly at lines 87-94 to compute length
- l_array1/2/4 (lines 101-111):
start[1], start[2], start[size] accessed at lines 103-110 to compute count
- l_tag1/2/4/8 (lines 128-138): Tag value bytes accessed at lines 186-198
Concrete example: Input of 1 byte: \x1b (8-byte unsigned integer). The parser checks start + 1 <= stop → passes. Then dispatches to l_int8. At the result extraction (lines 186-198), it reads start[1] through start[8] — 8 bytes past the 1-byte input buffer.
Impact: Information disclosure via OOB read, or crash if the buffer is at a page boundary.
Bug 2: Unbounded Recursion via Nested Containers (CWE-674) — Medium
Location: cb0r(), lines 121, 138, 144
// For arrays (line 121):
end = cb0r(start+size+1, stop, count-1, NULL);
// For tags (line 138):
end = cb0r(start+size+1, stop, 0, NULL);
// For indefinite (line 144):
end = cb0r(start+1, stop, count, NULL);
Issue: Arrays, maps, and tags cause recursive calls. Nesting depth = recursion depth. With deeply nested CBOR (e.g., array of 1 containing array of 1 containing...), each level costs 2 bytes (0x81 + next level). An input of 10,000 bytes can create 5,000 levels of nesting, exceeding typical stack limits.
Trigger: Repeated bytes 0x81 (array of 1 item):
\x81\x81\x81\x81\x81\x81...\x81\x00
Each 0x81 creates one level of recursion.
Impact: Stack overflow → crash/DoS. On embedded systems (cb0r's target), stack space is typically very limited (1-8 KB).
Bug 3: Pointer Overflow in Byte String Length (CWE-190) — Low
Location: cb0r(), lines 85-94 (l_byte4)
l_byte4:
size = 2;
end += (uint32_t)(start[1]) << 24;
end += (uint32_t)(start[2]) << 16;
l_byte2:
size += 1;
end += (uint32_t)(start[size]) << 8;
l_byte1:
size += 1;
end += start[size] + size;
Issue: On 32-bit platforms, the accumulated additions to end (a pointer) can overflow, causing end to wrap around. For example, a 4-byte length of 0xFFFFFFFF adds ~4GB to a pointer, wrapping it to before start.
Impact: On 32-bit: invalid pointer returned, potential use in subsequent parsing.
Proof of Concept (Bug 1)
#include <stdio.h>
#include <stdint.h>
#include "cb0r.h"
int main() {
// Single byte: 0x1b = 8-byte unsigned int
// Parser reads start[1] through start[8] → 8 bytes OOB
uint8_t buf[1] = { 0x1b };
cb0r_s result;
cb0r_read(buf, 1, &result);
// result.value now contains 8 bytes of OOB data
printf("OOB value read: 0x%016llx\n", (unsigned long long)result.value);
return 0;
}
Suggested Fixes
Bug 1:
Add bounds checking after determining header size:
l_int8:
end += 4;
l_int4:
end += 2;
l_int2:
end += 1;
l_int1:
end += 1;
if(end > stop) goto l_eparse; // ADD THIS CHECK
l_int:
goto l_finish;
Apply the same pattern for l_byte1/2/4, l_array1/2/4, and l_tag1/2/4/8.
Bug 2:
Add a recursion depth parameter or limit:
#define CB0R_MAX_DEPTH 32
// Add depth parameter to cb0r(), check at entry
Found via manual source code audit. Reported responsibly — please let me know if you need any additional information.
Summary
Three memory safety vulnerabilities were found via manual source code audit of
cb0r.c.Bug 1: Out-of-Bounds Read on Multi-Byte CBOR Headers (CWE-125) — High
Location:
cb0r(), multiple locations (lines 73-98, 101-114, 128-138, and 186-198)Issue: The parser only checks that the first byte (
start[0]) is within the[start, stop)range. For multi-byte headers (int with 1/2/4/8 byte additional, byte/string with 1/2/4 byte length, array/map with 1/2/4 byte count, tag with 1/2/4/8 byte value), the additional bytes are accessed directly viastart[N]without any bounds check.Affected code paths:
endis advanced past the header, but bytes are read at lines 186-198 viastart[size - 7]throughstart[size]without checking boundsstart[1],start[2],start[size]accessed directly at lines 87-94 to compute lengthstart[1],start[2],start[size]accessed at lines 103-110 to compute countConcrete example: Input of 1 byte:
\x1b(8-byte unsigned integer). The parser checksstart + 1 <= stop→ passes. Then dispatches tol_int8. At the result extraction (lines 186-198), it readsstart[1]throughstart[8]— 8 bytes past the 1-byte input buffer.Impact: Information disclosure via OOB read, or crash if the buffer is at a page boundary.
Bug 2: Unbounded Recursion via Nested Containers (CWE-674) — Medium
Location:
cb0r(), lines 121, 138, 144Issue: Arrays, maps, and tags cause recursive calls. Nesting depth = recursion depth. With deeply nested CBOR (e.g., array of 1 containing array of 1 containing...), each level costs 2 bytes (
0x81+ next level). An input of 10,000 bytes can create 5,000 levels of nesting, exceeding typical stack limits.Trigger: Repeated bytes
0x81(array of 1 item):Each
0x81creates one level of recursion.Impact: Stack overflow → crash/DoS. On embedded systems (cb0r's target), stack space is typically very limited (1-8 KB).
Bug 3: Pointer Overflow in Byte String Length (CWE-190) — Low
Location:
cb0r(), lines 85-94 (l_byte4)Issue: On 32-bit platforms, the accumulated additions to
end(a pointer) can overflow, causingendto wrap around. For example, a 4-byte length of 0xFFFFFFFF adds ~4GB to a pointer, wrapping it to beforestart.Impact: On 32-bit: invalid pointer returned, potential use in subsequent parsing.
Proof of Concept (Bug 1)
Suggested Fixes
Bug 1:
Add bounds checking after determining header size:
Apply the same pattern for l_byte1/2/4, l_array1/2/4, and l_tag1/2/4/8.
Bug 2:
Add a recursion depth parameter or limit:
Found via manual source code audit. Reported responsibly — please let me know if you need any additional information.