Merged PR 666947: [Linux Sandbox] Stop wrapping processes in a bash script

[Linux Sandbox] Stop wrapping processes in a bash script
This commit is contained in:
Aleksandar Milicevic 2022-06-20 19:35:54 +00:00
Родитель 12d740cc89
Коммит ccf7741680
15 изменённых файлов: 284 добавлений и 151 удалений

Просмотреть файл

@ -23,6 +23,12 @@ using Xunit.Abstractions;
namespace ContentStoreTest.Distributed.ContentLocation.NuCache
{
// TODO: all fail with
// System.ArgumentException : The reader should have at least 1 length but has 0.
// Stack Trace:
// at BuildXL.Utilities.Serialization.SpanReader.ThrowArgumentException(Int32 minLength) in \.\Public\Src\Utilities\Utilities\Serialization\SpanReader.cs:line 100
[Trait("Category", "WindowsOSOnly")]
public class RocksDbContentLocationDatabaseTests : TestBase
{
protected readonly MemoryClock Clock = new MemoryClock();

Просмотреть файл

@ -43,7 +43,12 @@ namespace BuildXL.Processes
bool NotifyUsage(uint cpuUsageBasisPoints, uint availableRamMB);
/// <summary>
/// Notifies the sandbox that a new pip is about to start. Since the sandbox expects to receive the
/// Notifies the sandbox that a new pip process is ready to be launched.
/// </summary>
void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process);
/// <summary>
/// Notifies the sandbox that a new pip process has started. Since the sandbox expects to receive the
/// process ID of the pip, this method requires that the supplied <paramref name="process"/> has already been started,
/// and hence already has an ID assigned to it. To ensure that the process is not going to request file accesses before the
/// sandbox is notified about it being started, the process should be started in some kind of suspended mode, and
@ -55,7 +60,7 @@ namespace BuildXL.Processes
/// A concrete sandbox connection can override this method to specify additional environment variables
/// that should be set before executing the process.
/// </summary>
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(long pipId);
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName);
/// <summary>
/// SandboxedProcess uses this method to notify the connection that the root process of the pip exited.

Просмотреть файл

@ -42,7 +42,7 @@ namespace BuildXL.Processes
/// <summary>
/// Program to use to enter root jail. Defaults to <c>sudo chroot</c>, which requires NOPASSWD sudo privileges.
/// </summary>
public string RootJailProgram { get; init; } = "sudo chroot";
public (string program, string[] args) RootJailProgram { get; init; } = ("/usr/bin/sudo", new[] { "/usr/sbin/chroot" });
/// <nodoc />
public RootJailInfo(string rootJail, int? userId = null, int? groupId = null, bool disableSandboxing = false, bool disableAuditing = false)
@ -59,11 +59,12 @@ namespace BuildXL.Processes
public void Serialize(BuildXLWriter writer)
{
writer.Write(RootJail);
writer.Write(UserId, (w, v) => w.WriteCompact(v));
writer.Write(GroupId, (w, v) => w.WriteCompact(v));
writer.Write(UserId, static (w, v) => w.WriteCompact(v));
writer.Write(GroupId, static (w, v) => w.WriteCompact(v));
writer.Write(DisableSandboxing);
writer.Write(DisableAuditing);
writer.Write(RootJailProgram);
writer.Write(RootJailProgram.program);
writer.Write(RootJailProgram.args, static (w, a) => w.Write(a));
}
/// <nodoc />
@ -71,13 +72,46 @@ namespace BuildXL.Processes
{
return new RootJailInfo(
rootJail: reader.ReadString(),
userId: reader.ReadNullableStruct(r => r.ReadInt32Compact()),
groupId: reader.ReadNullableStruct(r => r.ReadInt32Compact()),
userId: reader.ReadNullableStruct(static r => r.ReadInt32Compact()),
groupId: reader.ReadNullableStruct(static r => r.ReadInt32Compact()),
disableSandboxing: reader.ReadBoolean(),
disableAuditing: reader.ReadBoolean())
{
RootJailProgram = reader.ReadString(),
RootJailProgram = (reader.ReadString(), reader.ReadArray(static r => r.ReadString())),
};
}
}
/// <summary>
/// Extension methods for <see cref="RootJailInfo"/>.
/// </summary>
public static class RootJailInfoExtensions
{
/// <summary>
/// If <see cref="RootJailInfo.RootJail"/> is set:
/// if <paramref name="path"/> is relative to <see cref="RootJailInfo.RootJail"/> returns an absolute path which
/// when accessed from the root jail resolves to path at location <paramref name="path"/>; otherwise throws.
///
/// If <see cref="RootJailInfo.RootJail"/> is null:
/// returns <paramref name="path"/>
/// </summary>
public static string ToPathInsideRootJail(this RootJailInfo? @this, string path)
{
string rootJailDir = @this?.RootJail;
if (rootJailDir == null)
{
return path;
}
if (!path.StartsWith(rootJailDir.TrimEnd('/') + '/'))
{
throw new BuildXLException($"Root jail dir '{rootJailDir}' must be a parent directory of path '{path}'");
}
var jailRelativePath = path.Substring(rootJailDir.Length);
return jailRelativePath[0] == '/'
? jailRelativePath
: "/" + jailRelativePath;
}
}
}

