CVE-2026-5663
authorDebian Med Packaging Team <debian-med-packaging@lists.alioth.debian.org>
Tue, 23 Jun 2026 19:44:21 +0000 (21:44 +0200)
committerÉtienne Mollier <emollier@debian.org>
Tue, 23 Jun 2026 19:44:21 +0000 (21:44 +0200)
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.

Gbp-Pq: Name 0016-CVE-2026-5663.patch

dcmnet/apps/storescp.cc
ofstd/libsrc/ofstd.cc

index 8969c92e3d63836d6cf69defee8167501ff92147..05f0d5850c4df96aa275428f2bf2de7be4c94908 100644 (file)
@@ -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 @@ static OFCondition acceptAssociation(T_ASC_Network *net, DcmAssociationConfigura
     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 @@ storeSCPCallback(
             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 @@ storeSCPCallback(
               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 @@ static OFCondition storeSCP(
     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 @@ static void executeOnReception()
   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 @@ static void executeOnReception()
   }
   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 @@ static void executeOnReception()
   }
   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 @@ static void executeOnEndOfStudy()
   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 );
index 748932ad47e42cdf68a940097c10ef8326139816..d7189a0bdd9f8519991d25d926e1b0cd30bca965 100644 (file)
@@ -3462,16 +3462,26 @@ void OFStandard::forceSleep(Uint32 seconds)
 }
 
 
+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 @@ void OFStandard::sanitizeFilename(char *fname)
         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;
         }
     }