Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit c53b84b

Browse files
tmdsstephentoub
authored andcommitted
Make Process.Start throw Win32Exception when child process doesn't exec. (#25266)
* Make Process.Start throw Win32Exception when child process doesn't exec. * Update tests * Fix test name * On Windows, use powershell.exe for test * Only run test when test program is found * Make read and write more robust * TestStartWithBadWorkingDirectory: skip on UWP * TestStartWithBadWorkingDirectory: skip UWP using Attribute
1 parent 8229cb7 commit c53b84b

File tree

5 files changed

+145
-31
lines changed

5 files changed

+145
-31
lines changed

src/Native/Unix/System.Native/pal_process.cpp

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,58 @@ static int Dup2WithInterruptedRetry(int oldfd, int newfd)
9292
return result;
9393
}
9494

95+
static ssize_t WriteSize(int fd, const void* buffer, size_t count)
96+
{
97+
ssize_t rv = 0;
98+
while (count > 0)
99+
{
100+
ssize_t result = 0;
101+
while (CheckInterrupted(result = write(fd, buffer, count)));
102+
if (result > 0)
103+
{
104+
rv += result;
105+
buffer = reinterpret_cast<const uint8_t*>(buffer) + result;
106+
count -= static_cast<size_t>(result);
107+
}
108+
else
109+
{
110+
return -1;
111+
}
112+
}
113+
return rv;
114+
}
115+
116+
static ssize_t ReadSize(int fd, void* buffer, size_t count)
117+
{
118+
ssize_t rv = 0;
119+
while (count > 0)
120+
{
121+
ssize_t result = 0;
122+
while (CheckInterrupted(result = read(fd, buffer, count)));
123+
if (result > 0)
124+
{
125+
rv += result;
126+
buffer = reinterpret_cast<uint8_t*>(buffer) + result;
127+
count -= static_cast<size_t>(result);
128+
}
129+
else
130+
{
131+
return -1;
132+
}
133+
}
134+
return rv;
135+
}
136+
137+
__attribute__((noreturn))
138+
static void ExitChild(int pipeToParent, int error)
139+
{
140+
if (pipeToParent != -1)
141+
{
142+
WriteSize(pipeToParent, &error, sizeof(error));
143+
}
144+
_exit(error != 0 ? error : EXIT_FAILURE);
145+
}
146+
95147
extern "C" int32_t SystemNative_ForkAndExecProcess(const char* filename,
96148
char* const argv[],
97149
char* const envp[],
@@ -153,6 +205,7 @@ extern "C" int32_t SystemNative_ForkAndExecProcess(const char* filename,
153205
// child process is actually transitioned to the target program. This avoids problems
154206
// where the parent process uses members of Process, like ProcessName, when the Process
155207
// is still the clone of this one. This is a best-effort attempt, so ignore any errors.
208+
// If the child fails to exec we use the pipe to pass the errno to the parent process.
156209
#if HAVE_PIPE2
157210
pipe2(waitForChildToExecPipe, O_CLOEXEC);
158211
#endif
@@ -172,7 +225,7 @@ extern "C" int32_t SystemNative_ForkAndExecProcess(const char* filename,
172225
(redirectStdout && Dup2WithInterruptedRetry(stdoutFds[WRITE_END_OF_PIPE], STDOUT_FILENO) == -1) ||
173226
(redirectStderr && Dup2WithInterruptedRetry(stderrFds[WRITE_END_OF_PIPE], STDERR_FILENO) == -1))
174227
{
175-
_exit(errno != 0 ? errno : EXIT_FAILURE);
228+
ExitChild(waitForChildToExecPipe[WRITE_END_OF_PIPE], errno);
176229
}
177230

178231
// Change to the designated working directory, if one was specified
@@ -182,13 +235,13 @@ extern "C" int32_t SystemNative_ForkAndExecProcess(const char* filename,
182235
while (CheckInterrupted(result = chdir(cwd)));
183236
if (result == -1)
184237
{
185-
_exit(errno != 0 ? errno : EXIT_FAILURE);
238+
ExitChild(waitForChildToExecPipe[WRITE_END_OF_PIPE], errno);
186239
}
187240
}
188241

189242
// Finally, execute the new process. execve will not return if it's successful.
190243
execve(filename, argv, envp);
191-
_exit(errno != 0 ? errno : EXIT_FAILURE); // execve failed
244+
ExitChild(waitForChildToExecPipe[WRITE_END_OF_PIPE], errno); // execve failed
192245
}
193246

194247
// This is the parent process. processId == pid of the child
@@ -212,10 +265,15 @@ extern "C" int32_t SystemNative_ForkAndExecProcess(const char* filename,
212265
CloseIfOpen(waitForChildToExecPipe[WRITE_END_OF_PIPE]);
213266
if (waitForChildToExecPipe[READ_END_OF_PIPE] != -1)
214267
{
215-
int ignored;
268+
int childError;
216269
if (success)
217270
{
218-
while (CheckInterrupted(read(waitForChildToExecPipe[READ_END_OF_PIPE], &ignored, 1)));
271+
ssize_t result = ReadSize(waitForChildToExecPipe[READ_END_OF_PIPE], &childError, sizeof(childError));
272+
if (result == sizeof(childError))
273+
{
274+
success = false;
275+
priorErrno = childError;
276+
}
219277
}
220278
CloseIfOpen(waitForChildToExecPipe[READ_END_OF_PIPE]);
221279
}

src/System.Diagnostics.Process/tests/Performance/System.Diagnostics.Process.Performance.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
<Compile Include="$(CommonPath)\System\PasteArguments.cs">
1818
<Link>Common\System\PasteArguments.cs</Link>
1919
</Compile>
20+
<Compile Include="$(CommonPath)\System\IO\StringParser.cs">
21+
<Link>Common\System\IO\StringParser.cs</Link>
22+
</Compile>
2023
<Compile Include="$(CommonTestPath)\System\PerfUtils.cs">
2124
<Link>Common\System\PerfUtils.cs</Link>
2225
</Compile>

src/System.Diagnostics.Process/tests/ProcessTestBase.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Generic;
6+
using System.IO;
67
using System.Reflection;
78
using System.Threading;
89
using Xunit;
@@ -69,5 +70,31 @@ protected void StartSleepKillWait(Process p)
6970
p.Kill();
7071
Assert.True(p.WaitForExit(WaitInMS));
7172
}
73+
74+
/// <summary>
75+
/// Checks if the program is installed
76+
/// </summary>
77+
/// <param name="program"></param>
78+
/// <returns></returns>
79+
protected static bool IsProgramInstalled(string program)
80+
{
81+
string path;
82+
string pathEnvVar = Environment.GetEnvironmentVariable("PATH");
83+
char separator = PlatformDetection.IsWindows ? ';' : ':';
84+
if (pathEnvVar != null)
85+
{
86+
var pathParser = new StringParser(pathEnvVar, separator, skipEmpty: true);
87+
while (pathParser.MoveNext())
88+
{
89+
string subPath = pathParser.ExtractCurrent();
90+
path = Path.Combine(subPath, program);
91+
if (File.Exists(path))
92+
{
93+
return true;
94+
}
95+
}
96+
}
97+
return false;
98+
}
7299
}
73100
}