Просмотреть файл

@ -33,6 +33,14 @@ namespace BuildXL.Processes
/// <inheritdoc />
public bool IsInTestMode { get; }
/// <nodoc />
public static bool IsInDebugMode { get; } =
#if DEBUG
true;
#else
false;
#endif
private readonly ConcurrentDictionary<long, SandboxedProcessUnix> m_pipProcesses = new ConcurrentDictionary<long, SandboxedProcessUnix>();
// TODO: remove at some later point
@ -96,11 +104,7 @@ namespace BuildXL.Processes
throw new BuildXLException($@"Unable to initialize generic sandbox, please check the sources for error code: {m_sandboxConnectionInfo.Error})");
}
#if DEBUG
ProcessUtilities.SetNativeConfiguration(true);
#else
ProcessUtilities.SetNativeConfiguration(false);
#endif
ProcessUtilities.SetNativeConfiguration(IsInDebugMode);
m_AccessReportCallback = (Sandbox.AccessReport report, int code) =>
{
@ -166,11 +170,14 @@ namespace BuildXL.Processes
}
/// <inheritdoc />
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(long pipId)
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName)
{
return Enumerable.Empty<(string, string)>();
}
/// <inheritdoc />
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) {}
/// <inheritdoc />
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process)
{

Просмотреть файл

@ -247,6 +247,9 @@ Use the the following command to load/reload the sandbox kernel extension and fi
return Sandbox.UpdateCurrentResourceUsage(cpuUsage, availableRamMB, m_kextConnectionInfo);
}
/// <inheritdoc />
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) {}
/// <inheritdoc />
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process)
{
@ -290,7 +293,7 @@ Use the the following command to load/reload the sandbox kernel extension and fi
}
/// <inheritdoc />
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(long pipId)
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName)
{
return Enumerable.Empty<(string, string)>();
}

Просмотреть файл

