Version in base suite: 3.6.9-5 Base version: dcmtk_3.6.9-5 Target version: dcmtk_3.6.9-5+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/d/dcmtk/dcmtk_3.6.9-5.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/d/dcmtk/dcmtk_3.6.9-5+deb13u1.dsc changelog | 13 + patches/0013-CVE-2025-9732.patch | 398 ++++++++++++++++++++++++++++++++++++++ patches/0014-CVE-2025-9732b.patch | 40 +++ patches/0015-CVE-2025-14607.patch | 31 ++ patches/0016-CVE-2026-5663.patch | 231 ++++++++++++++++++++++ patches/0017-CVE-2025-14841.patch | 37 +++ patches/0018-CVE-2026-10194.patch | 66 ++++++ patches/series | 6 8 files changed, 822 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp1iszikk4/dcmtk_3.6.9-5.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp1iszikk4/dcmtk_3.6.9-5+deb13u1.dsc: no acceptable signature found diff -Nru dcmtk-3.6.9/debian/changelog dcmtk-3.6.9/debian/changelog --- dcmtk-3.6.9/debian/changelog 2025-03-21 11:45:44.000000000 +0000 +++ dcmtk-3.6.9/debian/changelog 2026-06-11 18:54:58.000000000 +0000 @@ -1,3 +1,16 @@ +dcmtk (3.6.9-5+deb13u1) trixie; urgency=medium + + * Team upload + * d/patches/*-CVE-2025-9732.patch: new. + These changes pulled from dcmtk upstream address CVE-2025-9732. + (Closes: #1113993) + * 0015-CVE-2025-14607.patch: new: fix CVE-2025-14607. (Closes: #1122926) + * 0016-CVE-2026-5663.patch: new: fix CVE-2026-5663. (Closes: #1133001) + * 0017-CVE-2025-14841.patch: new: fix CVE-2025-14841. (Closes: #1123584) + * 0018-CVE-2026-10194.patch: new: fix CVE-2026-10194. (Closes: #1139181) + + -- Étienne Mollier Thu, 11 Jun 2026 20:54:58 +0200 + dcmtk (3.6.9-5) unstable; urgency=medium * d/control: relax dependency on dcmtk-data. Closes: #1098944 diff -Nru dcmtk-3.6.9/debian/patches/0013-CVE-2025-9732.patch dcmtk-3.6.9/debian/patches/0013-CVE-2025-9732.patch --- dcmtk-3.6.9/debian/patches/0013-CVE-2025-9732.patch 1970-01-01 00:00:00.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/0013-CVE-2025-9732.patch 2026-06-11 18:54:58.000000000 +0000 @@ -0,0 +1,398 @@ +commit 7ad81d69b19714936e18ea5fc74edaeb9f021ce7 +Author: Joerg Riesmeier +Date: Fri Aug 15 13:35:40 2025 +0200 + + Fixed issue with invalid "YBR_FULL" DICOM images. + + Fixed an issue when processing an invalid DICOM image with a Photometric + Interpretation of "YBR_FULL" and a Planar Configuration of "1" where + the number of pixels stored does not match the expected number of pixels + (much too less). Now, the pixel data of such an image is not processed + at all, but an empty image (black pixels) is created instead. The user + is warned about this by an appropriate log message. + + Thanks to Ding zhengzheng for the report + and the sample file (PoC). + +--- dcmtk.orig/dcmimage/include/dcmtk/dcmimage/dicopxt.h ++++ dcmtk/dcmimage/include/dcmtk/dcmimage/dicopxt.h +@@ -574,7 +574,11 @@ + { + /* erase empty part of the buffer (=blacken the background) */ + if (InputCount < Count) +- OFBitmanipTemplate::zeroMem(Data[j] + InputCount, Count - InputCount); ++ { ++ const size_t count = (Count - InputCount); ++ DCMIMAGE_TRACE("filing empty part of the intermediate pixel data (" << count << " pixels) of plane " << j << " with value = 0"); ++ OFBitmanipTemplate::zeroMem(Data[j] + InputCount, count); ++ } + } else { + DCMIMAGE_DEBUG("cannot allocate memory buffer for 'Data[" << j << "]' in DiColorPixelTemplate::Init()"); + result = 0; // at least one buffer could not be allocated! +--- dcmtk.orig/dcmimage/include/dcmtk/dcmimage/diybrpxt.h ++++ dcmtk/dcmimage/include/dcmtk/dcmimage/diybrpxt.h +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1998-2016, OFFIS e.V. ++ * Copyright (C) 1998-2025, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -24,6 +24,7 @@ + #define DIYBRPXT_H + + #include "dcmtk/config/osconfig.h" ++#include "dcmtk/ofstd/ofbmanip.h" + + #include "dcmtk/dcmimage/dicopxt.h" + #include "dcmtk/dcmimgle/diinpx.h" /* gcc 3.4 needs this */ +@@ -90,179 +91,189 @@ + // use the number of input pixels derived from the length of the 'PixelData' + // attribute), but not more than the size of the intermediate buffer + const unsigned long count = (this->InputCount < this->Count) ? this->InputCount : this->Count; +- if (rgb) /* convert to RGB model */ ++ // make sure that there is sufficient input data (for planar pixel data) ++ if (!this->PlanarConfiguration || (count >= planeSize * 3 /* number of planes */)) + { +- T2 *r = this->Data[0]; +- T2 *g = this->Data[1]; +- T2 *b = this->Data[2]; +- const T2 maxvalue = OFstatic_cast(T2, DicomImageClass::maxval(bits)); +- DiPixelRepresentationTemplate rep; +- if (bits == 8 && !rep.isSigned()) // only for unsigned 8 bit ++ if (rgb) /* convert to RGB model */ + { +- Sint16 rcr_tab[256]; +- Sint16 gcb_tab[256]; +- Sint16 gcr_tab[256]; +- Sint16 bcb_tab[256]; +- const double r_const = 0.7010 * OFstatic_cast(double, maxvalue); +- const double g_const = 0.5291 * OFstatic_cast(double, maxvalue); +- const double b_const = 0.8859 * OFstatic_cast(double, maxvalue); +- unsigned long l; +- for (l = 0; l < 256; ++l) ++ T2 *r = this->Data[0]; ++ T2 *g = this->Data[1]; ++ T2 *b = this->Data[2]; ++ const T2 maxvalue = OFstatic_cast(T2, DicomImageClass::maxval(bits)); ++ DiPixelRepresentationTemplate rep; ++ if (bits == 8 && !rep.isSigned()) // only for unsigned 8 bit + { +- rcr_tab[l] = OFstatic_cast(Sint16, 1.4020 * OFstatic_cast(double, l) - r_const); +- gcb_tab[l] = OFstatic_cast(Sint16, 0.3441 * OFstatic_cast(double, l)); +- gcr_tab[l] = OFstatic_cast(Sint16, 0.7141 * OFstatic_cast(double, l) - g_const); +- bcb_tab[l] = OFstatic_cast(Sint16, 1.7720 * OFstatic_cast(double, l) - b_const); +- } +- Sint32 sr; +- Sint32 sg; +- Sint32 sb; +- if (this->PlanarConfiguration) +- { +-/* +- const T1 *y = pixel; +- const T1 *cb = y + this->InputCount; +- const T1 *cr = cb + this->InputCount; +- for (i = count; i != 0; --i, ++y, ++cb, ++cr) ++ Sint16 rcr_tab[256]; ++ Sint16 gcb_tab[256]; ++ Sint16 gcr_tab[256]; ++ Sint16 bcb_tab[256]; ++ const double r_const = 0.7010 * OFstatic_cast(double, maxvalue); ++ const double g_const = 0.5291 * OFstatic_cast(double, maxvalue); ++ const double b_const = 0.8859 * OFstatic_cast(double, maxvalue); ++ unsigned long l; ++ for (l = 0; l < 256; ++l) + { +- sr = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, rcr_tab[*cr]); +- sg = OFstatic_cast(Sint32, *y) - OFstatic_cast(Sint32, gcb_tab[*cb]) - OFstatic_cast(Sint32, gcr_tab[*cr]); +- sb = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, bcb_tab[*cb]); +- *(r++) = (sr < 0) ? 0 : (sr > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sr); +- *(g++) = (sg < 0) ? 0 : (sg > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sg); +- *(b++) = (sb < 0) ? 0 : (sb > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sb); ++ rcr_tab[l] = OFstatic_cast(Sint16, 1.4020 * OFstatic_cast(double, l) - r_const); ++ gcb_tab[l] = OFstatic_cast(Sint16, 0.3441 * OFstatic_cast(double, l)); ++ gcr_tab[l] = OFstatic_cast(Sint16, 0.7141 * OFstatic_cast(double, l) - g_const); ++ bcb_tab[l] = OFstatic_cast(Sint16, 1.7720 * OFstatic_cast(double, l) - b_const); + } ++ Sint32 sr; ++ Sint32 sg; ++ Sint32 sb; ++ if (this->PlanarConfiguration) ++ { ++/* ++ const T1 *y = pixel; ++ const T1 *cb = y + this->InputCount; ++ const T1 *cr = cb + this->InputCount; ++ for (i = count; i != 0; --i, ++y, ++cb, ++cr) ++ { ++ sr = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, rcr_tab[*cr]); ++ sg = OFstatic_cast(Sint32, *y) - OFstatic_cast(Sint32, gcb_tab[*cb]) - OFstatic_cast(Sint32, gcr_tab[*cr]); ++ sb = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, bcb_tab[*cb]); ++ *(r++) = (sr < 0) ? 0 : (sr > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sr); ++ *(g++) = (sg < 0) ? 0 : (sg > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sg); ++ *(b++) = (sb < 0) ? 0 : (sb > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sb); ++ } + */ +- const T1 *y = pixel; +- const T1 *cb = y + planeSize; +- const T1 *cr = cb + planeSize; +- unsigned long i = count; +- while (i != 0) ++ const T1 *y = pixel; ++ const T1 *cb = y + planeSize; ++ const T1 *cr = cb + planeSize; ++ unsigned long i = count; ++ while (i != 0) ++ { ++ /* convert a single frame */ ++ for (l = planeSize; (l != 0) && (i != 0); --l, --i, ++y, ++cb, ++cr) ++ { ++ sr = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, rcr_tab[*cr]); ++ sg = OFstatic_cast(Sint32, *y) - OFstatic_cast(Sint32, gcb_tab[*cb]) - OFstatic_cast(Sint32, gcr_tab[*cr]); ++ sb = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, bcb_tab[*cb]); ++ *(r++) = (sr < 0) ? 0 : (sr > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sr); ++ *(g++) = (sg < 0) ? 0 : (sg > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sg); ++ *(b++) = (sb < 0) ? 0 : (sb > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sb); ++ } ++ /* jump to next frame start (skip 2 planes) */ ++ y += 2 * planeSize; ++ cb += 2 * planeSize; ++ cr += 2 * planeSize; ++ } ++ } ++ else + { +- /* convert a single frame */ +- for (l = planeSize; (l != 0) && (i != 0); --l, --i, ++y, ++cb, ++cr) ++ const T1 *p = pixel; ++ T1 y; ++ T1 cb; ++ T1 cr; ++ unsigned long i; ++ for (i = count; i != 0; --i) + { +- sr = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, rcr_tab[OFstatic_cast(Uint32, *cr)]); +- sg = OFstatic_cast(Sint32, *y) - OFstatic_cast(Sint32, gcb_tab[OFstatic_cast(Uint32, *cb)]) - OFstatic_cast(Sint32, gcr_tab[OFstatic_cast(Uint32, *cr)]); +- sb = OFstatic_cast(Sint32, *y) + OFstatic_cast(Sint32, bcb_tab[OFstatic_cast(Uint32, *cb)]); ++ y = *(p++); ++ cb = *(p++); ++ cr = *(p++); ++ sr = OFstatic_cast(Sint32, y) + OFstatic_cast(Sint32, rcr_tab[cr]); ++ sg = OFstatic_cast(Sint32, y) - OFstatic_cast(Sint32, gcb_tab[cb]) - OFstatic_cast(Sint32, gcr_tab[cr]); ++ sb = OFstatic_cast(Sint32, y) + OFstatic_cast(Sint32, bcb_tab[cb]); + *(r++) = (sr < 0) ? 0 : (sr > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sr); + *(g++) = (sg < 0) ? 0 : (sg > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sg); + *(b++) = (sb < 0) ? 0 : (sb > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sb); + } +- /* jump to next frame start (skip 2 planes) */ +- y += 2 * planeSize; +- cb += 2 * planeSize; +- cr += 2 * planeSize; + } + } + else + { +- const T1 *p = pixel; +- T1 y; +- T1 cb; +- T1 cr; +- unsigned long i; +- for (i = count; i != 0; --i) ++ if (this->PlanarConfiguration) ++ { ++/* ++ const T1 *y = pixel; ++ const T1 *cb = y + this->InputCount; ++ const T1 *cr = cb + this->InputCount; ++ for (i = count; i != 0; --i) ++ convertValue(*(r++), *(g++), *(b++), removeSign(*(y++), offset), removeSign(*(cb++), offset), ++ removeSign(*(cr++), offset), maxvalue); ++*/ ++ unsigned long l; ++ unsigned long i = count; ++ const T1 *y = pixel; ++ const T1 *cb = y + planeSize; ++ const T1 *cr = cb + planeSize; ++ while (i != 0) ++ { ++ /* convert a single frame */ ++ for (l = planeSize; (l != 0) && (i != 0); --l, --i) ++ { ++ convertValue(*(r++), *(g++), *(b++), removeSign(*(y++), offset), removeSign(*(cb++), offset), ++ removeSign(*(cr++), offset), maxvalue); ++ } ++ /* jump to next frame start (skip 2 planes) */ ++ y += 2 * planeSize; ++ cb += 2 * planeSize; ++ cr += 2 * planeSize; ++ } ++ } ++ else + { +- y = *(p++); +- cb = *(p++); +- cr = *(p++); +- sr = OFstatic_cast(Sint32, y) + OFstatic_cast(Sint32, rcr_tab[OFstatic_cast(Uint32, cr)]); +- sg = OFstatic_cast(Sint32, y) - OFstatic_cast(Sint32, gcb_tab[OFstatic_cast(Uint32, cb)]) - OFstatic_cast(Sint32, gcr_tab[OFstatic_cast(Uint32, cr)]); +- sb = OFstatic_cast(Sint32, y) + OFstatic_cast(Sint32, bcb_tab[OFstatic_cast(Uint32, cb)]); +- *(r++) = (sr < 0) ? 0 : (sr > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sr); +- *(g++) = (sg < 0) ? 0 : (sg > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sg); +- *(b++) = (sb < 0) ? 0 : (sb > OFstatic_cast(Sint32, maxvalue)) ? maxvalue : OFstatic_cast(T2, sb); ++ const T1 *p = pixel; ++ T2 y; ++ T2 cb; ++ T2 cr; ++ unsigned long i; ++ for (i = count; i != 0; --i) ++ { ++ y = removeSign(*(p++), offset); ++ cb = removeSign(*(p++), offset); ++ cr = removeSign(*(p++), offset); ++ convertValue(*(r++), *(g++), *(b++), y, cb, cr, maxvalue); ++ } + } + } +- } +- else +- { ++ } else { /* retain YCbCr model */ ++ const T1 *p = pixel; + if (this->PlanarConfiguration) + { +-/* +- const T1 *y = pixel; +- const T1 *cb = y + this->InputCount; +- const T1 *cr = cb + this->InputCount; +- for (i = count; i != 0; --i) +- convertValue(*(r++), *(g++), *(b++), removeSign(*(y++), offset), removeSign(*(cb++), offset), +- removeSign(*(cr++), offset), maxvalue); +-*/ ++ /* ++ T2 *q; ++ // number of pixels to be skipped (only applicable if 'PixelData' contains more ++ // pixels than expected) ++ const unsigned long skip = (this->InputCount > this->Count) ? (this->InputCount - this->Count) : 0; ++ for (int j = 0; j < 3; ++j) ++ { ++ q = this->Data[j]; ++ for (i = count; i != 0; --i) ++ *(q++) = removeSign(*(p++), offset); ++ // skip to beginning of next plane ++ p += skip; ++ } ++ */ + unsigned long l; +- unsigned long i = count; +- const T1 *y = pixel; +- const T1 *cb = y + planeSize; +- const T1 *cr = cb + planeSize; +- while (i != 0) ++ unsigned long i = 0; ++ while (i < count) + { +- /* convert a single frame */ +- for (l = planeSize; (l != 0) && (i != 0); --l, --i) ++ /* store current pixel index */ ++ const unsigned long iStart = i; ++ for (int j = 0; j < 3; ++j) + { +- convertValue(*(r++), *(g++), *(b++), removeSign(*(y++), offset), removeSign(*(cb++), offset), +- removeSign(*(cr++), offset), maxvalue); ++ /* convert a single plane */ ++ for (l = planeSize, i = iStart; (l != 0) && (i < count); --l, ++i) ++ this->Data[j][i] = removeSign(*(p++), offset); + } +- /* jump to next frame start (skip 2 planes) */ +- y += 2 * planeSize; +- cb += 2 * planeSize; +- cr += 2 * planeSize; + } + } + else + { +- const T1 *p = pixel; +- T2 y; +- T2 cb; +- T2 cr; ++ int j; + unsigned long i; +- for (i = count; i != 0; --i) +- { +- y = removeSign(*(p++), offset); +- cb = removeSign(*(p++), offset); +- cr = removeSign(*(p++), offset); +- convertValue(*(r++), *(g++), *(b++), y, cb, cr, maxvalue); +- } ++ for (i = 0; i < count; ++i) /* for all pixel ... */ ++ for (j = 0; j < 3; ++j) ++ this->Data[j][i] = removeSign(*(p++), offset); /* ... copy planes */ + } + } +- } else { /* retain YCbCr model */ +- const T1 *p = pixel; +- if (this->PlanarConfiguration) +- { +-/* +- T2 *q; +- // number of pixels to be skipped (only applicable if 'PixelData' contains more +- // pixels than expected) +- const unsigned long skip = (this->InputCount > this->Count) ? (this->InputCount - this->Count) : 0; +- for (int j = 0; j < 3; ++j) +- { +- q = this->Data[j]; +- for (i = count; i != 0; --i) +- *(q++) = removeSign(*(p++), offset); +- // skip to beginning of next plane +- p += skip; +- } +-*/ +- unsigned long l; +- unsigned long i = 0; +- while (i < count) +- { +- /* store current pixel index */ +- const unsigned long iStart = i; +- for (int j = 0; j < 3; ++j) +- { +- /* convert a single plane */ +- for (l = planeSize, i = iStart; (l != 0) && (i < count); --l, ++i) +- this->Data[j][i] = removeSign(*(p++), offset); +- } +- } +- } +- else +- { +- int j; +- unsigned long i; +- for (i = 0; i < count; ++i) /* for all pixel ... */ +- for (j = 0; j < 3; ++j) +- this->Data[j][i] = removeSign(*(p++), offset); /* ... copy planes */ +- } ++ } else { ++ // do not process the input data, as it is too short ++ DCMIMAGE_WARN("input data is too short, filling the complete image with black pixels"); ++ // erase empty part of the buffer (that has not been "blackened" yet) ++ for (int j = 0; j < 3; ++j) ++ OFBitmanipTemplate::zeroMem(this->Data[j], count); + } + } + } +--- dcmtk.orig/dcmimgle/libsrc/dcmimage.cc ++++ dcmtk/dcmimgle/libsrc/dcmimage.cc +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1996-2024, OFFIS e.V. ++ * Copyright (C) 1996-2025, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -210,6 +210,7 @@ + *(q++) = c; + } + *q = '\0'; // end of C string ++ DCMIMGLE_DEBUG("filtered version of 'PhotometricInterpretation' = " << OFSTRING_GUARD(cstr)); + while ((pin->Name != NULL) && (strcmp(pin->Name, cstr) != 0)) + ++pin; + delete[] cstr; diff -Nru dcmtk-3.6.9/debian/patches/0014-CVE-2025-9732b.patch dcmtk-3.6.9/debian/patches/0014-CVE-2025-9732b.patch --- dcmtk-3.6.9/debian/patches/0014-CVE-2025-9732b.patch 1970-01-01 00:00:00.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/0014-CVE-2025-9732b.patch 2026-06-11 18:54:58.000000000 +0000 @@ -0,0 +1,40 @@ +commit 3de96da6cd66b1af7224561c568bc3de50cd1398 +Author: Joerg Riesmeier +Date: Mon Aug 18 17:58:56 2025 +0200 + + Fixed issue with commit 7ad81d69b. + + Fixed an issue with recently committed changes that fix a problem with + invalid YBR_FULL images + +diff --git a/dcmimage/include/dcmtk/dcmimage/diybrpxt.h b/dcmimage/include/dcmtk/dcmimage/diybrpxt.h +index c5415c149..fdaaafc2d 100644 +--- a/dcmimage/include/dcmtk/dcmimage/diybrpxt.h ++++ b/dcmimage/include/dcmtk/dcmimage/diybrpxt.h +@@ -92,7 +92,7 @@ class DiYBRPixelTemplate + // attribute), but not more than the size of the intermediate buffer + const unsigned long count = (this->InputCount < this->Count) ? this->InputCount : this->Count; + // make sure that there is sufficient input data (for planar pixel data) +- if (!this->PlanarConfiguration || (count >= planeSize * 3 /* number of planes */)) ++ if (!this->PlanarConfiguration || (count >= planeSize)) + { + if (rgb) /* convert to RGB model */ + { +@@ -231,7 +231,7 @@ class DiYBRPixelTemplate + const T1 *p = pixel; + if (this->PlanarConfiguration) + { +- /* ++/* + T2 *q; + // number of pixels to be skipped (only applicable if 'PixelData' contains more + // pixels than expected) +@@ -244,7 +244,7 @@ class DiYBRPixelTemplate + // skip to beginning of next plane + p += skip; + } +- */ ++*/ + unsigned long l; + unsigned long i = 0; + while (i < count) diff -Nru dcmtk-3.6.9/debian/patches/0015-CVE-2025-14607.patch dcmtk-3.6.9/debian/patches/0015-CVE-2025-14607.patch --- dcmtk-3.6.9/debian/patches/0015-CVE-2025-14607.patch 1970-01-01 00:00:00.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/0015-CVE-2025-14607.patch 2026-06-11 18:54:58.000000000 +0000 @@ -0,0 +1,31 @@ +commit 4c0e5c10079392c594d6a7abd95dd78ac0aa556a +Author: Marco Eichelberg +Date: Tue Dec 2 09:06:30 2025 +0100 + + Fixed bug in handling of odd-length data elements. + + When a dataset containing an illegal odd-length attribute with a text VR + was read from file or received over a network connection, then accessing + the value of that attribute with DcmElement::getString() may return a + pointer to a string that was not properly null terminated. Using C string + functions such as strlen() or strcpy() on that string then lead to a read + beyond the end of a string, causing a segmentation fault. + + Thanks to Zou Dikai for the bug report and POC. + + This closes DCMTK issue #1184. + +--- dcmtk.orig/dcmdata/libsrc/dcbytstr.cc ++++ dcmtk/dcmdata/libsrc/dcbytstr.cc +@@ -658,7 +658,11 @@ + + /* terminate string after real length */ + if (value != NULL) ++ { + value[lengthField] = 0; ++ value[lengthField+1] = 0; ++ } ++ + /* enforce old (pre DCMTK 3.5.2) behaviour? */ + if (!dcmAcceptOddAttributeLength.get()) + { diff -Nru dcmtk-3.6.9/debian/patches/0016-CVE-2026-5663.patch dcmtk-3.6.9/debian/patches/0016-CVE-2026-5663.patch --- dcmtk-3.6.9/debian/patches/0016-CVE-2026-5663.patch 1970-01-01 00:00:00.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/0016-CVE-2026-5663.patch 2026-06-11 18:54:58.000000000 +0000 @@ -0,0 +1,231 @@ +commit edbb085e45788dccaf0e64d71534cfca925784b8 +Author: Marco Eichelberg +Date: Sat Mar 21 18:35:14 2026 +0100 + + Sanitize all strings passed to the exec options. + + Sanitize the text fields from incoming DICOM associations and DICOM objects + (such as Study Instance UID, SOP Instance UID, Patient's Name) and the + calling SCU's network presentation address by removing special characters + that may be interpreted as shell escape characters when one of the + execution options (e.g. --exec-on-reception) is in use. + + Thanks to Machine Spirits UG (haftungsbeschränkt) for the bug report, + detailed analysis and proof of concept. + + This closes DCMTK issue #1194. + +--- dcmtk.orig/dcmnet/apps/storescp.cc ++++ dcmtk/dcmnet/apps/storescp.cc +@@ -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 +@@ -1493,7 +1493,9 @@ + calledAETitle.clear(); + } + // store calling presentation address (i.e. remote hostname) +- callingPresentationAddress = OFSTRING_GUARD(assoc->params->DULparams.callingPresentationAddress); ++ callingPresentationAddress = "\""; ++ callingPresentationAddress += OFSTRING_GUARD(assoc->params->DULparams.callingPresentationAddress); ++ callingPresentationAddress += "\""; + + /* now do the real work, i.e. receive DIMSE commands over the network connection */ + /* which was established and handle these commands correspondingly. In case of */ +@@ -1857,6 +1859,7 @@ + dateTime.getTime().getHour(), dateTime.getTime().getMinute(), dateTime.getTime().getIntSecond(), dateTime.getTime().getMilliSecond()); + + OFString subdirectoryName; ++ OFString s; + switch (opt_sortStudyMode) + { + case ESM_Timestamp: +@@ -1871,15 +1874,27 @@ + subdirectoryName = opt_sortStudyDirPrefix; + if (!subdirectoryName.empty()) + subdirectoryName += '_'; +- subdirectoryName += currentStudyInstanceUID; +- OFStandard::sanitizeFilename(subdirectoryName); ++ s = currentStudyInstanceUID; ++ OFStandard::sanitizeFilename(s); ++ if (s != currentStudyInstanceUID) ++ { ++ OFLOG_WARN(storescpLogger, "Sanitized unusual characters in Study Instance UID, converted from \"" << currentStudyInstanceUID << "\" to \"" << s << "\"."); ++ } ++ subdirectoryName += s; + break; + case ESM_PatientName: + // pattern: "[Patient's Name]_[YYYYMMDD]_[HHMMSSMMM]" + subdirectoryName = currentPatientName; ++ OFStandard::sanitizeFilename(subdirectoryName); ++ if (subdirectoryName != currentPatientName) ++ { ++ // It is quite normal that we need to sanitize characters in PatientName. ++ // Therefore, this is only a debug message and not a warning, unlike the other ++ // messages about sanitized fields, which are normally not expected. ++ OFLOG_DEBUG(storescpLogger, "Sanitized characters in Patient Name, converted from \"" << currentPatientName << "\" to \"" << subdirectoryName << "\"."); ++ } + subdirectoryName += '_'; + subdirectoryName += timestamp; +- OFStandard::sanitizeFilename(subdirectoryName); + break; + case ESM_None: + break; +@@ -2088,8 +2103,13 @@ + else + { + // Use the SOP instance UID as found in the C-STORE request message as part of the filename +- OFString uid(OFSTRING_GUARD(req->AffectedSOPInstanceUID)); ++ OFString s(OFSTRING_GUARD(req->AffectedSOPInstanceUID)); ++ OFString uid = s; + OFStandard::sanitizeFilename(uid); ++ if (uid != s) ++ { ++ OFLOG_WARN(storescpLogger, "Sanitized unusual characters in SOP Instance UID, converted from \"" << s << "\" to \"" << uid << "\"."); ++ } + OFStandard::snprintf(imageFileName, sizeof(imageFileName), "%s%c%s.%s%s", opt_outputDirectory.c_str(), PATH_SEPARATOR, dcmSOPClassUIDToModality(req->AffectedSOPClassUID, "UNKNOWN"), + uid.c_str(), opt_fileNameExtension.c_str()); + } +@@ -2259,16 +2279,19 @@ + if( !opt_ignore ) + { + // perform substitution for placeholder #p (depending on presence of any --sort-xxx option) ++ // Note: We do not enclose this in quotes because it may be used as part of a path expression. + OFString dir = (opt_sortStudyMode == ESM_None) ? opt_outputDirectory : subdirectoryPathAndName; + cmd = replaceChars( cmd, OFString(PATH_PLACEHOLDER), dir ); + + // perform substitution for placeholder #f; note that outputFileNameArray.back() + // always contains the name of the file (without path) which was written last. ++ // Note: We do not enclose this in quotes because it may be used as part of a path expression. + OFString outputFileName = outputFileNameArray.back(); + cmd = replaceChars( cmd, OFString(FILENAME_PLACEHOLDER), outputFileName ); + } + +- // perform substitution for placeholder #a ++ // perform substitution for placeholder #a. ++ // Note that this string is already enclosed in double quotes at this point + s = callingAETitle; + sanitizeAETitle(s); + if (s != callingAETitle) +@@ -2277,7 +2300,8 @@ + } + cmd = replaceChars( cmd, OFString(CALLING_AETITLE_PLACEHOLDER), s ); + +- // perform substitution for placeholder #c ++ // perform substitution for placeholder #c. ++ // Note that this string is already enclosed in double quotes at this point + s = calledAETitle; + sanitizeAETitle(s); + if (s != calledAETitle) +@@ -2286,8 +2310,15 @@ + } + cmd = replaceChars( cmd, OFString(CALLED_AETITLE_PLACEHOLDER), s ); + +- // perform substitution for placeholder #r +- cmd = replaceChars( cmd, OFString(CALLING_PRESENTATION_ADDRESS_PLACEHOLDER), callingPresentationAddress ); ++ // perform substitution for placeholder #r. ++ // Note that this string is already enclosed in double quotes at this point ++ s = callingPresentationAddress; ++ sanitizeAETitle(s); ++ if (s != callingPresentationAddress) ++ { ++ OFLOG_WARN(storescpLogger, "Sanitized unusual characters in calling presentation address, converted from " << callingPresentationAddress << " to " << s << "."); ++ } ++ cmd = replaceChars( cmd, OFString(CALLING_PRESENTATION_ADDRESS_PLACEHOLDER), s ); + + // Execute command in a new process + executeCommand( cmd ); +@@ -2392,20 +2423,38 @@ + OFString s; + + // perform substitution for placeholder #p; #p will be substituted by lastStudySubdirectoryPathAndName ++ // Note: We do not enclose this in quotes because it may be used as part of a path expression. + cmd = replaceChars( cmd, OFString(PATH_PLACEHOLDER), lastStudySubdirectoryPathAndName ); + +- // perform substitution for placeholder #a ++ // perform substitution for placeholder #a. ++ // Note that this string is already enclosed in double quotes at this point + s = callingAETitle; + sanitizeAETitle(s); ++ if (s != callingAETitle) ++ { ++ OFLOG_WARN(storescpLogger, "Sanitized unusual characters in calling aetitle, converted from " << callingAETitle << " to " << s << "."); ++ } + cmd = replaceChars( cmd, OFString(CALLING_AETITLE_PLACEHOLDER), s ); + +- // perform substitution for placeholder #c ++ // perform substitution for placeholder #c. ++ // Note that this string is already enclosed in double quotes at this point + s = calledAETitle; + sanitizeAETitle(s); ++ if (s != calledAETitle) ++ { ++ OFLOG_WARN(storescpLogger, "Sanitized unusual characters in called aetitle, converted from " << calledAETitle << " to " << s << "."); ++ } + cmd = replaceChars( cmd, OFString(CALLED_AETITLE_PLACEHOLDER), s ); + +- // perform substitution for placeholder #r +- cmd = replaceChars( cmd, OFString(CALLING_PRESENTATION_ADDRESS_PLACEHOLDER), callingPresentationAddress ); ++ // perform substitution for placeholder #r. ++ // Note that this string is already enclosed in double quotes at this point ++ s = callingPresentationAddress; ++ sanitizeAETitle(s); ++ if (s != callingPresentationAddress) ++ { ++ OFLOG_WARN(storescpLogger, "Sanitized unusual characters in calling presentation address, converted from " << callingPresentationAddress << " to " << s << "."); ++ } ++ cmd = replaceChars( cmd, OFString(CALLING_PRESENTATION_ADDRESS_PLACEHOLDER), s ); + + // Execute command in a new process + executeCommand( cmd ); +--- dcmtk.orig/ofstd/libsrc/ofstd.cc ++++ dcmtk/ofstd/libsrc/ofstd.cc +@@ -3462,16 +3462,26 @@ + } + + ++static const char sanitized_filename_charset[] = ++{ ++ ' ', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '-', '.', '_', ++ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '_', '_', '_', '_', '_', ++ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', ++ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '_', '_', '_', '_', ++ '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', ++ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_', '_', '_', '_', '_' ++}; ++ ++ + void OFStandard::sanitizeFilename(OFString& fname) + { + const size_t len = fname.length(); ++ char c; + for (size_t i = 0; i < len; ++i) + { +-#ifdef _WIN32 +- if ((fname[i] == PATH_SEPARATOR) || (fname[i] == '/')) fname[i] = '_'; +-#else +- if (fname[i] == PATH_SEPARATOR) fname[i] = '_'; +-#endif ++ c = fname[i]; ++ if (c != 0 && (c < 32 || c >= 127)) c = '_'; else c = sanitized_filename_charset[c-32]; ++ fname[i] = c; + } + } + +@@ -3483,11 +3493,7 @@ + char *c = fname; + while (*c) + { +-#ifdef _WIN32 +- if ((*c == PATH_SEPARATOR) || (*c == '/')) *c = '_'; +-#else +- if (*c == PATH_SEPARATOR) *c = '_'; +-#endif ++ if (*c < 32 || *c >= 127) *c = '_'; else *c = sanitized_filename_charset[*c-32]; + ++c; + } + } diff -Nru dcmtk-3.6.9/debian/patches/0017-CVE-2025-14841.patch dcmtk-3.6.9/debian/patches/0017-CVE-2025-14841.patch --- dcmtk-3.6.9/debian/patches/0017-CVE-2025-14841.patch 1970-01-01 00:00:00.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/0017-CVE-2025-14841.patch 2026-06-11 18:54:58.000000000 +0000 @@ -0,0 +1,37 @@ +commit ffb1a4a37d2c876e3feeb31df4930f2aed7fa030 +Author: Marco Eichelberg +Date: Fri Nov 28 12:24:07 2025 +0100 + + Fixed two possible segfaults in dcmqrscp. + + Fixed two places where invalid messages may trigger a segmentation fault + due to a NULL pointer being de-referenced. + + Thanks to 邹 迪凯 for the bug report and proof-of-concept. + +--- dcmtk.orig/dcmqrdb/libsrc/dcmqrdbi.cc ++++ dcmtk/dcmqrdb/libsrc/dcmqrdbi.cc +@@ -1381,8 +1381,10 @@ + /* only char string type tags are supported at the moment */ + char *s = NULL; + dcelem->getString(s); ++ + /* the available space is always elem.ValueLength+1 */ +- OFStandard::strlcpy(elem.PValueField, s, elem.ValueLength+1); ++ if (s) OFStandard::strlcpy(elem.PValueField, s, elem.ValueLength+1); ++ else elem.PValueField[0]='\0'; + } + /** If element is the Query Level, store it in handle + */ +@@ -2066,8 +2068,10 @@ + /* only char string type tags are supported at the moment */ + char *s = NULL; + dcelem->getString(s); ++ + /* the available space is always elem.ValueLength+1 */ +- OFStandard::strlcpy(elem.PValueField, s, elem.ValueLength+1); ++ if (s) OFStandard::strlcpy(elem.PValueField, s, elem.ValueLength+1); ++ else elem.PValueField[0]='\0'; + } + + /** If element is the Query Level, store it in handle diff -Nru dcmtk-3.6.9/debian/patches/0018-CVE-2026-10194.patch dcmtk-3.6.9/debian/patches/0018-CVE-2026-10194.patch --- dcmtk-3.6.9/debian/patches/0018-CVE-2026-10194.patch 1970-01-01 00:00:00.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/0018-CVE-2026-10194.patch 2026-06-11 18:54:58.000000000 +0000 @@ -0,0 +1,66 @@ +commit 0f78a4ef6f645ea5530166e445e5436a5de58e75 +Author: Marco Eichelberg +Date: Mon May 4 17:48:30 2026 +0200 + + Fixed remote heap buffer overflow in dcmqrscp. + + Thanks to 'elp3pinill0' for the bug report, detailed + analysis, proof of concept and proposed fix. + + This closes DCMTK issue #1206. + +--- dcmtk.orig/dcmqrdb/libsrc/dcmqrdbi.cc ++++ dcmtk/dcmqrdb/libsrc/dcmqrdbi.cc +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1993-2024, OFFIS e.V. ++ * Copyright (C) 1993-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -2475,12 +2475,16 @@ + + DB_IdxInitLoop (&(handle_ -> idxCounter)) ; + while ( DB_IdxGetNext(&(handle_ -> idxCounter), &idxRec) == EC_Normal ) { +- if ( ! ( strncmp(idxRec. StudyInstanceUID, StudyUID, n) ) ) { +- +- StudyArray[nbimages]. idxCounter = handle_ -> idxCounter ; +- StudyArray[nbimages]. RecordedDate = idxRec. RecordedDate ; +- StudyArray[nbimages++]. ImageSize = idxRec. ImageSize ; +- } ++ if ( ! ( strncmp(idxRec. StudyInstanceUID, StudyUID, n) ) ) { ++ StudyArray[nbimages]. idxCounter = handle_ -> idxCounter ; ++ StudyArray[nbimages]. RecordedDate = idxRec. RecordedDate ; ++ StudyArray[nbimages++]. ImageSize = idxRec. ImageSize ; ++ if (nbimages == MAX_NUMBER_OF_IMAGES) { ++ // too many images in this study, bail out ++ DCMQRDB_ERROR("maximum number of images per study (" << MAX_NUMBER_OF_IMAGES << ") exceeded"); ++ return QR_EC_IndexDatabaseError; ++ } ++ } + } + + /** Sort the StudyArray in order to have the oldest images first +@@ -2567,6 +2571,8 @@ + s = matchStudyUIDInStudyDesc (pStudyDesc, StudyUID, + (int)(handle_ -> maxStudiesAllowed)) ; + ++ OFCondition cond; ++ + /** If Study already exists + */ + +@@ -2587,10 +2593,10 @@ + + RequiredSize = imageSize - + ( handle_ -> maxBytesPerStudy - pStudyDesc[s]. StudySize ) ; +- deleteOldestImages(pStudyDesc, s, StudyUID, RequiredSize) ; ++ cond = deleteOldestImages(pStudyDesc, s, StudyUID, RequiredSize) ; ++ if (cond.bad()) return cond; + } + +- + } + else { + #ifdef DEBUG diff -Nru dcmtk-3.6.9/debian/patches/series dcmtk-3.6.9/debian/patches/series --- dcmtk-3.6.9/debian/patches/series 2025-03-21 11:45:44.000000000 +0000 +++ dcmtk-3.6.9/debian/patches/series 2026-06-11 18:54:58.000000000 +0000 @@ -8,3 +8,9 @@ 0010-CVE-2025-25474.patch 0011-CVE-2025-25472.patch 0012-CVE-2025-2357.patch +0013-CVE-2025-9732.patch +0014-CVE-2025-9732b.patch +0015-CVE-2025-14607.patch +0016-CVE-2026-5663.patch +0017-CVE-2025-14841.patch +0018-CVE-2026-10194.patch