src/System.Diagnostics.Process/tests/ProcessTests.Unix.cs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ public void TestStartOnUnixWithBadPermissions()
266266
}
267267

268268
[Fact]
269+
[PlatformSpecific(~TestPlatforms.OSX)] // OSX doesn't support throwing on Process.Start
269270
public void TestStartOnUnixWithBadFormat()
270271
{
271272
string path = GetTestFilePath();
@@ -274,6 +275,20 @@ public void TestStartOnUnixWithBadFormat()
274275

275276
Assert.Equal(0, chmod(path, mode)); // execute permissions
276277

278+
Win32Exception e = Assert.Throws<Win32Exception>(() => Process.Start(path));
279+
Assert.NotEqual(0, e.NativeErrorCode);
280+
}
281+
282+
[Fact]
283+
[PlatformSpecific(TestPlatforms.OSX)] // OSX doesn't support throwing on Process.Start
284+
public void TestStartOnOSXWithBadFormat()
285+
{
286+
string path = GetTestFilePath();
287+
File.Create(path).Dispose();
288+
int mode = Convert.ToInt32("744", 8);
289+
290+
Assert.Equal(0, chmod(path, mode)); // execute permissions
291+
277292
using (Process p = Process.Start(path))
278293
{
279294
p.WaitForExit();
@@ -285,30 +300,5 @@ public void TestStartOnUnixWithBadFormat()
285300
private static extern int chmod(string path, int mode);
286301

287302
private readonly string[] s_allowedProgramsToRun = new string[] { "xdg-open", "gnome-open", "kfmclient" };
288-
289-
/// <summary>
290-
/// Checks if the program is installed
291-
/// </summary>
292-
/// <param name="program"></param>
293-
/// <returns></returns>
294-
private bool IsProgramInstalled(string program)
295-
{
296-
string path;
297-
string pathEnvVar = Environment.GetEnvironmentVariable("PATH");
298-
if (pathEnvVar != null)
299-
{
300-
var pathParser = new StringParser(pathEnvVar, ':', skipEmpty: true);
301-
while (pathParser.MoveNext())
302-
{
303-
string subPath = pathParser.ExtractCurrent();
304-
path = Path.Combine(subPath, program);
305-
if (File.Exists(path))
306-
{
307-
return true;
308-
}
309-
}
310-
}
311-
return false;
312-
}
313303
}
314304
}

src/System.Diagnostics.Process/tests/ProcessTests.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,43 @@ public void ProcessStart_TryOpenFolder_UseShellExecuteIsFalse_ThrowsWin32Excepti
135135
{
136136
Win32Exception e = Assert.Throws<Win32Exception>(() => Process.Start(new ProcessStartInfo { UseShellExecute = false, FileName = Path.GetTempPath() }));
137137
}
138-
138+
139+
[Fact]
140+
[PlatformSpecific(~TestPlatforms.OSX)] // OSX doesn't support throwing on Process.Start
141+
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // UWP overrides WorkingDirectory (https://github.com/dotnet/corefx/pull/25266#issuecomment-347719832).
142+
public void TestStartWithBadWorkingDirectory()
143+
{
144+
string program;
145+
string workingDirectory;
146+
if (PlatformDetection.IsWindows)
147+
{
148+
program = "powershell.exe";
149+
workingDirectory = @"C:\does-not-exist";
150+
}
151+
else
152+
{
153+
program = "uname";
154+
workingDirectory = "/does-not-exist";
155+
}
156+
157+
if (IsProgramInstalled(program))
158+
{
159+
var psi = new ProcessStartInfo
160+
{
161+
FileName = program,
162+
UseShellExecute = false,
163+
WorkingDirectory = workingDirectory
164+
};
165+
166+
Win32Exception e = Assert.Throws<Win32Exception>(() => Process.Start(psi));
167+
Assert.NotEqual(0, e.NativeErrorCode);
168+
}
169+
else
170+
{
171+
Console.WriteLine($"Program {program} is not installed on this machine.");
172+
}
173+
}
174+
139175
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasWindowsShell))]
140176
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "not supported on UAP")]
141177
[OuterLoop("Launches File Explorer")]

0 commit comments

Comments
 (0)