Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:dcmtk User: [email protected] Usertags: pu
Greetings Stable Release Managers, [ Reason ] dcmtk in trixie is currently affected by several security issues, namely: * CVE-2025-9732, described in #1113993, * CVE-2025-14607, described in #1122926, * CVE-2026-5663, described in #1133001, * CVE-2025-14841, described in #1123584 and * CVE-2026-10194, described in #1139181. They are all sorted as low-priority security issues, hence not coordinating directly with the Security Team. Yet having them fixed in the next stable release might be welcome. [ Impact ] If the update is not approved, then dcmtk will continue being affected by the above items. [ Tests ] I have ensured that the patches have caused no regressions in trixie using tests like piuparts and the embedded autopkgtest. I have also ensured that the reverse dependencies were not being affected by a regression in their autopkgtest if I introduce the patched library. [ Risks ] Changes brought to the code are not trivial to me, but I'm neither imaging specialist nor security specialist (or at least don't consider myself as such). Besides, mitigation of CVE-2025-9732 required an amendment upstream (see upstream commit 3de96da6c), which materializes here as patches 0013-CVE-2025-9732.patch and 0014-CVE-2025-9732b.patch. That being written, all the changes are part of the current dcmtk 3.7.0+really3.7.0-5 available in unstable and forky. I have also been mindful to make sure that the changes minimize alterations to the ABI. Apart perhaps from the introduction of a new constant static variable for listing admissible characters in sanitized file names, there should not be any breakage. Finally, all patches applied with minimal fuzz. The only rejects were caused by delta in changes to copyright years upstream. [ Checklist ] [*] *all* changes are documented in the d/changelog [*] I reviewed all changes and I approve them [*] attach debdiff against the package in (old)stable [*] the issue is verified as fixed in unstable [ Changes ] 0013-CVE-2025-9732.patch imports upstream commit 7ad81d69b, initially fixing CVE-2025-9732. 0014-CVE-2025-9732b.patch imports upstream commit 3de96da6c, which amends the previous patch with a fix needed for proper operation of the library. 0015-CVE-2025-14607.patch imports upstream commit 4c0e5c100, fixing CVE-2025-14607 by ensuring zero termination beyond the end of the string, necessary under certain conditions. 0016-CVE-2026-5663.patch imports upstream commit edbb085e4, fixing CVE-2026-5663 by making sure file names are converted to a safe subset of characters before processing. 0017-CVE-2025-14841.patch imports upstream commit ffb1a4a37, fixing CVE-2025-14841 by applying proper checks to ensure the pointer causing potential crashes is not NULL, or proceed appropriately. 0018-CVE-2026-10194.patch imports upstream commit 0f78a4ef6, fixing CVE-2026-10194 about a heap buffer overflow. Have a nice day, :) -- .''`. Étienne Mollier <[email protected]> : :' : pgp: 8f91 b227 c7d6 f2b1 948c 8236 793c f67e 8f0d 11da `. `' sent from /dev/pts/1, please excuse my verbosity `-
diff -Nru dcmtk-3.6.9/debian/changelog dcmtk-3.6.9/debian/changelog --- dcmtk-3.6.9/debian/changelog 2025-03-21 12:45:44.000000000 +0100 +++ dcmtk-3.6.9/debian/changelog 2026-06-11 20:54:58.000000000 +0200 @@ -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 <[email protected]> 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 01:00:00.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/0013-CVE-2025-9732.patch 2026-06-11 20:31:04.000000000 +0200 @@ -0,0 +1,398 @@ +commit 7ad81d69b19714936e18ea5fc74edaeb9f021ce7 +Author: Joerg Riesmeier <[email protected]> +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 <[email protected]> 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<T>::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<T>::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<T1> 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<T1> 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<T2>::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 01:00:00.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/0014-CVE-2025-9732b.patch 2026-06-11 20:38:22.000000000 +0200 @@ -0,0 +1,40 @@ +commit 3de96da6cd66b1af7224561c568bc3de50cd1398 +Author: Joerg Riesmeier <[email protected]> +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 01:00:00.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/0015-CVE-2025-14607.patch 2026-06-11 20:38:49.000000000 +0200 @@ -0,0 +1,31 @@ +commit 4c0e5c10079392c594d6a7abd95dd78ac0aa556a +Author: Marco Eichelberg <[email protected]> +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 <[email protected]> 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 01:00:00.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/0016-CVE-2026-5663.patch 2026-06-11 20:47:10.000000000 +0200 @@ -0,0 +1,231 @@ +commit edbb085e45788dccaf0e64d71534cfca925784b8 +Author: Marco Eichelberg <[email protected]> +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 01:00:00.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/0017-CVE-2025-14841.patch 2026-06-11 20:50:48.000000000 +0200 @@ -0,0 +1,37 @@ +commit ffb1a4a37d2c876e3feeb31df4930f2aed7fa030 +Author: Marco Eichelberg <[email protected]> +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 邹 迪凯 <[email protected]> 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 01:00:00.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/0018-CVE-2026-10194.patch 2026-06-11 20:53:44.000000000 +0200 @@ -0,0 +1,66 @@ +commit 0f78a4ef6f645ea5530166e445e5436a5de58e75 +Author: Marco Eichelberg <[email protected]> +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 12:45:44.000000000 +0100 +++ dcmtk-3.6.9/debian/patches/series 2026-06-11 20:53:55.000000000 +0200 @@ -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
signature.asc
Description: PGP signature

