Mercurial > hg > orthanc
changeset 6738:bae99026ca97
Added support for deeply nested sequences (including a patch for DCMTK 3.7.0)
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Wed, 06 May 2026 12:40:49 +0200 |
| parents | 2110a445e088 |
| children | ace9135428dd |
| files | NEWS OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.7.0.cmake OrthancFramework/Resources/Patches/dcmtk-3.7.0-max-nested-sequence.patch OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp |
| diffstat | 4 files changed, 751 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Tue May 05 15:24:25 2026 +0200 +++ b/NEWS Wed May 06 12:40:49 2026 +0200 @@ -34,6 +34,12 @@ was required. * Fix usage of "LocalAet" in C-Find and "queries/../answers/../retrieve". * Fix Orthanc::ImageAccessor that was broken in Orthanc Framework 1.12.11 +* Fix a Denial of Service via Deeply Nested DICOM Sequences + https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=258 + Security issue reported by Jose Lopez Martinez (aka elpe_pinillo) from Deloitte. +* Upgraded dependencies for static builds: + - dcmtk 3.7.0 (hot-fix: https://github.com/DCMTK/dcmtk/commit/847d50e83ae5bbfbc731c99c142ee1410303d222) + Plugin SDK
--- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.7.0.cmake Tue May 05 15:24:25 2026 +0200 +++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.7.0.cmake Wed May 06 12:40:49 2026 +0200 @@ -65,6 +65,17 @@ message(FATAL_ERROR "Error while patching a file") endif() + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${CMAKE_CURRENT_LIST_DIR}/../Patches/dcmtk-3.7.0-max-nested-sequence.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + if (MSVC) # Older versions of Microsoft Visual Studio (notably MSVC2008) # don't like void usage of function arguments in C source files,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Resources/Patches/dcmtk-3.7.0-max-nested-sequence.patch Wed May 06 12:40:49 2026 +0200 @@ -0,0 +1,729 @@ +Only in dcmtk-3.7.0/: .gitignore +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/config/docs/macros.txt dcmtk-3.7.0/config/docs/macros.txt +--- dcmtk-3.7.0.orig/config/docs/macros.txt 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/config/docs/macros.txt 2026-05-06 10:47:14.137800800 +0200 +@@ -109,6 +109,21 @@ + dcmtk::log4cplus::threadCleanup() should be called by the user code in + order to clean-up oflog's thread local storage. + ++DCMTK_MAX_SEQUENCE_NESTING ++ Affected: dcmdata ++ Type of modification: Compile-time tunable ++ Explanation: Defines the default maximum permitted sequence nesting depth ++ during DICOM parsing. Deeply nested sequences can cause a stack overflow ++ by exhausting the call stack through unbounded recursion. When this macro ++ is not defined, the default limit of 64 nested sequence levels is used. ++ Real-world DICOM data rarely exceeds 5-10 nesting levels. The limit can ++ be changed at runtime per parse operation via ++ DcmInputStream::setMaxNestingDepth(), DcmItem::setMaxNestingDepth(), or ++ DcmFileFormat::setMaxNestingDepth(). Setting the runtime value to -1 ++ disables the check entirely. ++ Minimum value: 1. Values less than 1 cause a compile-time error. ++ Maximum value: 2147483647 (aligned to 32-bit signed integer runtime API). ++ + DCMTK_MERGE_STDERR_TO_STDOUT + Affected: dcmdata + Type of modification: Activates feature +Only in dcmtk-3.7.0.orig/config/include/dcmtk/config: osconfig.h +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcerror.h dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcerror.h +--- dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcerror.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcerror.h 2026-05-06 10:47:14.137800800 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2025, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -200,6 +200,8 @@ + extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_UnsupportedURIType; + /// Execution of command line failed + extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_CommandLineFailed; ++/// Maximum sequence nesting depth exceeded (stack overflow protection) ++extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_NestingDepthLimitExceeded; + + ///@} + +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcfilefo.h dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcfilefo.h +--- dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcfilefo.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcfilefo.h 2026-05-06 10:47:14.137800800 +0200 +@@ -514,6 +514,35 @@ + + /// implementation version name to write in the meta-header + OFString ImplementationVersionName; ++ ++ /// maximum sequence nesting depth for parsing ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ Sint32 MaxNestingDepth; ++ ++ public: ++ ++ /** set the maximum permitted sequence nesting depth for parsing. ++ * Applied to the input stream in loadFile() and read(), and also ++ * forwarded to the contained dataset. ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum permitted nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(Sint32 maxDepth) ++ { ++ MaxNestingDepth = maxDepth; ++ DcmDataset* dset = getDataset(); ++ if (dset) ++ dset->setMaxNestingDepth(maxDepth); ++ } ++ ++ /** return the maximum permitted sequence nesting depth for parsing. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const { return MaxNestingDepth; } + }; + + +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcistrma.h dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcistrma.h +--- dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcistrma.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcistrma.h 2026-05-06 10:47:14.137800800 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2018, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -224,6 +224,40 @@ + */ + virtual DcmInputStreamFactory *newFactory() const = 0; + ++ /** returns the current sequence nesting depth. ++ * This counter is used to prevent stack overflow from deeply nested ++ * DICOM sequences in malicious input files. ++ * @return current nesting depth ++ */ ++ Uint32 nestingDepth() const; ++ ++ /** increments the sequence nesting depth counter. ++ * @return the new nesting depth after incrementing ++ */ ++ Uint32 incrementNestingDepth(); ++ ++ /** decrements the sequence nesting depth counter. ++ * Does nothing if the counter is already zero. ++ */ ++ void decrementNestingDepth(); ++ ++ /** returns the maximum permitted sequence nesting depth for this stream. ++ * A value of 0 means the compile-time default (DCMTK_MAX_SEQUENCE_NESTING) applies. ++ * A value of -1 means the check is disabled (unlimited nesting). ++ * @return maximum nesting depth setting ++ */ ++ Sint32 maxNestingDepth() const; ++ ++ /** sets the maximum permitted sequence nesting depth for this stream. ++ * Must be called before parsing begins. ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum permitted nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(Sint32 maxDepth); ++ + /** marks the current stream position for a later putback operation, + * overwriting a possibly existing prior putback mark. + * The DcmObject read methods rely on the possibility to putback +@@ -272,6 +306,12 @@ + + /// putback marker + offile_off_t mark_; ++ ++ /// current sequence nesting depth (for stack overflow protection) ++ Uint32 nestingDepth_; ++ ++ /// maximum permitted sequence nesting depth (0 = default 64, -1 = unlimited) ++ Sint32 maxNestingDepth_; + }; + + +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcitem.h dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcitem.h +--- dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcitem.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcitem.h 2026-05-06 10:47:14.137800800 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2025, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -1573,6 +1573,29 @@ + + /// cache for private creator tags and identifiers + DcmPrivateTagCache privateCreatorCache; ++ ++ /// maximum sequence nesting depth for parsing ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ Sint32 maxNestingDepth; ++ ++ public: ++ ++ /** set the maximum permitted sequence nesting depth for parsing. ++ * This limit is applied to the input stream before parsing in ++ * loadFile() and read(). ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum permitted nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(Sint32 maxDepth) { maxNestingDepth = maxDepth; } ++ ++ /** return the maximum permitted sequence nesting depth for parsing. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const { return maxNestingDepth; } + }; + + /** Checks whether left hand side item is smaller than right hand side +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcobject.h dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcobject.h +--- dcmtk-3.7.0.orig/dcmdata/include/dcmtk/dcmdata/dcobject.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/include/dcmtk/dcmdata/dcobject.h 2026-05-06 10:47:14.137800800 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2024, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -50,6 +50,18 @@ + + // Undefined Length Identifier now defined in dctypes.h + ++// Default maximum sequence nesting depth (can be overridden at compile time). ++// Must be in the range [1, 2147483647]. ++#ifndef DCMTK_MAX_SEQUENCE_NESTING ++#define DCMTK_MAX_SEQUENCE_NESTING 64 ++#endif ++#if DCMTK_MAX_SEQUENCE_NESTING < 1 ++#error "DCMTK_MAX_SEQUENCE_NESTING must be >= 1" ++#endif ++#if DCMTK_MAX_SEQUENCE_NESTING > 2147483647 ++#error "DCMTK_MAX_SEQUENCE_NESTING must be <= 2147483647" ++#endif ++ + // Maximum number of read bytes for a Value Element + const Uint32 DCM_MaxReadLength = 4096; + +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/libsrc/dcdatset.cc dcmtk-3.7.0/dcmdata/libsrc/dcdatset.cc +--- dcmtk-3.7.0.orig/dcmdata/libsrc/dcdatset.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/libsrc/dcdatset.cc 2026-05-06 10:47:14.141581926 +0200 +@@ -647,6 +647,9 @@ + { + /* use stdin stream */ + DcmStdinStream inStream; ++ /* apply configured nesting depth limit */ ++ if (getMaxNestingDepth() > 0) ++ inStream.setMaxNestingDepth(getMaxNestingDepth()); + + /* clear this object */ + l_error = clear(); +@@ -670,6 +673,9 @@ + } else { + /* open file for input */ + DcmInputFileStream fileStream(fileName); ++ /* apply configured nesting depth limit */ ++ if (getMaxNestingDepth() > 0) ++ fileStream.setMaxNestingDepth(getMaxNestingDepth()); + + /* check stream status */ + l_error = fileStream.status(); +Only in dcmtk-3.7.0.orig/dcmdata/libsrc: dcdict_orthanc.cc +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/libsrc/dcerror.cc dcmtk-3.7.0/dcmdata/libsrc/dcerror.cc +--- dcmtk-3.7.0.orig/dcmdata/libsrc/dcerror.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/libsrc/dcerror.cc 2026-05-06 10:47:14.141581926 +0200 +@@ -91,6 +91,7 @@ + makeOFConditionConst(EC_BulkDataURINotSupported, OFM_dcmdata, 66, OF_error, "BulkDataURI not yet supported" ); + makeOFConditionConst(EC_UnsupportedURIType, OFM_dcmdata, 67, OF_error, "Unsupported URI type" ); + makeOFConditionConst(EC_CommandLineFailed, OFM_dcmdata, 68, OF_error, "Execution of command line failed" ); ++makeOFConditionConst(EC_NestingDepthLimitExceeded, OFM_dcmdata, 69, OF_error, "Maximum sequence nesting depth exceeded" ); + + const unsigned short EC_CODE_CannotSelectCharacterSet = 35; + const unsigned short EC_CODE_CannotConvertCharacterSet = 36; +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/libsrc/dcfilefo.cc dcmtk-3.7.0/dcmdata/libsrc/dcfilefo.cc +--- dcmtk-3.7.0.orig/dcmdata/libsrc/dcfilefo.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/libsrc/dcfilefo.cc 2026-05-06 10:48:15.265234129 +0200 +@@ -54,7 +54,8 @@ + : DcmSequenceOfItems(DCM_InternalUseTag), + FileReadMode(ERM_autoDetect), + ImplementationClassUID(OFFIS_IMPLEMENTATION_CLASS_UID), +- ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME) ++ ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME), ++ MaxNestingDepth(0) + { + DcmMetaInfo *MetaInfo = new DcmMetaInfo(); + DcmSequenceOfItems::itemList->insert(MetaInfo); +@@ -71,7 +72,8 @@ + : DcmSequenceOfItems(DCM_InternalUseTag), + FileReadMode(ERM_autoDetect), + ImplementationClassUID(OFFIS_IMPLEMENTATION_CLASS_UID), +- ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME) ++ ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME), ++ MaxNestingDepth(0) + { + DcmMetaInfo *MetaInfo = new DcmMetaInfo(); + DcmSequenceOfItems::itemList->insert(MetaInfo); +@@ -99,7 +101,8 @@ + : DcmSequenceOfItems(old), + FileReadMode(old.FileReadMode), + ImplementationClassUID(old.ImplementationClassUID), +- ImplementationVersionName(old.ImplementationVersionName) ++ ImplementationVersionName(old.ImplementationVersionName), ++ MaxNestingDepth(old.MaxNestingDepth) + { + } + +@@ -128,8 +131,8 @@ + FileReadMode = obj.FileReadMode; + ImplementationClassUID = obj.ImplementationClassUID; + ImplementationVersionName = obj.ImplementationVersionName; ++ MaxNestingDepth = obj.MaxNestingDepth; + } +- + return *this; + } + +@@ -942,6 +945,9 @@ + { + /* use stdin stream */ + DcmStdinStream inStream; ++ /* apply configured nesting depth limit */ ++ if (MaxNestingDepth != 0) ++ inStream.setMaxNestingDepth(MaxNestingDepth); + + /* clear this object */ + l_error = clear(); +@@ -972,6 +978,9 @@ + } else { + /* open file for output */ + DcmInputFileStream fileStream(fileName); ++ /* apply configured nesting depth limit */ ++ if (MaxNestingDepth > 0) ++ fileStream.setMaxNestingDepth(MaxNestingDepth); + + /* check stream status */ + l_error = fileStream.status(); +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/libsrc/dcistrma.cc dcmtk-3.7.0/dcmdata/libsrc/dcistrma.cc +--- dcmtk-3.7.0.orig/dcmdata/libsrc/dcistrma.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/libsrc/dcistrma.cc 2026-05-06 10:47:14.141581926 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2010, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -29,6 +29,8 @@ + , compressionFilter_(NULL) + , tell_(0) + , mark_(0) ++, nestingDepth_(0) ++, maxNestingDepth_(0) + { + } + +@@ -94,6 +96,32 @@ + return current_; + } + ++Uint32 DcmInputStream::nestingDepth() const ++{ ++ return nestingDepth_; ++} ++ ++Uint32 DcmInputStream::incrementNestingDepth() ++{ ++ return ++nestingDepth_; ++} ++ ++void DcmInputStream::decrementNestingDepth() ++{ ++ if (nestingDepth_ > 0) ++ --nestingDepth_; ++} ++ ++Sint32 DcmInputStream::maxNestingDepth() const ++{ ++ return maxNestingDepth_; ++} ++ ++void DcmInputStream::setMaxNestingDepth(Sint32 maxDepth) ++{ ++ maxNestingDepth_ = maxDepth; ++} ++ + OFCondition DcmInputStream::installCompressionFilter(E_StreamCompression filterType) + { + OFCondition result = EC_Normal; +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/libsrc/dcitem.cc dcmtk-3.7.0/dcmdata/libsrc/dcitem.cc +--- dcmtk-3.7.0.orig/dcmdata/libsrc/dcitem.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/libsrc/dcitem.cc 2026-05-06 10:47:14.141581926 +0200 +@@ -83,7 +83,8 @@ + elementList(NULL), + lastElementComplete(OFTrue), + fStartPosition(0), +- privateCreatorCache() ++ privateCreatorCache(), ++ maxNestingDepth(0) + { + elementList = new DcmList; + } +@@ -95,7 +96,8 @@ + elementList(NULL), + lastElementComplete(OFTrue), + fStartPosition(0), +- privateCreatorCache() ++ privateCreatorCache(), ++ maxNestingDepth(0) + { + elementList = new DcmList; + } +@@ -106,7 +108,8 @@ + elementList(new DcmList), + lastElementComplete(old.lastElementComplete), + fStartPosition(old.fStartPosition), +- privateCreatorCache() ++ privateCreatorCache(), ++ maxNestingDepth(old.maxNestingDepth) + { + if (!old.elementList->empty()) + { +@@ -136,6 +139,7 @@ + // copy DcmItem's member variables + lastElementComplete = obj.lastElementComplete; + fStartPosition = obj.fStartPosition; ++ maxNestingDepth = obj.maxNestingDepth; + if (!obj.elementList->empty()) + { + elementList->seek(ELP_first); +@@ -1394,6 +1398,10 @@ + return errorFlag; + } + ++ /* apply configured nesting depth limit to the input stream (0 = use default, skip override) */ ++ if (getMaxNestingDepth() != 0) ++ inStream.setMaxNestingDepth(getMaxNestingDepth()); ++ + /* figure out if the stream reported an error */ + errorFlag = inStream.status(); + /* if the stream reported an error or if it is the end of the */ +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmdata/libsrc/dcsequen.cc dcmtk-3.7.0/dcmdata/libsrc/dcsequen.cc +--- dcmtk-3.7.0.orig/dcmdata/libsrc/dcsequen.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmdata/libsrc/dcsequen.cc 2026-05-06 10:47:14.141581926 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2023, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -704,6 +704,23 @@ + errorFlag = EC_IllegalCall; + else + { ++ const Uint32 depth = inStream.incrementNestingDepth(); ++ const Sint32 maxDepthSetting = inStream.maxNestingDepth(); ++ /* -1 = unlimited; 0 = use built-in default (DCMTK_MAX_SEQUENCE_NESTING); > 0 = custom limit */ ++ const Uint32 effectiveMax = (maxDepthSetting == 0) ? DCMTK_MAX_SEQUENCE_NESTING ++ : (maxDepthSetting < 0) ? 0 /* unlimited */ ++ : OFstatic_cast(Uint32, maxDepthSetting); ++ if (effectiveMax > 0 && depth > effectiveMax) ++ { ++ DCMDATA_ERROR("DcmSequenceOfItems: Maximum nesting depth (" << effectiveMax ++ << ") exceeded while parsing sequence " << getTagName() << " " << getTag()); ++ inStream.decrementNestingDepth(); ++ errorFlag = EC_NestingDepthLimitExceeded; ++ // dump information if required ++ DCMDATA_TRACE("DcmSequenceOfItems::read() returns error = " << errorFlag.text()); ++ return errorFlag; ++ } ++ + errorFlag = inStream.status(); + + if (errorFlag.good() && inStream.eos()) +@@ -773,6 +790,7 @@ + errorFlag = EC_Normal; + if (errorFlag.good()) + setTransferState(ERW_ready); // sequence is complete ++ inStream.decrementNestingDepth(); + } + // dump information if required + DCMDATA_TRACE("DcmSequenceOfItems::read() returns error = " << errorFlag.text()); +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmnet/include/dcmtk/dcmnet/scp.h dcmtk-3.7.0/dcmnet/include/dcmtk/dcmnet/scp.h +--- dcmtk-3.7.0.orig/dcmnet/include/dcmtk/dcmnet/scp.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmnet/include/dcmtk/dcmnet/scp.h 2026-05-06 10:47:14.145363052 +0200 +@@ -360,6 +360,17 @@ + */ + void setAlwaysAcceptDefaultRole(const OFBool enabled); + ++ /** Set the maximum permitted sequence nesting depth for parsing ++ * datasets received over the network. This limit is applied to ++ * each dataset received via receiveDIMSEDataset(). ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(const Sint32 maxDepth); ++ + /* Get methods for SCP settings */ + + /** Returns TCP/IP port number SCP listens for new connection requests +@@ -434,6 +445,14 @@ + */ + OFBool getProgressNotificationMode() const; + ++ /** Return the maximum permitted sequence nesting depth for ++ * parsing datasets received over the network. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ * -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const; ++ + /** Get access to the configuration of the SCP. Note that the functionality + * on the configuration object is shadowed by other API functions of DcmSCP. + * The existing functions are provided in order to not break users of this +@@ -1157,6 +1176,11 @@ + /// it, e.g. in the context of the DcmSCPPool class. + DcmSharedSCPConfig m_cfg; + ++ /// Maximum sequence nesting depth for parsing received datasets ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ /// -1 = unlimited) ++ Sint32 m_maxNestingDepth; ++ + /** Drops association and clears internal structures to free memory + */ + void dropAndDestroyAssociation(); +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmnet/include/dcmtk/dcmnet/scu.h dcmtk-3.7.0/dcmnet/include/dcmtk/dcmnet/scu.h +--- dcmtk-3.7.0.orig/dcmnet/include/dcmtk/dcmnet/scu.h 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmnet/include/dcmtk/dcmnet/scu.h 2026-05-06 10:47:14.145363052 +0200 +@@ -739,6 +739,17 @@ + */ + void setProgressNotificationMode(const OFBool mode); + ++ /** Set the maximum permitted sequence nesting depth for parsing ++ * datasets received over the network. This limit is applied to ++ * each dataset received via receiveDIMSEDataset(). ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(const Sint32 maxDepth); ++ + /* Get methods */ + + /** Get current connection status +@@ -831,6 +842,14 @@ + */ + OFBool getProgressNotificationMode() const; + ++ /** Return the maximum permitted sequence nesting depth for ++ * parsing datasets received over the network. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ * -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const; ++ + /** Returns whether SCU is configured to create a TLS connection with the SCP + * @return OFTrue if TLS mode has been enabled, OFFalse otherwise + */ +@@ -1136,6 +1155,11 @@ + /// Progress notification mode (default: enabled) + OFBool m_progressNotificationMode; + ++ /// Maximum sequence nesting depth for parsing received datasets ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ /// -1 = unlimited) ++ Sint32 m_maxNestingDepth; ++ + /// Flag indicating whether secure mode has been enabled (default: disabled) + OFBool m_secureConnectionEnabled; + +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmnet/libsrc/dimse.cc dcmtk-3.7.0/dcmnet/libsrc/dimse.cc +--- dcmtk-3.7.0.orig/dcmnet/libsrc/dimse.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmnet/libsrc/dimse.cc 2026-05-06 10:47:14.145363052 +0200 +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2025, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were partly developed by +@@ -1536,7 +1536,8 @@ + dset = *dataObject; + } + +- /* check if there is still no DcmDataset object which can be used to store the data set. */ ++ /* check if there is still no DcmDataset object (i.e. if allocation fails) */ ++ /* which can be used to store the data set. */ + if (dset == NULL) + { + /* if this is the case, just go ahead an receive data, but do not store it anywhere */ +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmnet/libsrc/scp.cc dcmtk-3.7.0/dcmnet/libsrc/scp.cc +--- dcmtk-3.7.0.orig/dcmnet/libsrc/scp.cc 2025-12-15 08:28:13.000000000 +0100 ++++ dcmtk-3.7.0/dcmnet/libsrc/scp.cc 2026-05-06 10:47:14.145363052 +0200 +@@ -32,6 +32,7 @@ + : m_network(NULL) + , m_assoc(NULL) + , m_cfg() ++, m_maxNestingDepth(0) + { + OFStandard::initializeNetwork(); + } +@@ -1590,6 +1591,19 @@ + if (m_assoc == NULL) + return DIMSE_ILLEGALASSOCIATION; + ++ /* if a custom nesting depth limit is configured, pre-allocate ++ * the dataset and apply the setting before parsing starts. ++ * DIMSE_receiveDataSetInMemory() will use this dataset instead ++ * of creating a new one internally. */ ++ OFBool weAllocated = OFFalse; ++ if (dataObject != NULL && *dataObject == NULL ++ && m_maxNestingDepth != 0) ++ { ++ *dataObject = new DcmDataset(); ++ (*dataObject)->setMaxNestingDepth(m_maxNestingDepth); ++ weAllocated = OFTrue; ++ } ++ + OFCondition cond; + /* call the corresponding DIMSE function to receive the dataset */ + if (m_cfg->getProgressNotificationMode()) +@@ -1619,6 +1633,14 @@ + } + else + { ++ /* if we pre-allocated the dataset, clean up on error since ++ * DIMSE_receiveDataSetInMemory() won't delete a dataset ++ * that was passed in by the caller */ ++ if (weAllocated && dataObject != NULL) ++ { ++ delete *dataObject; ++ *dataObject = NULL; ++ } + OFString tempStr; + DCMNET_ERROR("Unable to receive dataset on presentation context " + << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond)); +@@ -1910,6 +1932,13 @@ + + // ---------------------------------------------------------------------------- + ++void DcmSCP::setMaxNestingDepth(const Sint32 maxDepth) ++{ ++ m_maxNestingDepth = maxDepth; ++} ++ ++// ---------------------------------------------------------------------------- ++ + /* Get methods for SCP settings and current association information */ + + OFBool DcmSCP::getRefuseAssociation() const +@@ -2002,6 +2031,13 @@ + } + + // ---------------------------------------------------------------------------- ++ ++Sint32 DcmSCP::getMaxNestingDepth() const ++{ ++ return m_maxNestingDepth; ++} ++ ++// ---------------------------------------------------------------------------- + + OFBool DcmSCP::isConnected() const + { +diff -urEb '--exclude=.git' dcmtk-3.7.0.orig/dcmnet/libsrc/scu.cc dcmtk-3.7.0/dcmnet/libsrc/scu.cc +--- dcmtk-3.7.0.orig/dcmnet/libsrc/scu.cc 2026-05-06 11:24:55.427982600 +0200 ++++ dcmtk-3.7.0/dcmnet/libsrc/scu.cc 2026-05-06 10:47:14.145363052 +0200 +@@ -61,6 +61,7 @@ + , m_verbosePCMode(OFFalse) + , m_datasetConversionMode(OFFalse) + , m_progressNotificationMode(OFTrue) ++ , m_maxNestingDepth(0) + , m_secureConnectionEnabled(OFFalse) + , m_protocolVersion(ASC_AF_Default) + { +@@ -2505,6 +2506,19 @@ + if (!isConnected()) + return DIMSE_ILLEGALASSOCIATION; + ++ /* if a custom nesting depth limit is configured, pre-allocate ++ * the dataset and apply the setting before parsing starts. ++ * DIMSE_receiveDataSetInMemory() will use this dataset instead ++ * of creating a new one internally. */ ++ OFBool weAllocated = OFFalse; ++ if (dataObject != NULL && *dataObject == NULL ++ && m_maxNestingDepth != 0) ++ { ++ *dataObject = new DcmDataset(); ++ (*dataObject)->setMaxNestingDepth(m_maxNestingDepth); ++ weAllocated = OFTrue; ++ } ++ + OFCondition cond; + /* call the corresponding DIMSE function to receive the dataset */ + if (m_progressNotificationMode) +@@ -2524,6 +2538,14 @@ + } + else + { ++ /* if we pre-allocated the dataset, clean up on error since ++ * DIMSE_receiveDataSetInMemory() won't delete a dataset ++ * that was passed in by the caller */ ++ if (weAllocated && dataObject != NULL) ++ { ++ delete *dataObject; ++ *dataObject = NULL; ++ } + OFString tempStr; + DCMNET_ERROR("Unable to receive dataset on presentation context " + << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond)); +@@ -2612,6 +2634,11 @@ + m_progressNotificationMode = mode; + } + ++void DcmSCU::setMaxNestingDepth(const Sint32 maxDepth) ++{ ++ m_maxNestingDepth = maxDepth; ++} ++ + void DcmSCU::setProtocolVersion(T_ASC_ProtocolFamily protocolVersion) + { + m_protocolVersion = protocolVersion; +@@ -2699,6 +2726,11 @@ + return m_progressNotificationMode; + } + ++Sint32 DcmSCU::getMaxNestingDepth() const ++{ ++ return m_maxNestingDepth; ++} ++ + OFCondition DcmSCU::getDatasetInfo(DcmDataset* dataset, + OFString& sopClassUID, + OFString& sopInstanceUID,
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue May 05 15:24:25 2026 +0200 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Wed May 06 12:40:49 2026 +0200 @@ -1248,6 +1248,11 @@ { assert(parent.type() == Json::objectValue); + if (depth >= 64) + { + throw OrthancException(ErrorCode_BadFileFormat, "Too many levels of nested Sequences (max 64)"); + } + for (unsigned long i = 0; i < item.card(); i++) { DcmElement* element = item.getElement(i);
