Skip to content

Commit

Permalink
Process.Unix: consider executable permission while searching PATH.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmds committed Jul 13, 2021
1 parent f463d8d commit bbdc607
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
internal static unsafe uint[]? GetGroups()
{
const int InitialGroupsLength =
#if DEBUG
1;
#else
64;
#endif
Span<uint> groups = stackalloc uint[InitialGroupsLength];
do
{
int rv;
fixed (uint* pGroups = groups)
{
rv = Interop.Sys.GetGroups(groups.Length, pGroups);
}

if (rv >= 0)
{
// success
return groups.Slice(0, rv).ToArray();
}
else if (rv == -1 && Interop.Sys.GetLastError() == Interop.Error.EINVAL)
{
// increase buffer size
groups = new uint[groups.Length * 2];
}
else
{
// failure
return null;
}
}
while (true);
}

[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetGroups", SetLastError = true)]
private static extern unsafe int GetGroups(int ngroups, uint* groups);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ internal enum Permissions
S_IROTH = 0x4,
S_IWOTH = 0x2,
S_IXOTH = 0x1,

S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH,
}
}
}
1 change: 1 addition & 0 deletions src/libraries/Native/Unix/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_HandleNonCanceledPosixSignal)
DllImportEntry(SystemNative_SetPosixSignalHandler)
DllImportEntry(SystemNative_GetPlatformSignalNumber)
DllImportEntry(SystemNative_GetGroups)
};

EXTERN_C const void* SystemResolveDllImport(const char* name);
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/Native/Unix/System.Native/pal_uid.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,11 @@ int32_t SystemNative_GetGroupList(const char* name, uint32_t group, uint32_t* gr

return rv;
}

int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups)
{
assert(ngroups >= 0);
assert(groups != NULL);

return getgroups(ngroups, groups);
}
9 changes: 9 additions & 0 deletions src/libraries/Native/Unix/System.Native/pal_uid.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,12 @@ PALEXPORT int32_t SystemNative_GetGroupList(const char* name, uint32_t group, ui
* Always succeeds.
*/
PALEXPORT uint32_t SystemNative_GetUid(void);

/**
* Gets groups associated with current process.
*
* Returns number of groups for success.
* On error, -1 is returned and errno is set.
* If the buffer is too small, errno is EINVAL.
*/
PALEXPORT int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups);
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@
Link="Common\Interop\Unix\Interop.WaitPid.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Access.cs"
Link="Common\Interop\Unix\System.Native\Interop.Access.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
Link="Common\Interop\Unix\Interop.Stat.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Permissions.cs"
Link="Common\Interop\Unix\Interop.Permissions.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEUid.cs"
Link="Common\Interop\Unix\Interop.GetEUid.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEGid.cs"
Link="Common\Interop\Unix\Interop.GetEGid.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetGroups.cs"
Link="Common\Interop\Linux\Interop.GetGroups.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' and '$(IsiOSLike)' != 'true'">
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ConfigureTerminalForChildProcess.cs"
Expand Down Expand Up @@ -332,8 +342,6 @@
Link="Common\Interop\FreeBSD\Interop.Process.cs" />
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Process.GetProcInfo.cs"
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
Link="Common\Unix\System.Native\Interop.Stat.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnknownUnix)' == 'true'">
<Compile Include="System\Diagnostics\Process.UnknownUnix.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ namespace System.Diagnostics
public partial class Process : IDisposable
{
private static volatile bool s_initialized;
private static uint s_euid;
private static uint s_egid;
private static uint[]? s_groups;
private static readonly object s_initializedGate = new object();
private static readonly ReaderWriterLockSlim s_processStartLock = new ReaderWriterLockSlim();

Expand Down Expand Up @@ -743,7 +746,7 @@ private static string[] CreateEnvp(ProcessStartInfo psi)
{
string subPath = pathParser.ExtractCurrent();
path = Path.Combine(subPath, program);
if (File.Exists(path))
if (IsExecutable(path))
{
return path;
}
Expand All @@ -752,6 +755,46 @@ private static string[] CreateEnvp(ProcessStartInfo psi)
return null;
}

private static bool IsExecutable(string fullPath)
{
Interop.Sys.FileStatus fileinfo;

if (Interop.Sys.Stat(fullPath, out fileinfo) < 0)
{
return false;
}

// Check if the path is a directory.
if ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR)
{
return false;
}

Interop.Sys.Permissions permissions = (Interop.Sys.Permissions)fileinfo.Mode;

if (s_euid == 0)
{
// We're root.
return (permissions & Interop.Sys.Permissions.S_IXUGO) != 0;
}

if (s_euid == fileinfo.Uid)
{
// We own the file.
return (permissions & Interop.Sys.Permissions.S_IXUSR) != 0;
}

if (s_egid == fileinfo.Gid ||
(s_groups != null && Array.BinarySearch(s_groups, fileinfo.Gid) >= 0))
{
// A group we're a member of owns the file.
return (permissions & Interop.Sys.Permissions.S_IXGRP) != 0;
}

// Other.
return (permissions & Interop.Sys.Permissions.S_IXOTH) != 0;
}

private static long s_ticksPerSecond;

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

s_euid = Interop.Sys.GetEUid();
s_egid = Interop.Sys.GetEGid();
s_groups = Interop.Sys.GetGroups();
if (s_groups != null)
{
Array.Sort(s_groups);
}

// Register our callback.
Interop.Sys.RegisterForSigChld(&OnSigChild);
SetDelayedSigChildConsoleConfigurationHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,40 @@ public void ProcessNameMatchesScriptName()
}
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void ProcessStart_SkipsNonExecutableFilesOnPATH()
{
const string ScriptName = "script";

// Create a directory named ScriptName.
string path1 = Path.Combine(TestDirectory, "Path1");
Directory.CreateDirectory(Path.Combine(path1, ScriptName));

// Create a non-executable file named ScriptName
string path2 = Path.Combine(TestDirectory, "Path2");
Directory.CreateDirectory(path2);
File.WriteAllText(Path.Combine(path2, ScriptName), "Not executable");

// Create an executable script named ScriptName
string path3 = Path.Combine(TestDirectory, "Path3");
Directory.CreateDirectory(path3);
string filename = WriteScriptFile(path3, ScriptName, returnValue: 42);

// Process.Start ScriptName with the above on PATH.
RemoteInvokeOptions options = new RemoteInvokeOptions();
options.StartInfo.EnvironmentVariables["PATH"] = $"{path1}:{path2}:{path3}";
RemoteExecutor.Invoke(() =>
{
using (var px = Process.Start(new ProcessStartInfo { FileName = ScriptName }))
{
Assert.NotNull(px);
px.WaitForExit();
Assert.True(px.HasExited);
Assert.Equal(42, px.ExitCode);
}
}, options).Dispose();
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[PlatformSpecific(TestPlatforms.Linux)] // s_allowedProgramsToRun is Linux specific
public void ProcessStart_UseShellExecute_OnUnix_FallsBackWhenNotRealExecutable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1954,7 +1954,7 @@
<Link>Common\Interop\Unix\System.Native\Interop.GetCwd.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEGid.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetHostName.cs</Link>
<Link>Common\Interop\Unix\System.Native\Interop.GetEGid.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetHostName.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetHostName.cs</Link>
Expand Down

0 comments on commit bbdc607

Please sign in to comment.