update isolate integrity

This commit is contained in:
prixod
2025-11-04 23:16:13 +04:00
parent b61eac05a2
commit 44cb179cf5
9 changed files with 1446 additions and 14 deletions

View File

@@ -0,0 +1,280 @@
using System.Collections.Generic;
using System.Linq;
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;
namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// C++ compilation service using Isolate sandbox for security
/// </summary>
public class CppCompilationServiceIsolate : ICompilationService
{
private readonly ILogger<CppCompilationServiceIsolate> _logger;
private readonly IConfiguration _configuration;
private readonly IsolateService _isolateService;
private readonly IsolateBoxPool _boxPool;
// Compilation limits (more generous than execution limits)
private const int CompilationTimeLimitSeconds = 30;
private const int CompilationMemoryLimitMb = 512;
public CppCompilationServiceIsolate(
ILogger<CppCompilationServiceIsolate> logger,
IConfiguration configuration,
IsolateService isolateService,
IsolateBoxPool boxPool)
{
_logger = logger;
_configuration = configuration;
_isolateService = isolateService;
_boxPool = boxPool;
}
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
{
var sourceFilePath = Path.Combine(workingDirectory, "solution.cpp");
var executablePath = Path.Combine(workingDirectory, "solution");
_logger.LogInformation("Compiling C++ code with Isolate in {WorkingDirectory} with version {Version}",
workingDirectory, version ?? "latest");
try
{
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
return await CompileFileInIsolateAsync(sourceFilePath, executablePath, version);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
}
private async Task<CompilationResult> CompileFileInIsolateAsync(
string sourceFilePath,
string outputExecutablePath,
string? version = null)
{
int boxId = -1;
try
{
// Acquire box from pool
boxId = await _boxPool.AcquireBoxAsync();
_logger.LogDebug("Acquired isolate box {BoxId} for compilation", boxId);
// Initialize box
await _isolateService.InitBoxAsync(boxId);
// Copy source file to box
var boxDir = $"/var/local/lib/isolate/{boxId}/box";
var sourceFileName = Path.GetFileName(sourceFilePath);
var boxSourcePath = Path.Combine(boxDir, sourceFileName);
var boxOutputName = "solution";
var boxOutputPath = Path.Combine(boxDir, boxOutputName);
File.Copy(sourceFilePath, boxSourcePath, overwrite: true);
// Resolve compiler and flags
var (compiler, compilerFlags) = ResolveVersion(version);
_logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, string.Join(' ', compilerFlags));
// Build compiler arguments
var arguments = new List<string>(compilerFlags);
arguments.Add($"/box/{sourceFileName}");
arguments.Add("-o");
arguments.Add($"/box/{boxOutputName}");
// Prepare stderr output file for compiler messages
var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
// Run compiler in Isolate
// Note: Isolate by default provides access to /usr, /lib, etc. via --share-net=no
// For compilation, we need access to system headers and libraries
var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
{
BoxId = boxId,
Executable = $"/usr/bin/{compiler}",
Arguments = arguments.ToArray(),
TimeLimitSeconds = CompilationTimeLimitSeconds,
WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
MemoryLimitKb = CompilationMemoryLimitMb * 1024,
StackLimitKb = 256 * 1024,
ProcessLimit = 10, // g++ spawns multiple processes
EnableNetwork = false,
StderrFile = stderrFilePath,
WorkingDirectory = "/box",
DirectoryBindings = new List<DirectoryBinding>
{
new DirectoryBinding { HostPath = "/usr/include", SandboxPath = "/usr/include", ReadOnly = true },
new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true }
}
});
// Read compiler output
var compilerOutput = string.Empty;
if (File.Exists(stderrFilePath))
{
compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
}
if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
{
compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
}
// Check for time/memory limits during compilation
if (isolateResult.TimeLimitExceeded)
{
_logger.LogWarning("Compilation time limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation timeout: exceeded {CompilationTimeLimitSeconds}s limit",
CompilerOutput = compilerOutput
};
}
if (isolateResult.MemoryLimitExceeded)
{
_logger.LogWarning("Compilation memory limit exceeded for box {BoxId}", boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
CompilerOutput = compilerOutput
};
}
// Copy compiled executable back if successful
if (isolateResult.ExitCode == 0 && File.Exists(boxOutputPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(outputExecutablePath)!);
File.Copy(boxOutputPath, outputExecutablePath, overwrite: true);
_logger.LogInformation("Compilation successful in Isolate box {BoxId}", boxId);
return new CompilationResult
{
Success = true,
ExecutablePath = outputExecutablePath,
CompilerOutput = compilerOutput
};
}
// Compilation failed
_logger.LogWarning("Compilation failed with exit code {ExitCode} in box {BoxId}",
isolateResult.ExitCode, boxId);
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation failed with exit code {isolateResult.ExitCode}",
CompilerOutput = compilerOutput
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Isolate compilation");
return new CompilationResult
{
Success = false,
ErrorMessage = $"Compilation error: {ex.Message}"
};
}
finally
{
// Cleanup box
if (boxId >= 0)
{
try
{
await _isolateService.CleanupBoxAsync(boxId);
_logger.LogDebug("Cleaned up isolate box {BoxId}", boxId);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to cleanup compilation box {BoxId}", boxId);
}
// Release box back to pool
_boxPool.ReleaseBox(boxId);
_logger.LogDebug("Released compilation box {BoxId} back to pool", boxId);
}
}
}
private (string compiler, List<string> compilerFlags) ResolveVersion(string? version)
{
var defaultCompiler = _configuration["Cpp:Compiler"] ?? "g++";
var defaultFlags = SplitFlags(_configuration["Cpp:CompilerFlags"] ?? "-O2 -std=c++17 -Wall");
if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
return (defaultCompiler, defaultFlags);
}
var versionKey = $"Cpp:Versions:{version}";
var versionCompiler = _configuration[$"{versionKey}:Compiler"];
var versionFlagsValue = _configuration[$"{versionKey}:CompilerFlags"];
if (!string.IsNullOrEmpty(versionCompiler) || !string.IsNullOrEmpty(versionFlagsValue))
{
var resolvedFlags = !string.IsNullOrEmpty(versionFlagsValue)
? SplitFlags(versionFlagsValue)
: defaultFlags;
_logger.LogInformation("Using C++ version {Version} configuration", version);
return (versionCompiler ?? defaultCompiler, resolvedFlags);
}
var normalized = NormalizeCppVersion(version);
if (normalized != null)
{
var flagsWithoutStd = defaultFlags
.Where(flag => !flag.StartsWith("-std=", StringComparison.OrdinalIgnoreCase))
.ToList();
flagsWithoutStd.Add($"-std=c++{normalized}");
_logger.LogInformation("Using inferred C++ standard c++{Standard}", normalized);
return (defaultCompiler, flagsWithoutStd);
}
_logger.LogWarning("C++ version {Version} not found in configuration, using default", version);
return (defaultCompiler, defaultFlags);
}
private static List<string> SplitFlags(string flags) =>
flags.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.ToList();
private static string? NormalizeCppVersion(string version)
{
var cleaned = version.Trim().ToLowerInvariant();
cleaned = cleaned.Replace("c++", string.Empty, StringComparison.OrdinalIgnoreCase)
.Replace("gnu++", string.Empty, StringComparison.OrdinalIgnoreCase)
.Trim('+', ' ');
cleaned = cleaned switch
{
"2b" => "23",
"2a" => "20",
"1z" => "17",
"0x" => "11",
_ => cleaned
};
return cleaned switch
{
"26" => "26",
"23" => "23",
"20" => "20",
"17" => "17",
"14" => "14",
"11" => "11",
_ => null
};
}
}