@ -76,8 +76,6 @@ namespace BuildXL.Processes
internal string ReportsFifoPath { get; }
internal string FamPath { get; }
internal string DebugLogJailPath { get; }
private readonly Sandbox.ManagedFailureCallback m_failureCallback;
private readonly Dictionary<string, PathCacheRecord> m_pathCache; // TODO: use AbsolutePath instead of string
private readonly CancellationTokenSource m_waitToCompleteCts;
@ -115,14 +113,10 @@ namespace BuildXL.Processes
Process = process;
ReportsFifoPath = reportsFifoPath;
FamPath = famPath;
DebugLogJailPath = debugLogPath;
m_waitToCompleteCts = new CancellationTokenSource();
m_pathCache = new Dictionary<string, PathCacheRecord>();
m_activeProcesses = new ConcurrentDictionary<int, byte>
{
[process.ProcessId] = 1
};
m_activeProcesses = new ConcurrentDictionary<int, byte>();
m_activeProcessesChecker = new CancellableTimedAction(
CheckActiveProcesses,
intervalMs: Math.Min((int)process.ChildProcessTimeout.TotalMilliseconds, (int)ActiveProcessesCheckerInterval.TotalMilliseconds));
@ -282,6 +276,7 @@ namespace BuildXL.Processes
internal void LogError(string message)
{
message = $"{message} (errno: {Marshal.GetLastWin32Error()})";
Process.LogDebug("[ERROR]: " + message);
m_failureCallback?.Invoke(1, message);
}
@ -415,7 +410,7 @@ namespace BuildXL.Processes
using var readHandle = IO.Open(fifoName, IO.OpenFlags.O_RDONLY, 0);
if (readHandle.IsInvalid)
{
LogError($"Opening FIFO {fifoName} for reading failed");
LogError($"Opening FIFO {fifoName} for reading failed.");
return;
}
@ -435,7 +430,7 @@ namespace BuildXL.Processes
if (numRead < 0) // error
{
LogError($"Read from FIFO {ReportsFifoPath} failed with return value {numRead}");
LogError($"Read from FIFO {ReportsFifoPath} failed with return value {numRead}.");
break;
}
@ -447,7 +442,7 @@ namespace BuildXL.Processes
numRead = Read(readHandle, messageBytes.Instance, 0, messageLength);
if (numRead < messageLength)
{
LogError($"Read from FIFO {ReportsFifoPath} failed: read only {numRead} out of {messageLength} bytes");
LogError($"Read from FIFO {ReportsFifoPath} failed: read only {numRead} out of {messageLength} bytes.");
messageBytes.Dispose();
break;
}
@ -487,12 +482,7 @@ namespace BuildXL.Processes
{
m_failureCallback = failureCallback;
IsInTestMode = isInTestMode;
#if DEBUG
BuildXL.Native.Processes.ProcessUtilities.SetNativeConfiguration(true);
#else
BuildXL.Native.Processes.ProcessUtilities.SetNativeConfiguration(false);
#endif
BuildXL.Native.Processes.ProcessUtilities.SetNativeConfiguration(BuildXL.Processes.SandboxConnection.IsInDebugMode);
}
/// <inheritdoc />
@ -530,34 +520,27 @@ namespace BuildXL.Processes
}
/// <inheritdoc />
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(long pipId)
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName)
{
if (!m_pipProcesses.TryGetValue(pipId, out var info))
{
throw new BuildXLException($"No info found for pip id {pipId}");
}
var detoursLibPath = CopyToRootJailIfNeeded(info.RootJailInfo?.RootJail, DetoursLibFile);
(string fifoPath, string famPath, string debugLogPath) = GetPaths(info.RootJailInfo, uniqueName);
var detoursLibPath = CopyToRootJailIfNeeded(info.Process.RootJail, DetoursLibFile);
// TODO: the ROOT_PID env var is a temporary solution for breakway processes
// CODESYNC: Public/Src/Sandbox/Linux/bxl_observer.hpp
yield return ("__BUILDXL_ROOT_PID", info.Process.ProcessId.ToString());
yield return ("__BUILDXL_FAM_PATH", info.Process.ToPathInsideRootJail(info.FamPath));
yield return ("__BUILDXL_ROOT_PID", "1"); // CODESYNC: Public/Src/Sandbox/Linux/bxl_observer.hpp (temp solution for breakaway processes)
yield return ("__BUILDXL_FAM_PATH", info.RootJailInfo.ToPathInsideRootJail(famPath));
yield return ("__BUILDXL_DETOURS_PATH", detoursLibPath);
if (info.DebugLogJailPath != null)
if (debugLogPath != null)
{
yield return ("__BUILDXL_LOG_PATH", info.DebugLogJailPath);
yield return ("__BUILDXL_LOG_PATH", info.RootJailInfo.ToPathInsideRootJail(debugLogPath));
}
if (info.Process.RootJailInfo?.DisableSandboxing != true)
if (info.RootJailInfo?.DisableSandboxing != true)
{
yield return ("LD_PRELOAD", detoursLibPath + ":$LD_PRELOAD");
yield return ("LD_PRELOAD", detoursLibPath + ":" + info.EnvironmentVariables.TryGetValue("LD_PRELOAD", string.Empty));
}
if (info.Process.RootJailInfo?.DisableAuditing != true)
if (info.RootJailInfo?.DisableAuditing != true)
{
yield return ("LD_AUDIT", CopyToRootJailIfNeeded(info.Process.RootJail, AuditLibFile) + ":$LD_AUDIT");
yield return ("LD_AUDIT", CopyToRootJailIfNeeded(info.RootJailInfo?.RootJail, AuditLibFile) + ":" + info.EnvironmentVariables.TryGetValue("LD_AUDIT", string.Empty));
}
}
@ -573,20 +556,37 @@ namespace BuildXL.Processes
return "/" + basename;
}
private (string fifo, string fam, string log) GetPaths(RootJailInfo? rootJailInfo, string uniqueName)
{
string rootDir = rootJailInfo?.RootJail ?? Path.GetTempPath();
string fifoPath = Path.Combine(rootDir, $"bxl_{uniqueName}.fifo");
string famPath = Path.ChangeExtension(fifoPath, ".fam");
string debugLogPath = IsInTestMode ? Path.ChangeExtension(fifoPath, ".log") : null;
return (fifo: fifoPath, fam: famPath, log: debugLogPath);
}
/// <inheritdoc />
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process)
{
Contract.Requires(process.Started);
if (!m_pipProcesses.TryGetValue(process.PipId, out var info))
{
throw new BuildXLException($"No info found for pip id {process.PipId}");
}
info.AddPid(process.ProcessId);
return true;
}
/// <inheritdoc />
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process)
{
Contract.Requires(!process.Started);
Contract.Requires(process.PipId != 0);
string rootDir = process.RootJail ?? Path.GetTempPath();
string fifoPath = Path.Combine(rootDir, $"bxl_Pip{process.PipSemiStableHash:X}.{process.ProcessId}.fifo");
string famPath = Path.ChangeExtension(fifoPath, ".fam");
string debugLogPath = null;
if (IsInTestMode)
(string fifoPath, string famPath, string debugLogPath) = GetPaths(process.RootJailInfo, process.UniqueName);
if (debugLogPath != null)
{
debugLogPath = process.ToPathInsideRootJail(Path.ChangeExtension(fifoPath, ".log"));
fam.AddPath(toAbsPath(debugLogPath), mask: FileAccessPolicy.MaskAll, values: FileAccessPolicy.AllowAll);
fam.AddPath(toAbsPath(process.ToPathInsideRootJail(debugLogPath)), mask: FileAccessPolicy.MaskAll, values: FileAccessPolicy.AllowAll);
}
// serialize FAM
@ -607,10 +607,10 @@ namespace BuildXL.Processes
process.LogDebug($"Saved FAM to '{famPath}'");
// create a FIFO (named pipe)
Analysis.IgnoreResult(FileUtilities.TryDeleteFile(fifoPath, retryOnFailure: false));
if (IO.MkFifo(fifoPath, IO.FilePermissions.S_IRWXU) != 0)
{
m_failureCallback?.Invoke(1, $"Creating FIFO {fifoPath} failed");
return false;
throw new BuildXLException($"Creating FIFO {fifoPath} failed. (errno: {Marshal.GetLastWin32Error()})");
}
process.LogDebug($"Created FIFO at '{fifoPath}'");
@ -623,7 +623,6 @@ namespace BuildXL.Processes
}
info.Start();
return true;
AbsolutePath toAbsPath(string path) => AbsolutePath.Create(process.PathTable, path);
}

