Skip to content

Commit

Permalink
Process.Linux: handle when /proc and the process pid namespace don't …
Browse files Browse the repository at this point in the history
…match. (#100076)

* Process.Linux: handle when /proc and the process pid namespace don't match.

Normally the '/proc' filesystem uses the same pid namespace as the process.
With rootless containers, it may happen that these pid namespaces do not match
because the container doesn't have permissions to change '/proc' but it can
create a new pid namespace.

When that happens, the numeric ids used by the /proc filesystem no longer match with
the process pid namespace. We can still access information for the current process
using '/proc/self'. For other processes, we can't map pids to the proc pids, which
leads to reading information for non-existing/wrong/inaccessible processes.
  • Loading branch information
tmds authored Jul 16, 2024
1 parent 817a2fb commit 773f3cd
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ internal static partial class @procfs
{
private const string MapsFileName = "/maps";

private static string GetMapsFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{MapsFileName}");
private static string GetMapsFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{MapsFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{MapsFileName}");

internal static ProcessModuleCollection? ParseMapsModules(int pid)
internal static ProcessModuleCollection? ParseMapsModules(ProcPid pid)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,28 @@ internal static partial class Interop
internal static partial class @procfs
{
internal const string RootPath = "/proc/";
internal const string Self = "self";
private const string StatusFileName = "/status";

// Normally the '/proc' filesystem uses the same pid namespace as the process.
// With rootless containers, it may happen that these pid namespaces do not match
// because the container doesn't have permissions to change '/proc' but it can
// create a new pid namespace.
//
// When that happens, the numeric ids used by the '/proc' filesystem no longer match with
// the process pid namespace. We can still access information for the current process
// using '/proc/self'. For other processes, we can't map pids to the proc pids so we musn't
// use '/proc' as that would return information for non-existing/wrong/inaccessible processes.
//
// The 'ProcPid' type represents a pid used by the '/proc' filesystem.
// This type provides a type-safe way to distingish the proc pids from the process pid namespace pids,
// which are passed as regular 'int's.
internal enum ProcPid : int
{
Invalid = -1,
Self = 0, // Information for the current process, accessible through '/proc/self'.
}

internal struct ParsedStatus
{
#if DEBUG
Expand All @@ -30,13 +50,15 @@ internal struct ParsedStatus
internal ulong VmPeak;
}

internal static string GetStatusFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatusFileName}");
internal static string GetStatusFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{StatusFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatusFileName}");

internal static bool TryReadStatusFile(int pid, out ParsedStatus result)
internal static bool TryReadStatusFile(ProcPid pid, out ParsedStatus result)
{
bool b = TryParseStatusFile(GetStatusFilePathForProcess(pid), out result);
#if DEBUG
Debug.Assert(!b || result.Pid == pid, "Expected process ID from status file to match supplied pid");
Debug.Assert(!b || (ProcPid)result.Pid == pid || pid == ProcPid.Self, "Expected process ID from status file to match supplied pid");
#endif
return b;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,38 @@ internal struct ParsedStat
//internal long cguest_time;
}

internal static string GetExeFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{ExeFileName}");
internal static string GetExeFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{ExeFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{ExeFileName}");

internal static string GetCmdLinePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{CmdLineFileName}");
internal static string GetCmdLinePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{CmdLineFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{CmdLineFileName}");

internal static string GetStatFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatFileName}");
internal static string GetStatFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{StatFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatFileName}");

internal static string GetTaskDirectoryPathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}");
internal static string GetTaskDirectoryPathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{TaskDirectoryName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}");

internal static string GetFileDescriptorDirectoryPathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{FileDescriptorDirectoryName}");
internal static string GetFileDescriptorDirectoryPathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{FileDescriptorDirectoryName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{FileDescriptorDirectoryName}");

private static string GetStatFilePathForThread(int pid, int tid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}{(uint)tid}{StatFileName}");
private static string GetStatFilePathForThread(ProcPid pid, int tid) =>
pid == ProcPid.Self ? string.Create(null, stackalloc char[256], $"{RootPath}{Self}{TaskDirectoryName}{(uint)tid}{StatFileName}") :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}{(uint)tid}{StatFileName}");

internal static bool TryReadStatFile(int pid, out ParsedStat result)
internal static bool TryReadStatFile(ProcPid pid, out ParsedStat result)
{
bool b = TryParseStatFile(GetStatFilePathForProcess(pid), out result);
Debug.Assert(!b || result.pid == pid, "Expected process ID from stat file to match supplied pid");
Debug.Assert(!b || pid == ProcPid.Self|| (ProcPid)result.pid == pid, "Expected process ID from stat file to match supplied pid");
return b;
}

internal static bool TryReadStatFile(int pid, int tid, out ParsedStat result)
internal static bool TryReadStatFile(ProcPid pid, int tid, out ParsedStat result)
{
bool b = TryParseStatFile(GetStatFilePathForThread(pid, tid), out result);
Debug.Assert(!b || result.pid == tid, "Expected thread ID from stat file to match supplied tid");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ public static Process[] GetProcessesByName(string? processName, string machineNa
ArrayBuilder<Process> processes = default;
foreach (int pid in ProcessManager.EnumerateProcessIds())
{
if (Interop.procfs.TryReadStatFile(pid, out Interop.procfs.ParsedStat parsedStat))
if (ProcessManager.TryGetProcPid(pid, out Interop.procfs.ProcPid procPid) &&
Interop.procfs.TryReadStatFile(procPid, out Interop.procfs.ParsedStat parsedStat))
{
string actualProcessName = GetUntruncatedProcessName(ref parsedStat);
string actualProcessName = GetUntruncatedProcessName(procPid, ref parsedStat);
if ((processName == "" || string.Equals(processName, actualProcessName, StringComparison.OrdinalIgnoreCase)) &&
Interop.procfs.TryReadStatusFile(pid, out Interop.procfs.ParsedStatus parsedStatus))
Interop.procfs.TryReadStatusFile(procPid, out Interop.procfs.ParsedStatus parsedStatus))
{
ProcessInfo processInfo = ProcessManager.CreateProcessInfo(ref parsedStat, ref parsedStatus, actualProcessName);
ProcessInfo processInfo = ProcessManager.CreateProcessInfo(procPid, ref parsedStat, ref parsedStatus, actualProcessName);
processes.Add(new Process(machineName, isRemoteMachine: false, pid, processInfo));
}
}
Expand Down Expand Up @@ -160,7 +161,11 @@ partial void EnsureHandleCountPopulated()
{
return;
}
string path = Interop.procfs.GetFileDescriptorDirectoryPathForProcess(_processId);
if (!ProcessManager.TryGetProcPid(_processId, out Interop.procfs.ProcPid procPid))
{
return;
}
string path = Interop.procfs.GetFileDescriptorDirectoryPathForProcess(procPid);
if (Directory.Exists(path))
{
try
Expand Down Expand Up @@ -250,18 +255,19 @@ private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out
#pragma warning restore IDE0060

/// <summary>Gets the path to the executable for the process, or null if it could not be retrieved.</summary>
/// <param name="processId">The pid for the target process, or -1 for the current process.</param>
internal static string? GetExePath(int processId = -1)
/// <param name="procPid">The pid for the target process.</param>
internal static string? GetExePath(Interop.procfs.ProcPid procPid)
{
return processId == -1 ? Environment.ProcessPath :
Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(processId));
return procPid == Interop.procfs.ProcPid.Self ? Environment.ProcessPath :
Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(procPid));
}

/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
/// <param name="procPid">The pid for the target process.</param>
/// <param name="stat">The stat for the target process.</param>
internal static string GetUntruncatedProcessName(ref Interop.procfs.ParsedStat stat)
internal static string GetUntruncatedProcessName(Interop.procfs.ProcPid procPid, ref Interop.procfs.ParsedStat stat)
{
string cmdLineFilePath = Interop.procfs.GetCmdLinePathForProcess(stat.pid);
string cmdLineFilePath = Interop.procfs.GetCmdLinePathForProcess(procPid);

byte[]? rentedArray = null;
try
Expand Down Expand Up @@ -362,7 +368,7 @@ private Interop.procfs.ParsedStat GetStat()
{
EnsureState(State.HaveNonExitedId);
Interop.procfs.ParsedStat stat;
if (!Interop.procfs.TryReadStatFile(_processId, out stat))
if (!ProcessManager.TryReadStatFile(_processId, out stat))
{
throw new Win32Exception(SR.ProcessInformationUnavailable);
}
Expand Down
Loading

0 comments on commit 773f3cd

Please sign in to comment.