--- /dev/null
+// 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);
+ }
+}
S_IROTH = 0x4,
S_IWOTH = 0x2,
S_IXOTH = 0x1,
+
+ S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH,
}
}
}
DllImportEntry(SystemNative_HandleNonCanceledPosixSignal)
DllImportEntry(SystemNative_SetPosixSignalHandler)
DllImportEntry(SystemNative_GetPlatformSignalNumber)
+ DllImportEntry(SystemNative_GetGroups)
};
EXTERN_C const void* SystemResolveDllImport(const char* name);
return rv;
}
+
+int32_t SystemNative_GetGroups(int32_t ngroups, uint32_t* groups)
+{
+ assert(ngroups >= 0);
+ assert(groups != NULL);
+
+ return getgroups(ngroups, groups);
+}
* 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);
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"
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" />
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();
{
string subPath = pathParser.ExtractCurrent();
path = Path.Combine(subPath, program);
- if (File.Exists(path))
+ if (IsExecutable(path))
{
return path;
}
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>
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();
}
[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()
{
<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>