summaryrefslogtreecommitdiffstats
path: root/win/tclWinPipe.c
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2018-08-29 15:41:55 (GMT)
committersebres <sebres@users.sourceforge.net>2018-08-29 15:41:55 (GMT)
commitafb5bf558344f2985346b88580f4b9712302991f (patch)
tree03b8512e588f97b5d861046f0f3e7fe9039fea91 /win/tclWinPipe.c
parent89ff80fbdd973caecbbaf87cc772f19ed369cc50 (diff)
parent9b7ce068fe00fb30818dff8934c1417a482a4a12 (diff)
downloadtcl-afb5bf558344f2985346b88580f4b9712302991f.zip
tcl-afb5bf558344f2985346b88580f4b9712302991f.tar.gz
tcl-afb5bf558344f2985346b88580f4b9712302991f.tar.bz2
merge 8.5 ([21b0629c81] 0-day vulnerability - insufficient escape by exec of batch-files for windows)
Diffstat (limited to 'win/tclWinPipe.c')
-rw-r--r--win/tclWinPipe.c233
1 files changed, 181 insertions, 52 deletions
diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c
index 4b372a5..be9d920 100644
--- a/win/tclWinPipe.c
+++ b/win/tclWinPipe.c
@@ -1289,7 +1289,7 @@ ApplicationType(
/*
* Look for the program as an external program. First try the name as it
- * is, then try adding .com, .exe, and .bat, in that order, to the name,
+ * is, then try adding .com, .exe, .bat and .cmd, in that order, to the name,
* looking for an executable.
*
* Using the raw SearchPath() function doesn't do quite what is necessary.
@@ -1408,7 +1408,7 @@ ApplicationType(
return APPL_NONE;
}
- if ((applType == APPL_DOS) || (applType == APPL_WIN3X)) {
+ if (applType == APPL_WIN3X) {
/*
* Replace long path name of executable with short path name for
* 16-bit applications. Otherwise the application may not be able to
@@ -1442,6 +1442,86 @@ ApplicationType(
*----------------------------------------------------------------------
*/
+static const char *
+BuildCmdLineBypassBS(
+ const char *current,
+ const char **bspos
+) {
+ /* mark first backslash possition */
+ if (!*bspos) {
+ *bspos = current;
+ }
+ do {
+ current++;
+ } while (*current == '\\');
+ return current;
+}
+
+static void
+QuoteCmdLineBackslash(
+ Tcl_DString *dsPtr,
+ const char *start,
+ const char *current,
+ const char *bspos
+) {
+ if (!bspos) {
+ if (current > start) { /* part before current (special) */
+ Tcl_DStringAppend(dsPtr, start, (int) (current - start));
+ }
+ } else {
+ if (bspos > start) { /* part before first backslash */
+ Tcl_DStringAppend(dsPtr, start, (int) (bspos - start));
+ }
+ while (bspos++ < current) { /* each backslash twice */
+ Tcl_DStringAppend(dsPtr, "\\\\", 2);
+ }
+ }
+}
+
+static const char *
+QuoteCmdLinePart(
+ Tcl_DString *dsPtr,
+ const char *start,
+ const char *special,
+ const char *specMetaChars,
+ const char **bspos
+) {
+ if (!*bspos) {
+ /* rest before special (before quote) */
+ QuoteCmdLineBackslash(dsPtr, start, special, NULL);
+ start = special;
+ } else {
+ /* rest before first backslash and backslashes into new quoted block */
+ QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL);
+ start = *bspos;
+ }
+ /*
+ * escape all special chars enclosed in quotes like `"..."`, note that here we
+ * don't must escape `\` (with `\`), because it's outside of the main quotes,
+ * so `\` remains `\`, but important - not at end of part, because results as
+ * before the quote, so `%\%\` should be escaped as `"%\%"\\`).
+ */
+ Tcl_DStringAppend(dsPtr, "\"", 1); /* opening escape quote-char */
+ do {
+ *bspos = NULL;
+ special++;
+ if (*special == '\\') {
+ /* bypass backslashes (and mark first backslash possition)*/
+ special = BuildCmdLineBypassBS(special, bspos);
+ if (*special == '\0') break;
+ }
+ } while (*special && strchr(specMetaChars, *special));
+ if (!*bspos) {
+ /* unescaped rest before quote */
+ QuoteCmdLineBackslash(dsPtr, start, special, NULL);
+ } else {
+ /* unescaped rest before first backslash (rather belongs to the main block) */
+ QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL);
+ }
+ Tcl_DStringAppend(dsPtr, "\"", 1); /* closing escape quote-char */
+ return special;
+}
+
static void
BuildCommandLine(
const char *executable, /* Full path of executable (including
@@ -1451,19 +1531,31 @@ BuildCommandLine(
Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the
* command line (TCHAR). */
{
- const char *arg, *start, *special;
- int quote, i;
+ const char *arg, *start, *special, *bspos;
+ int quote = 0, i;
Tcl_DString ds;
+ /* characters to enclose in quotes if unpaired quote flag set */
+ const static char *specMetaChars = "&|^<>!()%";
+ /* characters to enclose in quotes in any case (regardless unpaired-flag) */
+ const static char *specMetaChars2 = "%";
+
+ /* Quote flags:
+ * CL_ESCAPE - escape argument;
+ * CL_QUOTE - enclose in quotes;
+ * CL_UNPAIRED - previous arguments chain contains unpaired quote-char;
+ */
+ enum {CL_ESCAPE = 1, CL_QUOTE = 2, CL_UNPAIRED = 4};
+
Tcl_DStringInit(&ds);
/*
* Prime the path. Add a space separator if we were primed with something.
*/
- TclDStringAppendDString(&ds, linePtr);
+ Tcl_DStringAppend(&ds, Tcl_DStringValue(linePtr), -1);
if (Tcl_DStringLength(linePtr) > 0) {
- TclDStringAppendLiteral(&ds, " ");
+ Tcl_DStringAppend(&ds, " ", 1);
}
for (i = 0; i < argc; i++) {
@@ -1471,65 +1563,102 @@ BuildCommandLine(
arg = executable;
} else {
arg = argv[i];
- TclDStringAppendLiteral(&ds, " ");
+ Tcl_DStringAppend(&ds, " ", 1);
}
- quote = 0;
+ quote &= ~(CL_ESCAPE|CL_QUOTE); /* reset escape flags */
+ bspos = NULL;
if (arg[0] == '\0') {
- quote = 1;
+ quote = CL_QUOTE;
} else {
int count;
- Tcl_UniChar ch = 0;
-
- for (start = arg; *start != '\0'; start += count) {
- count = TclUtfToUniChar(start, &ch);
- if (Tcl_UniCharIsSpace(ch)) { /* INTL: ISO space. */
- quote = 1;
- break;
- }
- }
- }
- if (quote) {
- TclDStringAppendLiteral(&ds, "\"");
- }
- start = arg;
- for (special = arg; ; ) {
- if ((*special == '\\') && (special[1] == '\\' ||
- special[1] == '"' || (quote && special[1] == '\0'))) {
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- start = special;
- while (1) {
- special++;
- if (*special == '"' || (quote && *special == '\0')) {
- /*
- * N backslashes followed a quote -> insert N * 2 + 1
- * backslashes then a quote.
- */
-
- Tcl_DStringAppend(&ds, start,
- (int) (special - start));
+ Tcl_UniChar ch;
+ for (start = arg;
+ *start != '\0' &&
+ (quote & (CL_ESCAPE|CL_QUOTE)) != (CL_ESCAPE|CL_QUOTE);
+ start += count
+ ) {
+ count = Tcl_UtfToUniChar(start, &ch);
+ if (count > 1) continue;
+ if (Tcl_UniCharIsSpace(ch)) {
+ quote |= CL_QUOTE; /* quote only */
+ if (bspos) { /* if backslash found - escape & quote */
+ quote |= CL_ESCAPE;
break;
}
- if (*special != '\\') {
+ continue;
+ }
+ if (strchr(specMetaChars, *start)) {
+ quote |= (CL_ESCAPE|CL_QUOTE); /*escape & quote */
+ break;
+ }
+ if (*start == '"') {
+ quote |= CL_ESCAPE; /* escape only */
+ continue;
+ }
+ if (*start == '\\') {
+ bspos = start;
+ if (quote & CL_QUOTE) { /* if quote - escape & quote */
+ quote |= CL_ESCAPE;
break;
}
+ continue;
}
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- start = special;
- }
- if (*special == '"') {
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- TclDStringAppendLiteral(&ds, "\\\"");
- start = special + 1;
}
- if (*special == '\0') {
- break;
+ bspos = NULL;
+ }
+ if (quote & CL_QUOTE) {
+ /* start of argument (main opening quote-char) */
+ Tcl_DStringAppend(&ds, "\"", 1);
+ }
+ if (!(quote & CL_ESCAPE)) {
+ /* nothing to escape */
+ Tcl_DStringAppend(&ds, arg, -1);
+ } else {
+ start = arg;
+ for (special = arg; *special != '\0'; ) {
+ /* position of `\` is important before quote or at end (equal `\"` because quoted) */
+ if (*special == '\\') {
+ /* bypass backslashes (and mark first backslash possition)*/
+ special = BuildCmdLineBypassBS(special, &bspos);
+ if (*special == '\0') break;
+ }
+ /* ["] */
+ if (*special == '"') {
+ quote ^= CL_UNPAIRED; /* invert unpaired flag - observe unpaired quotes */
+ /* add part before (and escape backslashes before quote) */
+ QuoteCmdLineBackslash(&ds, start, special, bspos);
+ bspos = NULL;
+ /* escape using backslash */
+ Tcl_DStringAppend(&ds, "\\\"", 2);
+ start = ++special;
+ continue;
+ }
+ /* unpaired (escaped) quote causes special handling on meta-chars */
+ if ((quote & CL_UNPAIRED) && strchr(specMetaChars, *special)) {
+ special = QuoteCmdLinePart(&ds, start, special, specMetaChars, &bspos);
+ /* start to current or first backslash */
+ start = !bspos ? special : bspos;
+ continue;
+ }
+ /* special case for % - should be enclosed always (paired also) */
+ if (strchr(specMetaChars2, *special)) {
+ special = QuoteCmdLinePart(&ds, start, special, specMetaChars2, &bspos);
+ /* start to current or first backslash */
+ start = !bspos ? special : bspos;
+ continue;
+ }
+ /* other not special (and not meta) character */
+ bspos = NULL; /* reset last backslash possition (not interesting) */
+ special++;
}
- special++;
+ /* rest of argument (and escape backslashes before closing main quote) */
+ QuoteCmdLineBackslash(&ds, start, special,
+ (quote & CL_QUOTE) ? bspos : NULL);
}
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- if (quote) {
- TclDStringAppendLiteral(&ds, "\"");
+ if (quote & CL_QUOTE) {
+ /* end of argument (main closing quote-char) */
+ Tcl_DStringAppend(&ds, "\"", 1);
}
}
Tcl_DStringFree(linePtr);