0016-CVE-2026-5663.patch: new: fix CVE-2026-5663.
authorÉtienne Mollier <emollier@debian.org>
Thu, 11 Jun 2026 18:47:58 +0000 (20:47 +0200)
committerÉtienne Mollier <emollier@debian.org>
Thu, 11 Jun 2026 18:48:12 +0000 (20:48 +0200)
Closes: #1133001
debian/patches/0016-CVE-2026-5663.patch [new file with mode: 0644]
debian/patches/series

diff --git a/debian/patches/0016-CVE-2026-5663.patch b/debian/patches/0016-CVE-2026-5663.patch
new file mode 100644 (file)
index 0000000..8e1c5b5
--- /dev/null
@@ -0,0 +1,231 @@
+commit edbb085e45788dccaf0e64d71534cfca925784b8
+Author: Marco Eichelberg <eichelberg@offis.de>
+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;
+         }
+     }
index c154368b83d9240b30310f6157c05659d199fd36..0cad16a3744a2473f35a0778f3d2876e4535effb 100644 (file)
@@ -11,3 +11,4 @@ remove_version.patch
 0013-CVE-2025-9732.patch
 0014-CVE-2025-9732b.patch
 0015-CVE-2025-14607.patch
+0016-CVE-2026-5663.patch