Skip to content

Commit bbdc607

Browse files
committed
Process.Unix: consider executable permission while searching PATH.
1 parent f463d8d commit bbdc607

File tree

9 files changed

+167
-4
lines changed

9 files changed

+167
-4
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Sys
10+
{
11+
internal static unsafe uint[]? GetGroups()
12+
{
13+
const int InitialGroupsLength =
14+
#if DEBUG
15+
1;
16+
#else
17+
64;
18+
#endif
19+
Span<uint> groups = stackalloc uint[InitialGroupsLength];
20+
do
21+
{
22+
int rv;
23+
fixed (uint* pGroups = groups)
24+
{
25+
rv = Interop.Sys.GetGroups(groups.Length, pGroups);
26+
}
27+
28+
if (rv >= 0)
29+
{
30+
// success
31+
return groups.Slice(0, rv).ToArray();
32+
}
33+
else if (rv == -1 && Interop.Sys.GetLastError() == Interop.Error.EINVAL)
34+
{
35+
// increase buffer size
36+
groups = new uint[groups.Length * 2];
37+
}
38+
else
39+
{
40+
// failure
41+
return null;
42+
}
43+
}
44+
while (true);
45+
}
46+
47+
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetGroups", SetLastError = true)]
48+
private static extern unsafe int GetGroups(int ngroups, uint* groups);
49+
}
50+
}

src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ internal enum Permissions
2626
S_IROTH = 0x4,
2727
S_IWOTH = 0x2,
2828
S_IXOTH = 0x1,
29+
30+
S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH,
2931
}
3032
}
3133
}

src/libraries/Native/Unix/System.Native/entrypoints.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ static const Entry s_sysNative[] =
256256
DllImportEntry(SystemNative_HandleNonCanceledPosixSignal)
257257
DllImportEntry(SystemNative_SetPosixSignalHandler)
258258
DllImportEntry(SystemNative_GetPlatformSignalNumber)
259+
DllImportEntry(SystemNative_GetGroups)
259260
};
260261

261262
EXTERN_C const void* SystemResolveDllImport(const char* name);

src/libraries/Native/Unix/System.Native/pal_uid.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,11 @@ int32_t SystemNative_GetGroupList(const char* name, uint32_t group, uint32_t* gr
234234

235235
return rv;
236236
}
237+
238+
int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups)
239+
{
240+
assert(ngroups >= 0);
241+
assert(groups != NULL);
242+
243+
return getgroups(ngroups, groups);
244+
}

src/libraries/Native/Unix/System.Native/pal_uid.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,12 @@ PALEXPORT int32_t SystemNative_GetGroupList(const char* name, uint32_t group, ui
8181
* Always succeeds.
8282
*/
8383
PALEXPORT uint32_t SystemNative_GetUid(void);
84+
85+
/**
86+
* Gets groups associated with current process.
87+
*
88+
* Returns number of groups for success.
89+
* On error, -1 is returned and errno is set.
90+
* If the buffer is too small, errno is EINVAL.
91+
*/
92+
PALEXPORT int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups);

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@
273273
Link="Common\Interop\Unix\Interop.WaitPid.cs" />
274274
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Access.cs"
275275
Link="Common\Interop\Unix\System.Native\Interop.Access.cs" />
276+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
277+
Link="Common\Interop\Unix\Interop.Stat.cs" />
278+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Permissions.cs"
279+
Link="Common\Interop\Unix\Interop.Permissions.cs" />
280+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEUid.cs"
281+
Link="Common\Interop\Unix\Interop.GetEUid.cs" />
282+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEGid.cs"
283+
Link="Common\Interop\Unix\Interop.GetEGid.cs" />
284+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetGroups.cs"
285+
Link="Common\Interop\Linux\Interop.GetGroups.cs" />
276286
</ItemGroup>
277287
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' and '$(IsiOSLike)' != 'true'">
278288
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ConfigureTerminalForChildProcess.cs"
@@ -332,8 +342,6 @@
332342
Link="Common\Interop\FreeBSD\Interop.Process.cs" />
333343
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Process.GetProcInfo.cs"
334344
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
335-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
336-
Link="Common\Unix\System.Native\Interop.Stat.cs" />
337345
</ItemGroup>
338346
<ItemGroup Condition="'$(TargetsUnknownUnix)' == 'true'">
339347
<Compile Include="System\Diagnostics\Process.UnknownUnix.cs" />

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ namespace System.Diagnostics
1717
public partial class Process : IDisposable
1818
{
1919
private static volatile bool s_initialized;
20+
private static uint s_euid;
21+
private static uint s_egid;
22+
private static uint[]? s_groups;
2023
private static readonly object s_initializedGate = new object();
2124
private static readonly ReaderWriterLockSlim s_processStartLock = new ReaderWriterLockSlim();
2225

@@ -743,7 +746,7 @@ private static string[] CreateEnvp(ProcessStartInfo psi)
743746
{
744747
string subPath = pathParser.ExtractCurrent();
745748
path = Path.Combine(subPath, program);
746-
if (File.Exists(path))
749+
if (IsExecutable(path))
747750
{
748751
return path;
749752
}
@@ -752,6 +755,46 @@ private static string[] CreateEnvp(ProcessStartInfo psi)
752755
return null;
753756
}
754757

758+
private static bool IsExecutable(string fullPath)
759+
{
760+
Interop.Sys.FileStatus fileinfo;
761+
762+
if (Interop.Sys.Stat(fullPath, out fileinfo) < 0)
763+
{
764+
return false;
765+
}
766+
767+
// Check if the path is a directory.
768+
if ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR)
769+
{
770+
return false;
771+
}
772+
773+
Interop.Sys.Permissions permissions = (Interop.Sys.Permissions)fileinfo.Mode;
774+
775+
if (s_euid == 0)
776+
{
777+
// We're root.
778+
return (permissions & Interop.Sys.Permissions.S_IXUGO) != 0;
779+
}
780+
781+
if (s_euid == fileinfo.Uid)
782+
{
783+
// We own the file.
784+
return (permissions & Interop.Sys.Permissions.S_IXUSR) != 0;
785+
}
786+
787+
if (s_egid == fileinfo.Gid ||
788+
(s_groups != null && Array.BinarySearch(s_groups, fileinfo.Gid) >= 0))
789+
{
790+
// A group we're a member of owns the file.
791+
return (permissions & Interop.Sys.Permissions.S_IXGRP) != 0;
792+
}
793+
794+
// Other.
795+
return (permissions & Interop.Sys.Permissions.S_IXOTH) != 0;
796+
}
797+
755798
private static long s_ticksPerSecond;
756799

