# IC-DCMTK-0003: Stack Overflow via Deeply Nested DICOM Sequences

**Version**: DCMTK master `418274445` (DCMTK-3.7.0+64)
**CWE**: CWE-674 (Uncontrolled Recursion)

## Description

The DCMTK binary DICOM parser processes nested sequences through unbounded mutual recursion between `DcmSequenceOfItems::read()` and `DcmItem::read()`. A crafted DICOM file containing deeply nested sequences causes stack exhaustion, crashing any DCMTK-based application that parses it. The PoC is only 39,879 bytes (~20 bytes per nesting level) and reliably triggers a stack overflow under AddressSanitizer at approximately 1,060 nesting levels.

The recursion cycle spans four functions with no depth limit anywhere in the chain:

```
DcmSequenceOfItems::read()        dcsequen.cc:758
  -> readSubItem()                 dcsequen.cc:654
    -> DcmItem::read()             dcitem.cc:1385
      -> readSubElement()          dcitem.cc:1306/1483
        -> subElem->read()         [if VR is SQ, dispatches back to DcmSequenceOfItems::read()]
```

Each recursion level consumes approximately 7-8 KB of stack with ASan instrumentation (1.5-2 KB without). Since `readSubElement()` creates a `DcmSequenceOfItems` for any SQ-typed element and immediately calls its `read()` method, the recursion depth is determined entirely by the nesting depth of sequences in the input file. Real-world DICOM datasets rarely exceed 5-10 levels of nesting.

## Reproduction

The PoC contains 1,100 levels of nested `(0040,A730)` Content Sequence. Each recursion level consumes ~7-8 KB of stack under ASan, so the crash threshold depends on the stack size limit. With the default 8 MB stack (`ulimit -s 8192`), the overflow triggers at approximately 1,060 levels. Larger stack limits require proportionally deeper nesting.

```bash
ulimit -s 8192   # default 8 MB stack; ensures crash with 1,100-level PoC
./bin/dcmdump poc.dcm
```

Actual output:
```
W: DcmItem: Length of element (0002,0012) is odd
AddressSanitizer:DEADLYSIGNAL
=================================================================
==2190515==ERROR: AddressSanitizer: stack-overflow on address 0x7fffffefe868 (pc 0x5555556c5536 bp 0x7fffffeff0b0 sp 0x7fffffefe870 T0)
    #0 0x5555556c5536 in __asan_memcpy (build-asan/bin/dcmdump+0x171536)
    #1 0x555555715be5 in OFCondition::OFCondition(OFConditionConst const&) ofstd/include/dcmtk/ofstd/ofcond.h:208:5
    #2 0x55555574ccd9 in DcmItem::readTagAndLength(DcmInputStream&, E_TransferSyntax, DcmTag&, unsigned int&, unsigned int&) dcmdata/libsrc/dcitem.cc:1020:27
    #3 0x5555557599d6 in DcmItem::readUntilTag(DcmInputStream&, E_TransferSyntax, E_GrpLenEncoding, unsigned int, DcmTagKey const&) dcmdata/libsrc/dcitem.cc:1429:29
    #4 0x555555758cc7 in DcmItem::read(DcmInputStream&, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcitem.cc:1385:21
    #5 0x5555557c23b0 in DcmSequenceOfItems::readSubItem(DcmInputStream&, DcmTag const&, unsigned int, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcsequen.cc:654:30
    #6 0x5555557c4d04 in DcmSequenceOfItems::read(DcmInputStream&, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcsequen.cc:758:33
    #7 0x555555751a1d in DcmItem::readSubElement(DcmInputStream&, DcmTag&, unsigned int, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcitem.cc:1306:28
    #8 0x55555575aa42 in DcmItem::readUntilTag(DcmInputStream&, E_TransferSyntax, E_GrpLenEncoding, unsigned int, DcmTagKey const&) dcmdata/libsrc/dcitem.cc:1483:37
    #9 0x555555758cc7 in DcmItem::read(DcmInputStream&, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcitem.cc:1385:21
    #10 0x5555557c23b0 in DcmSequenceOfItems::readSubItem(DcmInputStream&, DcmTag const&, unsigned int, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcsequen.cc:654:30
    #11 0x5555557c4d04 in DcmSequenceOfItems::read(DcmInputStream&, E_TransferSyntax, E_GrpLenEncoding, unsigned int) dcmdata/libsrc/dcsequen.cc:758:33
    ... (alternating DcmItem::read/DcmSequenceOfItems::read pattern continues for 246 frames) ...

SUMMARY: AddressSanitizer: stack-overflow (build-asan/bin/dcmdump+0x171536) in __asan_memcpy
==2190515==ABORTING
```

Without ASan, the process receives `SIGSEGV` (threshold is higher: ~3,000-5,000 levels).

## Fix

Add a recursion depth counter through the `DcmSequenceOfItems::read()` / `DcmItem::read()` chain, rejecting input that exceeds a reasonable nesting limit:

```cpp
// In DcmSequenceOfItems::read() -- add a nestingDepth parameter through the chain
OFCondition DcmSequenceOfItems::read(DcmInputStream &inStream,
    const E_TransferSyntax xfer, const E_GrpLenEncoding glenc,
    const Uint32 maxReadLength, const Uint32 nestingDepth /* = 0 */)
{
    static const Uint32 MAX_NESTING_DEPTH = 64;
    if (nestingDepth > MAX_NESTING_DEPTH) {
        DCMDATA_ERROR("Maximum sequence nesting depth exceeded ("
            << nestingDepth << " > " << MAX_NESTING_DEPTH << ")");
        return EC_InvalidStream;
    }
    // ... existing code, passing nestingDepth + 1 to readSubItem() ...
}
```