Просмотреть файл

@ -104,6 +104,11 @@ namespace BuildXL.Processes
/// </summary>
internal const string ShellExecutable = "/bin/bash"; // /bin/sh doesn't support env vars that contain funky characters (e.g., [])
/// <summary>
/// Full path to the standard "env" Unix program.
/// </summary>
internal const string EnvExecutable = "/usr/bin/env";
/// <summary>
/// Optional configuration for running this process in a root jail.
/// </summary>
@ -116,31 +121,7 @@ namespace BuildXL.Processes
private const double NanosecondsToMillisecondsFactor = 1000000d;
/// <summary>
/// If <see cref="RootJail"/> is set:
/// if <paramref name="path"/> is relative to <see cref="RootJail"/> returns an absolute path which
/// when accessed from the root jail resolves to path at location <paramref name="path"/>; otherwise throws.
///
/// If <see cref="RootJail"/> is null:
/// returns <paramref name="path"/>
/// </summary>
internal string ToPathInsideRootJail(string path)
{
if (RootJail == null)
{
return path;
}
if (!path.StartsWith(RootJail))
{
ThrowBuildXLException($"Path '{path}' is not relative to root jail: '{RootJail}'");
}
var jailRelativePath = path.Substring(RootJail.Length);
return jailRelativePath[0] == '/'
? jailRelativePath
: "/" + jailRelativePath;
}
internal string ToPathInsideRootJail(string path) => RootJailInfo.ToPathInsideRootJail(path);
/// <nodoc />
public SandboxedProcessUnix(SandboxedProcessInfo info, bool ignoreReportedAccesses = false)
@ -189,7 +170,8 @@ namespace BuildXL.Processes
m_pendingReports = new ActionBlock<AccessReport>(HandleAccessReport, executionOptions);
// install a 'ProcessStarted' handler that informs the sandbox of the newly started process
// install 'ProcessReady' and 'ProcessStarted' handlers to inform the sandbox
ProcessReady += () => SandboxConnection.NotifyPipReady(info.LoggingContext, info.FileAccessManifest, this);
ProcessStarted += (pid) => OnProcessStartedAsync(info).GetAwaiter().GetResult();
}
@ -219,13 +201,12 @@ namespace BuildXL.Processes
}
}
private bool NeedsShellWrapping() => OperatingSystemHelper.IsMacOS;
/// <inheritdoc />
protected override System.Diagnostics.Process CreateProcess(SandboxedProcessInfo info)
{
var process = base.CreateProcess(info);
process.StartInfo.FileName = ShellExecutable;
process.StartInfo.Arguments = string.Empty;
process.StartInfo.RedirectStandardInput = true;
if (info.RootJailInfo?.RootJail != null)
{
@ -233,6 +214,72 @@ namespace BuildXL.Processes
process.StartInfo.WorkingDirectory = "/";
}
if (NeedsShellWrapping())
{
// shell script streamed to stdin
process.StartInfo.FileName = ShellExecutable;
process.StartInfo.Arguments = string.Empty;
}
else if (info.RootJailInfo == null)
{
foreach (var kvp in AdditionalEnvVars(info))
{
process.StartInfo.EnvironmentVariables[kvp.Item1] = kvp.Item2;
}
}
else
{
#if NETCOREAPP
var rootJailInfo = info.RootJailInfo.Value;
// top-level process is the root jail program
process.StartInfo.FileName = rootJailInfo.RootJailProgram.program;
process.StartInfo.Arguments = string.Empty;
process.StartInfo.ArgumentList.Clear();
// root jail arguments
foreach (string rootJailArg in rootJailInfo.RootJailProgram.args)
{
process.StartInfo.ArgumentList.Add(rootJailArg);
}
if (rootJailInfo.UserId != null && rootJailInfo.GroupId != null)
{
process.StartInfo.ArgumentList.Add($"--userspec={rootJailInfo.UserId}:{rootJailInfo.GroupId}");
}
// root jail directory
process.StartInfo.ArgumentList.Add(rootJailInfo.RootJail);
// inside the jail, run "env" to change into user-specified directory as well as to set environment variables before running user-specified program
process.StartInfo.ArgumentList.Add(EnvExecutable);
// change directory into what the user specified
process.StartInfo.ArgumentList.Add("-C");
process.StartInfo.ArgumentList.Add(info.WorkingDirectory);
// propagate environment variables (because root jail program won't do it)
process.StartInfo.ArgumentList.Add("-i");
foreach (var kvp in process.StartInfo.Environment.Select(kvp => (kvp.Key, kvp.Value)).Concat(AdditionalEnvVars(info)))
{
process.StartInfo.ArgumentList.Add($"{kvp.Item1}={kvp.Item2}");
}
// finally add the original executable and its arguments
process.StartInfo.ArgumentList.Add(info.FileName);
foreach (var arg in CommandLineEscaping.SplitArguments(info.Arguments))
{
process.StartInfo.ArgumentList.Add(arg.Value.ToString());
}
#else
throw new ArgumentException($"Running {nameof(SandboxedProcessUnix)} in a non .NET Core environment should not be possible");
#endif
}
// In any case, allow read access to the process file.
// When executed using external tool, the manifest tree has been sealed, and cannot be modified.
// We take care of adding this path in the manifest in SandboxedProcessPipExecutor.cs;
// see AddUnixSpecificSandcboxedProcessFileAccessPolicies
if (!info.FileAccessManifest.IsManifestTreeBlockSealed)
{
info.FileAccessManifest.AddPath(
AbsolutePath.Create(PathTable, process.StartInfo.FileName),
mask: FileAccessPolicy.MaskNothing,
values: FileAccessPolicy.AllowReadAlways);
}
return process;
}
@ -246,23 +293,16 @@ namespace BuildXL.Processes
/// </summary>
private async Task OnProcessStartedAsync(SandboxedProcessInfo info)
{
// Generate "Process Created" report because the rest of the system expects to see it before any other file access reports
//
// IMPORTANT: do this before notifying sandbox kernel extension, because otherwise it can happen that a report
// from the extension is received before the "process created" report is handled, causing
// a "Should see a process creation before its accesses" assertion exception.
ReportProcessCreated();
// Allow read access for /bin/sh
// When executed using external tool, the manifest tree has been sealed, and cannot be modified.
// We take care of adding this path in the manifest in SandboxedProcessPipExecutor.cs;
// see AddUnixSpecificSandcboxedProcessFileAccessPolicies
if (!info.FileAccessManifest.IsManifestTreeBlockSealed)
if (NeedsShellWrapping())
{
info.FileAccessManifest.AddPath(
AbsolutePath.Create(PathTable, Process.StartInfo.FileName),
mask: FileAccessPolicy.MaskNothing,
values: FileAccessPolicy.AllowReadAlways);
// The shell wrapper script started, so generate "Process Created" report before the actual pip process starts
// (the rest of the system expects to see it before any other file access reports).
//
// IMPORTANT (macOS-only):
// do this before notifying sandbox kernel extension, because otherwise it can happen that a report
// from the extension is received before the "process created" report is handled, causing
// a "Should see a process creation before its accesses" assertion exception.
ReportProcessCreated();
}
if (OperatingSystemHelper.IsLinuxOS)
@ -301,7 +341,6 @@ namespace BuildXL.Processes
private string DetoursFile => Path.Combine(Path.GetDirectoryName(AssemblyHelper.GetThisProgramExeLocation()), "libBuildXLDetours.dylib");
private const string DetoursEnvVar = "DYLD_INSERT_LIBRARIES";
private const string EofDelim = "__EOF__";
/// <inheritdoc />
protected override IEnumerable<ReportedProcess> GetSurvivingChildProcesses()
@ -400,7 +439,7 @@ namespace BuildXL.Processes
/// </summary>
private IReadOnlyList<ReportedProcess> GetCurrentlyActiveChildProcesses()
{
return m_reports.GetActiveProcesses().Where(p => p.ProcessId != ProcessId).ToList();
return m_reports.GetActiveProcesses().Where(p => p.ProcessId > 0 && p.ProcessId != ProcessId).ToList();
}
private void KillAllChildProcesses()
@ -444,7 +483,7 @@ namespace BuildXL.Processes
private static string EnsureQuoted(string cmdLineArgs)
{
#if NET_CORE
#if NETCOREAPP
if (cmdLineArgs == null)
{
return null;
@ -472,9 +511,29 @@ namespace BuildXL.Processes
#endif
}
// TODO: instead of generating a bash script (and be exposed to all kinds of injection attacks) we should write a wrapper runner program
private async Task FeedStdInAsync(SandboxedProcessInfo info, [CanBeNull] string processStdinFileName)
{
Contract.Requires(info.RootJailInfo == null || !NeedsShellWrapping(), $"Cannot run root jail on this OS");
// if no shell wrapping is needed, only feed processStdinFileName (if specified)
if (!NeedsShellWrapping())
{
if (processStdinFileName != null)
{
string stdinContent =
#if NETCOREAPP
await File.ReadAllTextAsync(processStdinFileName);
#else
File.ReadAllText(processStdinFileName);
#endif
await Process.StandardInput.WriteAsync(stdinContent);
}
Process.StandardInput.Close();
return;
}
// TODO: instead of generating a bash script (and be exposed to all kinds of injection attacks) we should write a wrapper runner program
string redirectedStdin = processStdinFileName != null ? $" < {ToPathInsideRootJail(processStdinFileName)}" : string.Empty;
// this additional round of escaping is needed because we are flushing the arguments to a shell script
@ -489,33 +548,17 @@ namespace BuildXL.Processes
lines.Add("set -e");
if (info.RootJailInfo != null)
{
// A process executed in a chroot jail does not automatically inherit the environment from the parent process,
// so we must export the vars before entering chroot and then source them once inside.
const string BxlEnvFile = "bxl_pip_env.sh";
lines.Add($"export -p > '{info.RootJailInfo.Value.RootJail}/{BxlEnvFile}'");
lines.Add($"exec {info.RootJailInfo.Value.RootJailProgram} --userspec={userIdExpr()}:{groupIdExpr()} '{info.RootJailInfo.Value.RootJail}' {ShellExecutable} <<'{EofDelim}'");
lines.Add("set -e");
lines.Add($". /{BxlEnvFile}");
lines.Add($"cd \"{info.WorkingDirectory}\"");
}
if (info.SandboxConnection.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours)
{
lines.Add($"export {DetoursEnvVar}={DetoursFile}");
}
foreach (var envKvp in info.SandboxConnection.AdditionalEnvVarsToSet(info.FileAccessManifest.PipId))
foreach (var envKvp in info.SandboxConnection.AdditionalEnvVarsToSet(info, UniqueName))
{
lines.Add($"export {envKvp.Item1}={envKvp.Item2}");
}
lines.Add($"exec {cmdLine}");
if (info.RootJailInfo != null)
{
lines.Add(EofDelim);
}
SetExecutePermissionIfNeeded(info.FileName, throwIfNotFound: false);
foreach (string line in lines)
@ -524,11 +567,16 @@ namespace BuildXL.Processes
}
LogDebug("Done feeding stdin:" + Environment.NewLine + string.Join(Environment.NewLine, lines));
Process.StandardInput.Close();
}
string userIdExpr() => info.RootJailInfo?.UserId?.ToString() ?? "$(id -u)";
string groupIdExpr() => info.RootJailInfo?.GroupId?.ToString() ?? "$(id -u)";
private IEnumerable<(string, string)> AdditionalEnvVars(SandboxedProcessInfo info)
{
return info.SandboxConnection
.AdditionalEnvVarsToSet(info, UniqueName)
.Concat(info.SandboxConnection.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours
? new[] { (DetoursEnvVar, DetoursFile) }
: Array.Empty<(string, string)>());
}
internal override void FeedStdErr(SandboxedProcessOutputBuilder builder, string line)
@ -700,7 +748,7 @@ namespace BuildXL.Processes
/// </summary>
internal void LogDebug(string message)
{
if (ShouldReportFileAccesses)
if (BuildXL.Processes.SandboxConnection.IsInDebugMode || ShouldReportFileAccesses)
{
LogProcessState(message);
}

Просмотреть файл

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.ContractsLight;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BuildXL.Interop;
using BuildXL.Utilities;
@ -64,6 +65,11 @@ namespace BuildXL.Processes
protected event ProcessStartedHandler ProcessStarted;
/// <summary>
/// Raised right before the process is started.
/// </summary>
protected event Action ProcessReady;
/// <summary>
/// Indicates if the process has been force killed during execution.
/// </summary>
@ -89,6 +95,20 @@ namespace BuildXL.Processes
/// </summary>
public long PipSemiStableHash { get; }
/// <summary>
/// Unique name for this process during this build. May change build over build, but need not be unique across different builds.
/// </summary>
/// <remarks>
/// Normally, PipId (or PipSemiStableHash) will meet all these criteria; unit tests are an exception, which is why this
/// property is added.
///
/// For example, this property could be used to create auxiliary per-pip files that will not clash with each other
/// (e.g., Linux sandbox needs to create a FIFO file per pip).
/// </remarks>
public string UniqueName { get; }
private int m_uniqueNameCounter = 0;
/// <summary>
/// Returns the path table from the supplied <see cref="SandboxedProcessInfo"/>.
/// </summary>
@ -126,6 +146,7 @@ namespace BuildXL.Processes
TimeoutDumpDirectory = info.TimeoutDumpDirectory;
ShouldReportFileAccesses = info.FileAccessManifest?.ReportFileAccesses == true;
DetoursListener = info.DetoursEventListener;
UniqueName = $"Pip{info.FileAccessManifest.PipId:X}.{Interlocked.Increment(ref m_uniqueNameCounter)}";
info.Timeout ??= s_defaultProcessTimeout;
@ -177,6 +198,7 @@ namespace BuildXL.Processes
{
Contract.Requires(!Started, "Process was already started. Cannot start process more than once.");
ProcessReady?.Invoke();
Started = true;
m_processExecutor.Start();
ProcessStarted?.Invoke(ProcessId);

Просмотреть файл

@ -50,13 +50,9 @@ namespace Test.BuildXL.Processes
public bool IsInTestMode => true;
public bool NotifyUsage(uint cpuUsage, uint availableRamMB) { return true; }
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) {}
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) { return true; }
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(long pipId)
{
return Enumerable.Empty<(string, string)>();
}
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName) { return Enumerable.Empty<(string, string)>(); }
public void NotifyPipProcessTerminated(long pipId, int processId) { ProcessTerminated?.Invoke(pipId, processId); }

