|
|
@@ -7,6 +7,7 @@
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <string>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <cwctype>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <vector>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
|
@@ -214,6 +215,120 @@ static bool CopyRuntimeTree(
|
|
|
|
return success && finalError == ERROR_NO_MORE_FILES;
|
|
|
|
return success && finalError == ERROR_NO_MORE_FILES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static constexpr ULONGLONG kMaxSnapshotBytes = 10ull * 1024 * 1024;
|
|
|
|
|
|
|
|
static constexpr DWORD kMaxSnapshotFiles = 50;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool SnapshotExtensionAllowed(const std::wstring& name) {
|
|
|
|
|
|
|
|
const size_t dot = name.find_last_of(L'.');
|
|
|
|
|
|
|
|
if (dot == std::wstring::npos) return false;
|
|
|
|
|
|
|
|
std::wstring extension = name.substr(dot);
|
|
|
|
|
|
|
|
for (wchar_t& ch : extension) ch = static_cast<wchar_t>(towlower(ch));
|
|
|
|
|
|
|
|
static const wchar_t* const allowed[] = {
|
|
|
|
|
|
|
|
L".py", L".dart", L".md", L".txt", L".json", L".yaml", L".yml",
|
|
|
|
|
|
|
|
L".toml", L".html", L".css", L".js", L".ts", L".tsx", L".jsx",
|
|
|
|
|
|
|
|
L".sh", L".ps1"
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
for (const wchar_t* candidate : allowed) {
|
|
|
|
|
|
|
|
if (extension == candidate) return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool CopySnapshotTree(
|
|
|
|
|
|
|
|
const std::wstring& source, const std::wstring& destination,
|
|
|
|
|
|
|
|
ULONGLONG& copiedBytes, DWORD& copiedFiles, unsigned depth = 0
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (depth > 24 || copiedFiles > kMaxSnapshotFiles) return false;
|
|
|
|
|
|
|
|
const DWORD sourceAttributes = GetFileAttributesW(source.c_str());
|
|
|
|
|
|
|
|
if (sourceAttributes == INVALID_FILE_ATTRIBUTES ||
|
|
|
|
|
|
|
|
!(sourceAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
|
|
|
|
|
|
|
|
(sourceAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) return false;
|
|
|
|
|
|
|
|
if (!CreateDirectoryW(destination.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
WIN32_FIND_DATAW entry{};
|
|
|
|
|
|
|
|
HANDLE search = FindFirstFileW((source + L"\\*").c_str(), &entry);
|
|
|
|
|
|
|
|
if (search == INVALID_HANDLE_VALUE) return false;
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
if (wcscmp(entry.cFileName, L".") == 0 || wcscmp(entry.cFileName, L"..") == 0) continue;
|
|
|
|
|
|
|
|
if ((entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || entry.cFileName[0] == L'.') {
|
|
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::wstring sourcePath = source + L"\\" + entry.cFileName;
|
|
|
|
|
|
|
|
const std::wstring destinationPath = destination + L"\\" + entry.cFileName;
|
|
|
|
|
|
|
|
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
|
|
|
|
|
|
success = CopySnapshotTree(sourcePath, destinationPath,
|
|
|
|
|
|
|
|
copiedBytes, copiedFiles, depth + 1);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (!SnapshotExtensionAllowed(entry.cFileName)) {
|
|
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attributes{};
|
|
|
|
|
|
|
|
if (!GetFileAttributesExW(sourcePath.c_str(), GetFileExInfoStandard, &attributes) ||
|
|
|
|
|
|
|
|
(attributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
|
|
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const ULONGLONG size = (static_cast<ULONGLONG>(attributes.nFileSizeHigh) << 32) |
|
|
|
|
|
|
|
|
attributes.nFileSizeLow;
|
|
|
|
|
|
|
|
if (copiedFiles >= kMaxSnapshotFiles || size > kMaxSnapshotBytes - copiedBytes ||
|
|
|
|
|
|
|
|
!CopyFileW(sourcePath.c_str(), destinationPath.c_str(), FALSE)) {
|
|
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
copiedBytes += size;
|
|
|
|
|
|
|
|
++copiedFiles;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (FindNextFileW(search, &entry));
|
|
|
|
|
|
|
|
const DWORD finalError = GetLastError();
|
|
|
|
|
|
|
|
FindClose(search);
|
|
|
|
|
|
|
|
return success && finalError == ERROR_NO_MORE_FILES;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static std::wstring ReadEnvironmentString(const wchar_t* name) {
|
|
|
|
|
|
|
|
std::vector<wchar_t> buffer(32768);
|
|
|
|
|
|
|
|
DWORD length = GetEnvironmentVariableW(name, buffer.data(),
|
|
|
|
|
|
|
|
static_cast<DWORD>(buffer.size()));
|
|
|
|
|
|
|
|
if (length == 0 || length >= buffer.size()) return {};
|
|
|
|
|
|
|
|
return std::wstring(buffer.data(), length);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool ResolvePythonEntry(
|
|
|
|
|
|
|
|
const std::wstring& projectRoot, std::wstring relative, std::wstring& resolved
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (relative.empty() || relative.size() > 240 || relative.front() == L'/' ||
|
|
|
|
|
|
|
|
relative.front() == L'\\' || relative.find(L':') != std::wstring::npos) return false;
|
|
|
|
|
|
|
|
for (wchar_t& ch : relative) if (ch == L'\\') ch = L'/';
|
|
|
|
|
|
|
|
std::wstring current = projectRoot;
|
|
|
|
|
|
|
|
size_t start = 0;
|
|
|
|
|
|
|
|
bool finalPart = false;
|
|
|
|
|
|
|
|
while (!finalPart) {
|
|
|
|
|
|
|
|
size_t separator = relative.find(L'/', start);
|
|
|
|
|
|
|
|
finalPart = separator == std::wstring::npos;
|
|
|
|
|
|
|
|
const std::wstring part = relative.substr(start,
|
|
|
|
|
|
|
|
finalPart ? std::wstring::npos : separator - start);
|
|
|
|
|
|
|
|
if (part.empty() || part == L"." || part == L".." || part.front() == L'.' ||
|
|
|
|
|
|
|
|
part.find_first_of(L"<>|?*\"") != std::wstring::npos) return false;
|
|
|
|
|
|
|
|
current += L"\\" + part;
|
|
|
|
|
|
|
|
const DWORD attributes = GetFileAttributesW(current.c_str());
|
|
|
|
|
|
|
|
if (attributes == INVALID_FILE_ATTRIBUTES ||
|
|
|
|
|
|
|
|
(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) return false;
|
|
|
|
|
|
|
|
if (!finalPart && !(attributes & FILE_ATTRIBUTE_DIRECTORY)) return false;
|
|
|
|
|
|
|
|
if (finalPart && (attributes & FILE_ATTRIBUTE_DIRECTORY)) return false;
|
|
|
|
|
|
|
|
start = separator + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t dot = relative.find_last_of(L'.');
|
|
|
|
|
|
|
|
if (dot == std::wstring::npos) return false;
|
|
|
|
|
|
|
|
std::wstring extension = relative.substr(dot);
|
|
|
|
|
|
|
|
for (wchar_t& ch : extension) ch = static_cast<wchar_t>(towlower(ch));
|
|
|
|
|
|
|
|
if (extension != L".py") return false;
|
|
|
|
|
|
|
|
resolved = std::move(current);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool CopyPythonRuntime(
|
|
|
|
static bool CopyPythonRuntime(
|
|
|
|
const std::wstring& sourceRoot, const std::wstring& destinationRoot,
|
|
|
|
const std::wstring& sourceRoot, const std::wstring& destinationRoot,
|
|
|
|
ULONGLONG& copiedBytes, DWORD& copiedFiles
|
|
|
|
ULONGLONG& copiedBytes, DWORD& copiedFiles
|
|
|
@@ -243,27 +358,20 @@ static bool CopyPythonRuntime(
|
|
|
|
copiedBytes, copiedFiles);
|
|
|
|
copiedBytes, copiedFiles);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool WriteFileBytes(const std::wstring& path, const char* bytes, DWORD length) {
|
|
|
|
static bool IsSmokeResult(const std::wstring& path) {
|
|
|
|
HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW,
|
|
|
|
|
|
|
|
FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
|
|
|
|
|
|
if (file == INVALID_HANDLE_VALUE) return false;
|
|
|
|
|
|
|
|
DWORD written = 0;
|
|
|
|
|
|
|
|
bool success = WriteFile(file, bytes, length, &written, nullptr) && written == length;
|
|
|
|
|
|
|
|
CloseHandle(file);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool IsVersionText(const std::wstring& path) {
|
|
|
|
|
|
|
|
HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
|
|
|
HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
|
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
|
|
if (file == INVALID_HANDLE_VALUE) return false;
|
|
|
|
if (file == INVALID_HANDLE_VALUE) return false;
|
|
|
|
char contents[64]{};
|
|
|
|
char contents[64]{};
|
|
|
|
DWORD count = 0;
|
|
|
|
DWORD count = 0;
|
|
|
|
bool valid = ReadFile(file, contents, sizeof(contents) - 1, &count, nullptr) &&
|
|
|
|
bool valid = ReadFile(file, contents, sizeof(contents) - 1, &count, nullptr) &&
|
|
|
|
count >= 5 && count < sizeof(contents);
|
|
|
|
count >= 20 && count < sizeof(contents);
|
|
|
|
CloseHandle(file);
|
|
|
|
CloseHandle(file);
|
|
|
|
|
|
|
|
const char prefix[] = "sandbox-python:";
|
|
|
|
|
|
|
|
valid = valid && count > sizeof(prefix) - 1 &&
|
|
|
|
|
|
|
|
memcmp(contents, prefix, sizeof(prefix) - 1) == 0;
|
|
|
|
bool sawDot = false;
|
|
|
|
bool sawDot = false;
|
|
|
|
for (DWORD index = 0; valid && index < count; ++index) {
|
|
|
|
for (DWORD index = sizeof(prefix) - 1; valid && index < count; ++index) {
|
|
|
|
if (contents[index] == '.') sawDot = true;
|
|
|
|
if (contents[index] == '.') sawDot = true;
|
|
|
|
else if (contents[index] < '0' || contents[index] > '9') valid = false;
|
|
|
|
else if (contents[index] < '0' || contents[index] > '9') valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -276,19 +384,63 @@ static DWORD RunContainedExe(
|
|
|
|
const std::wstring& arguments,
|
|
|
|
const std::wstring& arguments,
|
|
|
|
const wchar_t* environment,
|
|
|
|
const wchar_t* environment,
|
|
|
|
const std::wstring& cwd,
|
|
|
|
const std::wstring& cwd,
|
|
|
|
HANDLE job
|
|
|
|
HANDLE job,
|
|
|
|
|
|
|
|
std::string* capturedOutput = nullptr,
|
|
|
|
|
|
|
|
bool* outputTruncated = nullptr
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
|
|
|
|
constexpr size_t kMaxCapturedOutput = 64 * 1024;
|
|
|
|
|
|
|
|
const bool capture = capturedOutput != nullptr;
|
|
|
|
|
|
|
|
if (capturedOutput) capturedOutput->clear();
|
|
|
|
|
|
|
|
if (outputTruncated) *outputTruncated = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HANDLE pipeRead = nullptr;
|
|
|
|
|
|
|
|
HANDLE pipeWrite = nullptr;
|
|
|
|
|
|
|
|
HANDLE nullInput = nullptr;
|
|
|
|
|
|
|
|
if (capture) {
|
|
|
|
|
|
|
|
SECURITY_ATTRIBUTES pipeSecurity{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
|
|
|
|
|
|
|
|
if (!CreatePipe(&pipeRead, &pipeWrite, &pipeSecurity, 0) ||
|
|
|
|
|
|
|
|
!SetHandleInformation(pipeRead, HANDLE_FLAG_INHERIT, 0)) {
|
|
|
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
if (pipeWrite) CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
nullInput = CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
|
|
|
|
|
|
&pipeSecurity, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
|
|
|
|
|
|
if (nullInput == INVALID_HANDLE_VALUE) {
|
|
|
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
|
|
|
CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DWORD attributeCount = capture ? 2 : 1;
|
|
|
|
SIZE_T attributeBytes = 0;
|
|
|
|
SIZE_T attributeBytes = 0;
|
|
|
|
InitializeProcThreadAttributeList(nullptr, 1, 0, &attributeBytes);
|
|
|
|
InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &attributeBytes);
|
|
|
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return GetLastError();
|
|
|
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
if (pipeWrite) CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
if (nullInput) CloseHandle(nullInput);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto* attributes = static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
|
|
|
|
auto* attributes = static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
|
|
|
|
HeapAlloc(GetProcessHeap(), 0, attributeBytes)
|
|
|
|
HeapAlloc(GetProcessHeap(), 0, attributeBytes)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
if (!attributes) return ERROR_OUTOFMEMORY;
|
|
|
|
if (!attributes) {
|
|
|
|
if (!InitializeProcThreadAttributeList(attributes, 1, 0, &attributeBytes)) {
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
if (pipeWrite) CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
if (nullInput) CloseHandle(nullInput);
|
|
|
|
|
|
|
|
return ERROR_OUTOFMEMORY;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!InitializeProcThreadAttributeList(attributes, attributeCount, 0, &attributeBytes)) {
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
if (pipeWrite) CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
if (nullInput) CloseHandle(nullInput);
|
|
|
|
return error;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -308,13 +460,37 @@ static DWORD RunContainedExe(
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
DeleteProcThreadAttributeList(attributes);
|
|
|
|
DeleteProcThreadAttributeList(attributes);
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
if (pipeWrite) CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
if (nullInput) CloseHandle(nullInput);
|
|
|
|
return error;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (capture) {
|
|
|
|
|
|
|
|
HANDLE inheritedHandles[] = {nullInput, pipeWrite};
|
|
|
|
|
|
|
|
if (!UpdateProcThreadAttribute(
|
|
|
|
|
|
|
|
attributes, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
|
|
|
|
|
|
|
inheritedHandles, sizeof(inheritedHandles), nullptr, nullptr)) {
|
|
|
|
|
|
|
|
DWORD error = GetLastError();
|
|
|
|
|
|
|
|
DeleteProcThreadAttributeList(attributes);
|
|
|
|
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
|
|
|
|
CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
CloseHandle(nullInput);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
STARTUPINFOEXW startup{};
|
|
|
|
STARTUPINFOEXW startup{};
|
|
|
|
startup.StartupInfo.cb = sizeof(startup);
|
|
|
|
startup.StartupInfo.cb = sizeof(startup);
|
|
|
|
startup.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
|
|
|
startup.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
|
|
|
startup.StartupInfo.wShowWindow = SW_HIDE;
|
|
|
|
startup.StartupInfo.wShowWindow = SW_HIDE;
|
|
|
|
|
|
|
|
if (capture) {
|
|
|
|
|
|
|
|
startup.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
|
|
|
|
|
|
|
startup.StartupInfo.hStdInput = nullInput;
|
|
|
|
|
|
|
|
startup.StartupInfo.hStdOutput = pipeWrite;
|
|
|
|
|
|
|
|
startup.StartupInfo.hStdError = pipeWrite;
|
|
|
|
|
|
|
|
}
|
|
|
|
startup.lpAttributeList = attributes;
|
|
|
|
startup.lpAttributeList = attributes;
|
|
|
|
PROCESS_INFORMATION process{};
|
|
|
|
PROCESS_INFORMATION process{};
|
|
|
|
std::wstring commandLine = QuoteArg(application);
|
|
|
|
std::wstring commandLine = QuoteArg(application);
|
|
|
@@ -328,7 +504,7 @@ static DWORD RunContainedExe(
|
|
|
|
mutableLine.empty() ? nullptr : &mutableLine[0],
|
|
|
|
mutableLine.empty() ? nullptr : &mutableLine[0],
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
FALSE,
|
|
|
|
capture ? TRUE : FALSE,
|
|
|
|
flags,
|
|
|
|
flags,
|
|
|
|
const_cast<wchar_t*>(environment),
|
|
|
|
const_cast<wchar_t*>(environment),
|
|
|
|
cwd.c_str(),
|
|
|
|
cwd.c_str(),
|
|
|
@@ -338,25 +514,74 @@ static DWORD RunContainedExe(
|
|
|
|
DWORD error = created ? ERROR_SUCCESS : GetLastError();
|
|
|
|
DWORD error = created ? ERROR_SUCCESS : GetLastError();
|
|
|
|
DeleteProcThreadAttributeList(attributes);
|
|
|
|
DeleteProcThreadAttributeList(attributes);
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
HeapFree(GetProcessHeap(), 0, attributes);
|
|
|
|
if (!created) return error;
|
|
|
|
if (pipeWrite) CloseHandle(pipeWrite);
|
|
|
|
|
|
|
|
if (nullInput) CloseHandle(nullInput);
|
|
|
|
|
|
|
|
if (!created) {
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!AssignProcessToJobObject(job, process.hProcess)) {
|
|
|
|
if (!AssignProcessToJobObject(job, process.hProcess)) {
|
|
|
|
error = GetLastError();
|
|
|
|
error = GetLastError();
|
|
|
|
TerminateProcess(process.hProcess, error);
|
|
|
|
TerminateProcess(process.hProcess, error);
|
|
|
|
CloseHandle(process.hThread);
|
|
|
|
CloseHandle(process.hThread);
|
|
|
|
CloseHandle(process.hProcess);
|
|
|
|
CloseHandle(process.hProcess);
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
return error;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ResumeThread(process.hThread);
|
|
|
|
ResumeThread(process.hThread);
|
|
|
|
DWORD waitResult = WaitForSingleObject(process.hProcess, 30000);
|
|
|
|
|
|
|
|
DWORD exitCode = ERROR_TIMEOUT;
|
|
|
|
DWORD exitCode = ERROR_TIMEOUT;
|
|
|
|
if (waitResult == WAIT_OBJECT_0) {
|
|
|
|
if (capture) {
|
|
|
|
GetExitCodeProcess(process.hProcess, &exitCode);
|
|
|
|
const ULONGLONG deadline = GetTickCount64() + 30000;
|
|
|
|
|
|
|
|
bool processFinished = false;
|
|
|
|
|
|
|
|
bool pipeFinished = false;
|
|
|
|
|
|
|
|
while (!pipeFinished || !processFinished) {
|
|
|
|
|
|
|
|
DWORD available = 0;
|
|
|
|
|
|
|
|
if (!pipeFinished && PeekNamedPipe(pipeRead, nullptr, 0, nullptr, &available, nullptr)) {
|
|
|
|
|
|
|
|
while (available > 0) {
|
|
|
|
|
|
|
|
char buffer[4096];
|
|
|
|
|
|
|
|
DWORD bytesRead = 0;
|
|
|
|
|
|
|
|
const DWORD requested = (std::min)(available, static_cast<DWORD>(sizeof(buffer)));
|
|
|
|
|
|
|
|
if (!ReadFile(pipeRead, buffer, requested, &bytesRead, nullptr) || bytesRead == 0) {
|
|
|
|
|
|
|
|
pipeFinished = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t remaining = capturedOutput->size() < kMaxCapturedOutput
|
|
|
|
|
|
|
|
? kMaxCapturedOutput - capturedOutput->size() : 0;
|
|
|
|
|
|
|
|
const size_t retained = (std::min)(remaining, static_cast<size_t>(bytesRead));
|
|
|
|
|
|
|
|
capturedOutput->append(buffer, retained);
|
|
|
|
|
|
|
|
if (retained < bytesRead && outputTruncated) *outputTruncated = true;
|
|
|
|
|
|
|
|
available -= bytesRead;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (GetLastError() == ERROR_BROKEN_PIPE) {
|
|
|
|
|
|
|
|
pipeFinished = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!processFinished && WaitForSingleObject(process.hProcess, 0) == WAIT_OBJECT_0) {
|
|
|
|
|
|
|
|
GetExitCodeProcess(process.hProcess, &exitCode);
|
|
|
|
|
|
|
|
processFinished = true;
|
|
|
|
|
|
|
|
TerminateJobObject(job, ERROR_SUCCESS);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!processFinished && GetTickCount64() >= deadline) {
|
|
|
|
|
|
|
|
TerminateJobObject(job, ERROR_TIMEOUT);
|
|
|
|
|
|
|
|
exitCode = ERROR_TIMEOUT;
|
|
|
|
|
|
|
|
processFinished = WaitForSingleObject(process.hProcess, 5000) == WAIT_OBJECT_0;
|
|
|
|
|
|
|
|
pipeFinished = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pipeFinished || !processFinished) Sleep(20);
|
|
|
|
|
|
|
|
if (processFinished && pipeFinished) break;
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
TerminateJobObject(job, ERROR_TIMEOUT);
|
|
|
|
DWORD waitResult = WaitForSingleObject(process.hProcess, 30000);
|
|
|
|
|
|
|
|
if (waitResult == WAIT_OBJECT_0) {
|
|
|
|
|
|
|
|
GetExitCodeProcess(process.hProcess, &exitCode);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
TerminateJobObject(job, ERROR_TIMEOUT);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CloseHandle(process.hThread);
|
|
|
|
CloseHandle(process.hThread);
|
|
|
|
CloseHandle(process.hProcess);
|
|
|
|
CloseHandle(process.hProcess);
|
|
|
|
|
|
|
|
if (pipeRead) CloseHandle(pipeRead);
|
|
|
|
return exitCode;
|
|
|
|
return exitCode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -434,18 +659,25 @@ int wmain() {
|
|
|
|
std::wstring appDataPath(appContainerFolder);
|
|
|
|
std::wstring appDataPath(appContainerFolder);
|
|
|
|
std::wstring appTempPath = appDataPath + L"\\Temp";
|
|
|
|
std::wstring appTempPath = appDataPath + L"\\Temp";
|
|
|
|
std::wstring sandboxWorkspacePath = appDataPath + L"\\agent-workspace";
|
|
|
|
std::wstring sandboxWorkspacePath = appDataPath + L"\\agent-workspace";
|
|
|
|
std::wstring stagedInputPath = sandboxWorkspacePath + L"\\README.md";
|
|
|
|
std::wstring projectSnapshotPath = sandboxWorkspacePath + L"\\project";
|
|
|
|
std::wstring stagedCopyPath = sandboxWorkspacePath + L"\\staged-copy.md";
|
|
|
|
std::wstring stagedInputPath = projectSnapshotPath + L"\\README.md";
|
|
|
|
|
|
|
|
std::wstring stagedCopyPath = projectSnapshotPath + L"\\staged-copy.md";
|
|
|
|
std::wstring pythonSandboxPath = sandboxWorkspacePath + L"\\python";
|
|
|
|
std::wstring pythonSandboxPath = sandboxWorkspacePath + L"\\python";
|
|
|
|
std::wstring pythonProbePath = sandboxWorkspacePath + L"\\python_probe.py";
|
|
|
|
std::wstring pythonVersionPath = projectSnapshotPath + L"\\scripts\\execution-result.txt";
|
|
|
|
std::wstring pythonVersionPath = sandboxWorkspacePath + L"\\python-version.txt";
|
|
|
|
|
|
|
|
CreateDirectoryW(appTempPath.c_str(), nullptr);
|
|
|
|
CreateDirectoryW(appTempPath.c_str(), nullptr);
|
|
|
|
bool workspaceFolderReady = CreateDirectoryW(sandboxWorkspacePath.c_str(), nullptr) != FALSE ||
|
|
|
|
bool workspaceFolderReady = CreateDirectoryW(sandboxWorkspacePath.c_str(), nullptr) != FALSE ||
|
|
|
|
GetLastError() == ERROR_ALREADY_EXISTS;
|
|
|
|
GetLastError() == ERROR_ALREADY_EXISTS;
|
|
|
|
workspaceFolderReady = workspaceFolderReady &&
|
|
|
|
workspaceFolderReady = workspaceFolderReady &&
|
|
|
|
GrantContainerFolderAccess(sandboxWorkspacePath, appContainerSid);
|
|
|
|
GrantContainerFolderAccess(sandboxWorkspacePath, appContainerSid);
|
|
|
|
bool stagedInputCopied = workspaceFolderReady &&
|
|
|
|
const std::wstring hostSnapshotPath = ReadEnvironmentString(L"SOVEREIGNAI_STAGING_ROOT");
|
|
|
|
CopyFileW(L"README.md", stagedInputPath.c_str(), FALSE);
|
|
|
|
const std::wstring requestedEntry = ReadEnvironmentString(L"SOVEREIGNAI_ENTRY_SCRIPT");
|
|
|
|
|
|
|
|
ULONGLONG stagedBytes = 0;
|
|
|
|
|
|
|
|
DWORD stagedFiles = 0;
|
|
|
|
|
|
|
|
bool stagedInputCopied = workspaceFolderReady && !hostSnapshotPath.empty() &&
|
|
|
|
|
|
|
|
CopySnapshotTree(hostSnapshotPath, projectSnapshotPath, stagedBytes, stagedFiles);
|
|
|
|
|
|
|
|
std::wstring pythonEntryPath;
|
|
|
|
|
|
|
|
bool entryScriptReady = stagedInputCopied &&
|
|
|
|
|
|
|
|
ResolvePythonEntry(projectSnapshotPath, requestedEntry, pythonEntryPath);
|
|
|
|
std::wstring curlPath = sandboxWorkspacePath + L"\\curl.exe";
|
|
|
|
std::wstring curlPath = sandboxWorkspacePath + L"\\curl.exe";
|
|
|
|
bool curlCopied = workspaceFolderReady &&
|
|
|
|
bool curlCopied = workspaceFolderReady &&
|
|
|
|
CopyFileW(L"C:\\Windows\\System32\\curl.exe", curlPath.c_str(), FALSE);
|
|
|
|
CopyFileW(L"C:\\Windows\\System32\\curl.exe", curlPath.c_str(), FALSE);
|
|
|
@@ -459,12 +691,7 @@ int wmain() {
|
|
|
|
bool pythonRuntimeCopied = workspaceFolderReady && !pythonSourcePath.empty() &&
|
|
|
|
bool pythonRuntimeCopied = workspaceFolderReady && !pythonSourcePath.empty() &&
|
|
|
|
CopyPythonRuntime(pythonSourcePath, pythonSandboxPath,
|
|
|
|
CopyPythonRuntime(pythonSourcePath, pythonSandboxPath,
|
|
|
|
pythonRuntimeBytes, pythonRuntimeFiles);
|
|
|
|
pythonRuntimeBytes, pythonRuntimeFiles);
|
|
|
|
const char pythonSource[] =
|
|
|
|
bool pythonProbeReady = pythonRuntimeCopied && entryScriptReady;
|
|
|
|
"import pathlib, sys\n"
|
|
|
|
|
|
|
|
"pathlib.Path(__file__).with_name('python-version.txt').write_text(\n"
|
|
|
|
|
|
|
|
" '.'.join(map(str, sys.version_info[:3])), encoding='ascii')\n";
|
|
|
|
|
|
|
|
bool pythonProbeWritten = pythonRuntimeCopied &&
|
|
|
|
|
|
|
|
WriteFileBytes(pythonProbePath, pythonSource, sizeof(pythonSource) - 1);
|
|
|
|
|
|
|
|
SetEnvironmentValue(environment, L"PATH", L"C:\\Windows\\System32");
|
|
|
|
SetEnvironmentValue(environment, L"PATH", L"C:\\Windows\\System32");
|
|
|
|
SetEnvironmentValue(environment, L"APPDATA", appDataPath);
|
|
|
|
SetEnvironmentValue(environment, L"APPDATA", appDataPath);
|
|
|
|
SetEnvironmentValue(environment, L"LOCALAPPDATA", appDataPath);
|
|
|
|
SetEnvironmentValue(environment, L"LOCALAPPDATA", appDataPath);
|
|
|
@@ -479,6 +706,7 @@ int wmain() {
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONHOME", pythonSandboxPath);
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONHOME", pythonSandboxPath);
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONNOUSERSITE", L"1");
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONNOUSERSITE", L"1");
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONDONTWRITEBYTECODE", L"1");
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONDONTWRITEBYTECODE", L"1");
|
|
|
|
|
|
|
|
SetEnvironmentValue(environment, L"PYTHONUTF8", L"1");
|
|
|
|
std::wstring cwd = L"C:\\Windows\\System32";
|
|
|
|
std::wstring cwd = L"C:\\Windows\\System32";
|
|
|
|
DWORD shellResult = jobReady
|
|
|
|
DWORD shellResult = jobReady
|
|
|
|
? RunContained(appContainerSid, L"exit 0", environment.data(), cwd, job)
|
|
|
|
? RunContained(appContainerSid, L"exit 0", environment.data(), cwd, job)
|
|
|
@@ -505,9 +733,19 @@ int wmain() {
|
|
|
|
stagedFileUrl + L" -o " + stagedCopyPath, environment.data(), cwd, job)
|
|
|
|
stagedFileUrl + L" -o " + stagedCopyPath, environment.data(), cwd, job)
|
|
|
|
: ERROR_INVALID_HANDLE;
|
|
|
|
: ERROR_INVALID_HANDLE;
|
|
|
|
std::wstring pythonExecutable = pythonSandboxPath + L"\\python.exe";
|
|
|
|
std::wstring pythonExecutable = pythonSandboxPath + L"\\python.exe";
|
|
|
|
DWORD pythonRunResult = jobReady && pythonProbeWritten
|
|
|
|
std::string pythonOutput;
|
|
|
|
|
|
|
|
bool pythonOutputTruncated = false;
|
|
|
|
|
|
|
|
DWORD pythonRunResult = jobReady && pythonProbeReady
|
|
|
|
? RunContainedExe(appContainerSid, pythonExecutable,
|
|
|
|
? RunContainedExe(appContainerSid, pythonExecutable,
|
|
|
|
L"-s " + QuoteArg(pythonProbePath), environment.data(), cwd, job)
|
|
|
|
L"-s " + QuoteArg(pythonEntryPath), environment.data(), cwd, job,
|
|
|
|
|
|
|
|
&pythonOutput, &pythonOutputTruncated)
|
|
|
|
|
|
|
|
: ERROR_INVALID_HANDLE;
|
|
|
|
|
|
|
|
std::string overflowOutput;
|
|
|
|
|
|
|
|
bool overflowTruncated = false;
|
|
|
|
|
|
|
|
DWORD overflowRunResult = jobReady && pythonRuntimeCopied
|
|
|
|
|
|
|
|
? RunContainedExe(appContainerSid, pythonExecutable,
|
|
|
|
|
|
|
|
L"-s -c " + QuoteArg(L"print('x' * 70000)"), environment.data(), cwd,
|
|
|
|
|
|
|
|
job, &overflowOutput, &overflowTruncated)
|
|
|
|
: ERROR_INVALID_HANDLE;
|
|
|
|
: ERROR_INVALID_HANDLE;
|
|
|
|
DWORD curlVersionResult = jobReady && curlCopied
|
|
|
|
DWORD curlVersionResult = jobReady && curlCopied
|
|
|
|
? RunContained(appContainerSid, QuoteArg(curlPath) + L" --version",
|
|
|
|
? RunContained(appContainerSid, QuoteArg(curlPath) + L" --version",
|
|
|
@@ -543,7 +781,7 @@ int wmain() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bool stagedInputRoundTripMatches = stagedInputCopied && FilesMatch(
|
|
|
|
bool stagedInputRoundTripMatches = stagedInputCopied && FilesMatch(
|
|
|
|
L"README.md", stagedCopyPath);
|
|
|
|
L"README.md", stagedCopyPath);
|
|
|
|
bool pythonVersionVisible = IsVersionText(pythonVersionPath);
|
|
|
|
bool pythonVersionVisible = IsSmokeResult(pythonVersionPath);
|
|
|
|
|
|
|
|
|
|
|
|
if (job) CloseHandle(job);
|
|
|
|
if (job) CloseHandle(job);
|
|
|
|
if (appContainerFolder) CoTaskMemFree(appContainerFolder);
|
|
|
|
if (appContainerFolder) CoTaskMemFree(appContainerFolder);
|
|
|
@@ -551,7 +789,6 @@ int wmain() {
|
|
|
|
DeleteFileW(curlPath.c_str());
|
|
|
|
DeleteFileW(curlPath.c_str());
|
|
|
|
DeleteFileW(stagedInputPath.c_str());
|
|
|
|
DeleteFileW(stagedInputPath.c_str());
|
|
|
|
DeleteFileW(stagedCopyPath.c_str());
|
|
|
|
DeleteFileW(stagedCopyPath.c_str());
|
|
|
|
DeleteFileW(pythonProbePath.c_str());
|
|
|
|
|
|
|
|
DeleteFileW(pythonVersionPath.c_str());
|
|
|
|
DeleteFileW(pythonVersionPath.c_str());
|
|
|
|
DeleteAppContainerProfile(profileName.c_str());
|
|
|
|
DeleteAppContainerProfile(profileName.c_str());
|
|
|
|
DeleteFileW(secretPath.c_str());
|
|
|
|
DeleteFileW(secretPath.c_str());
|
|
|
@@ -566,20 +803,39 @@ int wmain() {
|
|
|
|
writeWasBlocked ? L"true" : L"false");
|
|
|
|
writeWasBlocked ? L"true" : L"false");
|
|
|
|
wprintf(L"profile_write_exit=%lu\nprofile_write_visible=%s\n",
|
|
|
|
wprintf(L"profile_write_exit=%lu\nprofile_write_visible=%s\n",
|
|
|
|
allowedWriteResult, allowedWorkspaceWriteVisible ? L"true" : L"false");
|
|
|
|
allowedWriteResult, allowedWorkspaceWriteVisible ? L"true" : L"false");
|
|
|
|
wprintf(L"staged_copy_exit=%lu\nstaged_input_copied=%s\nstaged_roundtrip_matches=%s\n",
|
|
|
|
wprintf(L"staged_copy_exit=%lu\nstaged_input_copied=%s\nstaged_files=%lu\nstaged_bytes=%llu\nstaged_roundtrip_matches=%s\n",
|
|
|
|
stagedReadResult, stagedInputCopied ? L"true" : L"false",
|
|
|
|
stagedReadResult, stagedInputCopied ? L"true" : L"false",
|
|
|
|
|
|
|
|
stagedFiles, stagedBytes,
|
|
|
|
stagedInputRoundTripMatches ? L"true" : L"false");
|
|
|
|
stagedInputRoundTripMatches ? L"true" : L"false");
|
|
|
|
wprintf(L"python_runtime_copied=%s\npython_runtime_bytes=%llu\npython_runtime_files=%lu\npython_run_exit=%lu\npython_version_written=%s\n",
|
|
|
|
wprintf(L"python_runtime_copied=%s\npython_runtime_bytes=%llu\npython_runtime_files=%lu\nentry_script_valid=%s\npython_run_exit=%lu\npython_result_written=%s\n",
|
|
|
|
pythonRuntimeCopied ? L"true" : L"false", pythonRuntimeBytes,
|
|
|
|
pythonRuntimeCopied ? L"true" : L"false", pythonRuntimeBytes,
|
|
|
|
pythonRuntimeFiles, pythonRunResult, pythonVersionVisible ? L"true" : L"false");
|
|
|
|
pythonRuntimeFiles, entryScriptReady ? L"true" : L"false",
|
|
|
|
|
|
|
|
pythonRunResult, pythonVersionVisible ? L"true" : L"false");
|
|
|
|
|
|
|
|
std::vector<wchar_t> pythonOutputWide(pythonOutput.size() + 1);
|
|
|
|
|
|
|
|
if (!pythonOutput.empty()) {
|
|
|
|
|
|
|
|
int wideCount = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
|
|
|
|
|
|
|
pythonOutput.data(), static_cast<int>(pythonOutput.size()),
|
|
|
|
|
|
|
|
pythonOutputWide.data(), static_cast<int>(pythonOutputWide.size()));
|
|
|
|
|
|
|
|
if (wideCount > 0) pythonOutputWide[wideCount] = L'\0';
|
|
|
|
|
|
|
|
else pythonOutputWide[0] = L'\0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
wprintf(L"python_output_bytes=%llu\npython_output_truncated=%s\npython_output=%ls\n",
|
|
|
|
|
|
|
|
static_cast<unsigned long long>(pythonOutput.size()),
|
|
|
|
|
|
|
|
pythonOutputTruncated ? L"true" : L"false",
|
|
|
|
|
|
|
|
pythonOutputWide.data());
|
|
|
|
|
|
|
|
wprintf(L"overflow_run_exit=%lu\noverflow_output_bytes=%llu\noverflow_truncated=%s\n",
|
|
|
|
|
|
|
|
overflowRunResult, static_cast<unsigned long long>(overflowOutput.size()),
|
|
|
|
|
|
|
|
overflowTruncated ? L"true" : L"false");
|
|
|
|
wprintf(L"curl_version_exit=%lu\ncurl_local_health_exit=%lu\n",
|
|
|
|
wprintf(L"curl_version_exit=%lu\ncurl_local_health_exit=%lu\n",
|
|
|
|
curlVersionResult, curlNetworkResult);
|
|
|
|
curlVersionResult, curlNetworkResult);
|
|
|
|
if (!jobReady || shellResult != 0 || readResult == 0 || writeResult == 0 ||
|
|
|
|
if (!jobReady || shellResult != 0 || readResult == 0 || writeResult == 0 ||
|
|
|
|
!hostSecretPreserved || !writeWasBlocked || !workspaceFolderReady ||
|
|
|
|
!hostSecretPreserved || !writeWasBlocked || !workspaceFolderReady ||
|
|
|
|
allowedWriteResult != 0 || !allowedWorkspaceWriteVisible ||
|
|
|
|
allowedWriteResult != 0 || !allowedWorkspaceWriteVisible ||
|
|
|
|
!stagedInputCopied || stagedReadResult != 0 || !stagedInputRoundTripMatches ||
|
|
|
|
!stagedInputCopied || stagedReadResult != 0 || !stagedInputRoundTripMatches ||
|
|
|
|
!pythonRuntimeCopied || !pythonProbeWritten || pythonRunResult != 0 ||
|
|
|
|
!pythonRuntimeCopied || !pythonProbeReady || pythonRunResult != 0 ||
|
|
|
|
!pythonVersionVisible ||
|
|
|
|
!pythonVersionVisible ||
|
|
|
|
|
|
|
|
overflowRunResult != 0 || overflowOutput.size() != 64 * 1024 ||
|
|
|
|
|
|
|
|
!overflowTruncated ||
|
|
|
|
!curlCopied || curlVersionResult != 0 || curlNetworkResult == 0) return 20;
|
|
|
|
!curlCopied || curlVersionResult != 0 || curlNetworkResult == 0) return 20;
|
|
|
|
return 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|