diff options
author | sebres <sebres@users.sourceforge.net> | 2018-08-29 15:41:55 (GMT) |
---|---|---|
committer | sebres <sebres@users.sourceforge.net> | 2018-08-29 15:41:55 (GMT) |
commit | afb5bf558344f2985346b88580f4b9712302991f (patch) | |
tree | 03b8512e588f97b5d861046f0f3e7fe9039fea91 /win | |
parent | 89ff80fbdd973caecbbaf87cc772f19ed369cc50 (diff) | |
parent | 9b7ce068fe00fb30818dff8934c1417a482a4a12 (diff) | |
download | tcl-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')
-rw-r--r-- | win/tclWinPipe.c | 233 |
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); |