Просмотреть файл

@ -19,12 +19,20 @@ BxlObserver* BxlObserver::GetInstance()
BxlObserver::BxlObserver()
{
char pidStr[20] = {0};
empty_str_ = "";
real_readlink("/proc/self/exe", progFullPath_, PATH_MAX);
disposed_ = false;
const char *rootPidStr = getenv(BxlEnvRootPid);
rootPid_ = is_null_or_empty(rootPidStr) ? -1 : atoi(rootPidStr);
disposed_ = false;
// value of "1" -> special case, set by BuildXL for the root process
if (rootPid_ == 1) {
rootPid_ = getpid();
sprintf(pidStr, "%d", rootPid_);
setenv(BxlEnvRootPid, pidStr, /* replace */ 1);
}
InitLogFile();
InitFam();

Просмотреть файл

@ -2,21 +2,14 @@
readonly __dir=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
set -e
set -u
function onExit {
popd > /dev/null 2>&1
}
set -euo pipefail
readonly version="$1"
readonly pkgName="runtime.linux-x64.BuildXL.${version}"
pushd "${__dir}" > /dev/null 2>&1
trap onExit EXIT
cd "${__dir}"
make debug release
make cleanall
bash build-manylinux.sh
cd bin

Просмотреть файл

@ -75,7 +75,7 @@ namespace Test.BuildXL.TestUtilities.Xunit
private static void FailureCallback(int status, string description)
{
XAssert.Fail($"Kernel extension failed. Status: {status}. Description: {description}");
XAssert.Fail($"Sandbox failed. Status: {status}. Description: {description}");
}
private static SandboxKind ReadSandboxKindFromEnvVars()

Просмотреть файл

@ -126,6 +126,18 @@ namespace BuildXL.Utilities
bool ContainsKey([NotNull]string key);
}
// ==================================================================================
// == IBuildParameters Extension Methods
// ==================================================================================
/// <summary>
/// Retrieves the value of a parameter named <paramref name="key"/>, if one is found; otherwise returns <paramref name="defaultValue"/>.
/// </summary>
public static string TryGetValue(this IBuildParameters @this, string key, string defaultValue)
{
return @this != null && @this.ContainsKey(key) ? @this[key] : defaultValue;
}
// ==================================================================================
// == Public Factory Methods
// ==================================================================================

Просмотреть файл

@ -4605,7 +4605,7 @@
"Type": "NuGet",
"NuGet": {
"Name": "runtime.linux-x64.BuildXL",
"Version": "0.0.59"
"Version": "0.0.65"
}
}
},

Просмотреть файл

@ -412,7 +412,7 @@ config({
// Runtime dependencies for Linux
{
id: "runtime.linux-x64.BuildXL",
version: "0.0.59",
version: "0.0.65",
osSkip: importFile(f`config.microsoftInternal.dsc`).isMicrosoftInternal
? []
: [ "win", "macOS", "unix" ]