757800
/// <summary>Convert a number of "jiffies", or ticks, to a TimeSpan.</summary>
@@ -1021,6 +1064,14 @@ private static unsafe void EnsureInitialized()
10211064
throw new Win32Exception();
10221065
}
10231066

1067+
s_euid = Interop.Sys.GetEUid();
1068+
s_egid = Interop.Sys.GetEGid();
1069+
s_groups = Interop.Sys.GetGroups();
1070+
if (s_groups != null)
1071+
{
1072+
Array.Sort(s_groups);
1073+
}
1074+
10241075
// Register our callback.
10251076
Interop.Sys.RegisterForSigChld(&OnSigChild);
10261077
SetDelayedSigChildConsoleConfigurationHandler();

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,40 @@ public void ProcessNameMatchesScriptName()
185185
}
186186
}
187187

188+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
189+
public void ProcessStart_SkipsNonExecutableFilesOnPATH()
190+
{
191+
const string ScriptName = "script";
192+
193+
// Create a directory named ScriptName.
194+
string path1 = Path.Combine(TestDirectory, "Path1");
195+
Directory.CreateDirectory(Path.Combine(path1, ScriptName));
196+
197+
// Create a non-executable file named ScriptName
198+
string path2 = Path.Combine(TestDirectory, "Path2");
199+
Directory.CreateDirectory(path2);
200+
File.WriteAllText(Path.Combine(path2, ScriptName), "Not executable");
201+
202+
// Create an executable script named ScriptName
203+
string path3 = Path.Combine(TestDirectory, "Path3");
204+
Directory.CreateDirectory(path3);
205+
string filename = WriteScriptFile(path3, ScriptName, returnValue: 42);
206+
207+
// Process.Start ScriptName with the above on PATH.
208+
RemoteInvokeOptions options = new RemoteInvokeOptions();
209+
options.StartInfo.EnvironmentVariables["PATH"] = $"{path1}:{path2}:{path3}";
210+
RemoteExecutor.Invoke(() =>
211+
{
212+
using (var px = Process.Start(new ProcessStartInfo { FileName = ScriptName }))
213+
{
214+
Assert.NotNull(px);
215+
px.WaitForExit();
216+
Assert.True(px.HasExited);
217+
Assert.Equal(42, px.ExitCode);
218+
}
219+
}, options).Dispose();
220+
}
221+
188222
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
189223
[PlatformSpecific(TestPlatforms.Linux)] // s_allowedProgramsToRun is Linux specific
190224
public void ProcessStart_UseShellExecute_OnUnix_FallsBackWhenNotRealExecutable()

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1954,7 +1954,7 @@
19541954
<Link>Common\Interop\Unix\System.Native\Interop.GetCwd.cs</Link>
19551955
</Compile>
19561956
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEGid.cs">
1957-
<Link>Common\Interop\Unix\System.Native\Interop.GetHostName.cs</Link>
1957+
<Link>Common\Interop\Unix\System.Native\Interop.GetEGid.cs</Link>
19581958
</Compile>
19591959
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetHostName.cs">
19601960
<Link>Common\Interop\Unix\System.Native\Interop.GetHostName.cs</Link>

0 commit comments

Comments
 (0)