Sandbox: Beenden sammelt den ganzen Baum ein und wartet nicht ewig auf Rohre (#654)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d97d4313f1
commit
c220d3cdb4
@@ -251,7 +251,13 @@ public sealed class BwrapSandbox : ISandboxRunner
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(stdout, stderr);
|
// After a stop, the streams end with the processes — but a reader must never hang on a
|
||||||
|
// pipe something still holds. Bounded, then the result is what arrived.
|
||||||
|
var pumps = Task.WhenAll(stdout, stderr);
|
||||||
|
if (timedOut || output.Truncated)
|
||||||
|
await Task.WhenAny(pumps, Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None));
|
||||||
|
else
|
||||||
|
await pumps;
|
||||||
if (timedOut)
|
if (timedOut)
|
||||||
_logger.LogWarning("Sandbox run of {Program} stopped after {Timeout}", request.Executable, request.Limits.Timeout);
|
_logger.LogWarning("Sandbox run of {Program} stopped after {Timeout}", request.Executable, request.Limits.Timeout);
|
||||||
|
|
||||||
@@ -280,16 +286,75 @@ public sealed class BwrapSandbox : ISandboxRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>bwrap dies, and with it — die-with-parent, own PID namespace — everything inside.</summary>
|
/// <summary>
|
||||||
|
/// Stops the run: everything below bwrap is collected first, then all of it is killed.
|
||||||
|
///
|
||||||
|
/// <para><b>Not trusting <c>--die-with-parent</c>.</b> The kernel clears the parent-death
|
||||||
|
/// signal when a process changes its credentials — and bwrap does exactly that while it
|
||||||
|
/// builds the user namespace. Seen on the test machine: bwrap's monitor was gone, the
|
||||||
|
/// sandbox lived on under PID 1 and kept the output pipe open. Killing the sandbox's own
|
||||||
|
/// PID 1 (the init of its PID namespace) takes every process inside with it.</para>
|
||||||
|
/// </summary>
|
||||||
private static void Kill(Process process)
|
private static void Kill(Process process)
|
||||||
{
|
{
|
||||||
|
int root;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!process.HasExited)
|
root = process.Id;
|
||||||
process.Kill(entireProcessTree: true);
|
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException) { }
|
catch (InvalidOperationException)
|
||||||
catch (System.ComponentModel.Win32Exception) { }
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pid in Descendants(root).Append(root))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var victim = Process.GetProcessById(pid);
|
||||||
|
victim.Kill();
|
||||||
|
}
|
||||||
|
catch (ArgumentException) { } // already gone
|
||||||
|
catch (InvalidOperationException) { }
|
||||||
|
catch (System.ComponentModel.Win32Exception) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>All processes below <paramref name="root"/>, from /proc — collected before anything is killed.</summary>
|
||||||
|
private static IEnumerable<int> Descendants(int root)
|
||||||
|
{
|
||||||
|
var children = new Dictionary<int, List<int>>();
|
||||||
|
foreach (var dir in Directory.EnumerateDirectories("/proc"))
|
||||||
|
{
|
||||||
|
if (!int.TryParse(Path.GetFileName(dir), out var pid))
|
||||||
|
continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// /proc/<pid>/stat: "pid (comm) state ppid …" — comm may hold spaces and parentheses.
|
||||||
|
var stat = File.ReadAllText(Path.Combine(dir, "stat"));
|
||||||
|
var fields = stat[(stat.LastIndexOf(')') + 2)..].Split(' ');
|
||||||
|
if (int.TryParse(fields[1], out var parent))
|
||||||
|
{
|
||||||
|
if (!children.TryGetValue(parent, out var list))
|
||||||
|
children[parent] = list = [];
|
||||||
|
list.Add(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException) { } // it ended meanwhile
|
||||||
|
catch (UnauthorizedAccessException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<int>();
|
||||||
|
var queue = new Queue<int>([root]);
|
||||||
|
while (queue.Count > 0 && result.Count < 10_000)
|
||||||
|
{
|
||||||
|
foreach (var child in children.GetValueOrDefault(queue.Dequeue()) ?? [])
|
||||||
|
{
|
||||||
|
result.Add(child);
|
||||||
|
queue.Enqueue(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Kilobytes(long bytes) => Math.Max(1, bytes / 1024).ToString(CultureInfo.InvariantCulture);
|
private static string Kilobytes(long bytes) => Math.Max(1, bytes / 1024).ToString(CultureInfo.InvariantCulture);
|
||||||
|
|||||||
Reference in New Issue
Block a user