1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
# HG changeset patch
# User andrew
# Date 1365784383 -3600
# Node ID dfa1c658a62a54dbcfa02e96c51af21a3cc71907
# Parent 0938f449476b5c89ccb68f0c195a57a0e72e9dc4
8005943: (process) Improved Runtime.exec
Reviewed-by: alanb, ahgross
diff --git a/src/share/classes/java/lang/ProcessBuilder.java b/src/share/classes/java/lang/ProcessBuilder.java
--- jdk/src/share/classes/java/lang/ProcessBuilder.java
+++ jdk/src/share/classes/java/lang/ProcessBuilder.java
@@ -27,6 +27,7 @@
import java.io.File;
import java.io.IOException;
+import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -459,8 +460,9 @@
String prog = cmdarray[0];
SecurityManager security = System.getSecurityManager();
- if (security != null)
+ if (security != null) {
security.checkExec(prog);
+ }
String dir = directory == null ? null : directory.toString();
@@ -470,13 +472,24 @@
dir,
redirectErrorStream);
} catch (IOException e) {
+ String exceptionInfo = ": " + e.getMessage();
+ Throwable cause = e;
+ if (security != null) {
+ // Can not disclose the fail reason for read-protected files.
+ try {
+ security.checkRead(prog);
+ } catch (AccessControlException ace) {
+ exceptionInfo = "";
+ cause = ace;
+ }
+ }
// It's much easier for us to create a high-quality error
// message than the low-level C code which found the problem.
throw new IOException(
"Cannot run program \"" + prog + "\""
+ (dir == null ? "" : " (in directory \"" + dir + "\")")
- + ": " + e.getMessage(),
- e);
+ + exceptionInfo,
+ cause);
}
}
}
diff --git a/src/windows/classes/java/lang/ProcessImpl.java b/src/windows/classes/java/lang/ProcessImpl.java
--- jdk/src/windows/classes/java/lang/ProcessImpl.java
+++ jdk/src/windows/classes/java/lang/ProcessImpl.java
@@ -47,6 +47,88 @@
return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);
}
+ // We guarantee the only command file execution for implicit [cmd.exe] run.
+ // http://technet.microsoft.com/en-us/library/bb490954.aspx
+ private static final char CMD_BAT_ESCAPE[] = {' ', '\t', '<', '>', '&', '|', '^'};
+ private static final char WIN32_EXECUTABLE_ESCAPE[] = {' ', '\t', '<', '>'};
+
+ private static boolean isQuoted(boolean noQuotesInside, String arg,
+ String errorMessage) {
+ int lastPos = arg.length() - 1;
+ if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') {
+ // The argument has already been quoted.
+ if (noQuotesInside) {
+ if (arg.indexOf('"', 1) != lastPos) {
+ // There is ["] inside.
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+ return true;
+ }
+ if (noQuotesInside) {
+ if (arg.indexOf('"') >= 0) {
+ // There is ["] inside.
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+ return false;
+ }
+
+ private static boolean needsEscaping(boolean isCmdFile, String arg) {
+ // Switch off MS heuristic for internal ["].
+ // Please, use the explicit [cmd.exe] call
+ // if you need the internal ["].
+ // Example: "cmd.exe", "/C", "Extended_MS_Syntax"
+
+ // For [.exe] or [.com] file the unpaired/internal ["]
+ // in the argument is not a problem.
+ boolean argIsQuoted = isQuoted(isCmdFile, arg,
+ "Argument has embedded quote, use the explicit CMD.EXE call.");
+
+ if (!argIsQuoted) {
+ char testEscape[] = isCmdFile
+ ? CMD_BAT_ESCAPE
+ : WIN32_EXECUTABLE_ESCAPE;
+ for (int i = 0; i < testEscape.length; ++i) {
+ if (arg.indexOf(testEscape[i]) >= 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static String getExecutablePath(String path)
+ throws IOException
+ {
+ boolean pathIsQuoted = isQuoted(true, path,
+ "Executable name has embedded quote, split the arguments");
+
+ // Win32 CreateProcess requires path to be normalized
+ File fileToRun = new File(pathIsQuoted
+ ? path.substring(1, path.length() - 1)
+ : path);
+
+ // From the [CreateProcess] function documentation:
+ //
+ // "If the file name does not contain an extension, .exe is appended.
+ // Therefore, if the file name extension is .com, this parameter
+ // must include the .com extension. If the file name ends in
+ // a period (.) with no extension, or if the file name contains a path,
+ // .exe is not appended."
+ //
+ // "If the file name !does not contain a directory path!,
+ // the system searches for the executable file in the following
+ // sequence:..."
+ //
+ // In practice ANY non-existent path is extended by [.exe] extension
+ // in the [CreateProcess] funcion with the only exception:
+ // the path ends by (.)
+
+ return fileToRun.getPath();
+ }
+
+
private long handle = 0;
private FileDescriptor stdin_fd;
private FileDescriptor stdout_fd;
@@ -61,30 +143,31 @@
boolean redirectErrorStream)
throws IOException
{
- // Win32 CreateProcess requires cmd[0] to be normalized
- cmd[0] = new File(cmd[0]).getPath();
+ // The [executablePath] is not quoted for any case.
+ String executablePath = getExecutablePath(cmd[0]);
+
+ // We need to extend the argument verification procedure
+ // to guarantee the only command file execution for implicit [cmd.exe]
+ // run.
+ String upPath = executablePath.toUpperCase();
+ boolean isCmdFile = (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));
StringBuilder cmdbuf = new StringBuilder(80);
- for (int i = 0; i < cmd.length; i++) {
- if (i > 0) {
- cmdbuf.append(' ');
- }
+
+ // Quotation protects from interpretation of the [path] argument as
+ // start of longer path with spaces. Quotation has no influence to
+ // [.exe] extension heuristic.
+ cmdbuf.append('"');
+ cmdbuf.append(executablePath);
+ cmdbuf.append('"');
+
+ for (int i = 1; i < cmd.length; i++) {
+ cmdbuf.append(' ');
String s = cmd[i];
- if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) {
- if (s.charAt(0) != '"') {
- cmdbuf.append('"');
- cmdbuf.append(s);
- if (s.endsWith("\\")) {
- cmdbuf.append("\\");
- }
- cmdbuf.append('"');
- } else if (s.endsWith("\"")) {
- /* The argument has already been quoted. */
- cmdbuf.append(s);
- } else {
- /* Unmatched quote for the argument. */
- throw new IllegalArgumentException();
- }
+ if (needsEscaping(isCmdFile, s)) {
+ cmdbuf.append('"');
+ cmdbuf.append(s);
+ cmdbuf.append('"');
} else {
cmdbuf.append(s);
}
|