Measure AppContainer disk quota overshoot

This commit is contained in:
Hamza Ayed
2026-10-03 13:32:26 +03:00
parent 5652db5dbc
commit 6834f218c8
4 changed files with 126 additions and 5 deletions
@@ -0,0 +1,14 @@
"""Fills only the disposable AppContainer profile to verify the broker quota."""
import os
from pathlib import Path
target = Path(__file__).with_name("disk-fill.bin")
chunk = b"x" * (64 * 1024)
with target.open("wb") as output:
while True:
output.write(chunk)
output.flush()
# Force the disposable test writes through the file cache so the
# broker's disk monitor observes durable growth, not buffered writes.
os.fsync(output.fileno())
@@ -4,6 +4,7 @@
#include <userenv.h>
#include <sddl.h>
#include <aclapi.h>
#include <jobapi2.h>
#include <stdio.h>
#include <string.h>
#include <string>
@@ -378,6 +379,51 @@ static bool IsSmokeResult(const std::wstring& path) {
return valid && sawDot;
}
static constexpr ULONGLONG kMaxAppContainerDataBytes = 128ull * 1024 * 1024;
static bool MeasureDirectoryBytes(
const std::wstring& root, ULONGLONG& totalBytes, unsigned depth = 0
) {
if (depth > 48) return false;
const DWORD rootAttributes = GetFileAttributesW(root.c_str());
if (rootAttributes == INVALID_FILE_ATTRIBUTES ||
!(rootAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
(rootAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) return false;
WIN32_FIND_DATAW entry{};
HANDLE search = FindFirstFileW((root + 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) {
success = false;
break;
}
const std::wstring path = root + L"\\" + entry.cFileName;
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
success = MeasureDirectoryBytes(path, totalBytes, depth + 1);
} else {
WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (!GetFileAttributesExW(path.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 (size > (~static_cast<ULONGLONG>(0)) - totalBytes) {
success = false;
break;
}
totalBytes += size;
}
if (!success) break;
} while (FindNextFileW(search, &entry));
const DWORD finalError = GetLastError();
FindClose(search);
return success && finalError == ERROR_NO_MORE_FILES;
}
static DWORD RunContainedExe(
PSID appContainerSid,
const std::wstring& application,
@@ -386,12 +432,26 @@ static DWORD RunContainedExe(
const std::wstring& cwd,
HANDLE job,
std::string* capturedOutput = nullptr,
bool* outputTruncated = nullptr
bool* outputTruncated = nullptr,
const std::wstring* quotaRoot = nullptr,
bool* quotaExceeded = nullptr,
ULONGLONG* quotaObservedBytes = nullptr
) {
constexpr size_t kMaxCapturedOutput = 64 * 1024;
const bool capture = capturedOutput != nullptr;
if (capturedOutput) capturedOutput->clear();
if (outputTruncated) *outputTruncated = false;
if (quotaExceeded) *quotaExceeded = false;
if (quotaObservedBytes) *quotaObservedBytes = 0;
if (quotaRoot) {
ULONGLONG currentBytes = 0;
const bool measured = MeasureDirectoryBytes(*quotaRoot, currentBytes);
if (quotaObservedBytes) *quotaObservedBytes = currentBytes;
if (!measured || currentBytes > kMaxAppContainerDataBytes) {
if (quotaExceeded) *quotaExceeded = true;
return ERROR_DISK_FULL;
}
}
HANDLE pipeRead = nullptr;
HANDLE pipeWrite = nullptr;
@@ -533,6 +593,7 @@ static DWORD RunContainedExe(
DWORD exitCode = ERROR_TIMEOUT;
if (capture) {
const ULONGLONG deadline = GetTickCount64() + 30000;
ULONGLONG nextQuotaCheck = GetTickCount64() + 25;
bool processFinished = false;
bool pipeFinished = false;
while (!pipeFinished || !processFinished) {
@@ -562,6 +623,19 @@ static DWORD RunContainedExe(
processFinished = true;
TerminateJobObject(job, ERROR_SUCCESS);
}
if (!processFinished && quotaRoot && GetTickCount64() >= nextQuotaCheck) {
ULONGLONG currentBytes = 0;
const bool measured = MeasureDirectoryBytes(*quotaRoot, currentBytes);
if (quotaObservedBytes) *quotaObservedBytes = currentBytes;
if (!measured || currentBytes > kMaxAppContainerDataBytes) {
if (quotaExceeded) *quotaExceeded = true;
TerminateJobObject(job, ERROR_DISK_FULL);
exitCode = ERROR_DISK_FULL;
processFinished = WaitForSingleObject(process.hProcess, 5000) == WAIT_OBJECT_0;
pipeFinished = false;
}
nextQuotaCheck = GetTickCount64() + 25;
}
if (!processFinished && GetTickCount64() >= deadline) {
TerminateJobObject(job, ERROR_TIMEOUT);
exitCode = ERROR_TIMEOUT;
@@ -653,6 +727,13 @@ int wmain() {
limits.JobMemoryLimit = 512ull * 1024 * 1024;
bool jobReady = job && SetInformationJobObject(
job, JobObjectExtendedLimitInformation, &limits, sizeof(limits));
JOBOBJECT_IO_RATE_CONTROL_INFORMATION ioLimits{};
ioLimits.MaxBandwidth = 8ll * 1024 * 1024;
ioLimits.ControlFlags = JOB_OBJECT_IO_RATE_CONTROL_ENABLE;
DWORD jobIoControlResult = jobReady
? SetIoRateControlInformationJobObject(job, &ioLimits)
: ERROR_INVALID_HANDLE;
DWORD jobIoControlError = jobIoControlResult == 0 ? GetLastError() : ERROR_SUCCESS;
auto environment = SafeEnvironmentBlock();
if (environment.empty()) return 15;
@@ -756,6 +837,19 @@ int wmain() {
L" --noproxy \"*\" --max-time 4 http://127.0.0.1:8100/health -o NUL",
environment.data(), cwd, job)
: ERROR_INVALID_HANDLE;
std::wstring diskFillScriptPath;
bool diskFillScriptReady = stagedInputCopied && ResolvePythonEntry(
projectSnapshotPath, L"scripts/appcontainer_disk_fill_smoke.py", diskFillScriptPath);
std::string diskFillOutput;
bool diskFillOutputTruncated = false;
bool diskQuotaExceeded = false;
ULONGLONG diskQuotaObservedBytes = 0;
DWORD diskFillRunResult = jobReady && pythonRuntimeCopied && diskFillScriptReady
? RunContainedExe(appContainerSid, pythonExecutable,
L"-s " + QuoteArg(diskFillScriptPath), environment.data(), cwd, job,
&diskFillOutput, &diskFillOutputTruncated, &appDataPath,
&diskQuotaExceeded, &diskQuotaObservedBytes)
: ERROR_INVALID_HANDLE;
DWORD attrs = GetFileAttributesW(writePath.c_str());
DWORD attrsError = attrs == INVALID_FILE_ATTRIBUTES ? GetLastError() : ERROR_SUCCESS;
@@ -828,6 +922,12 @@ int wmain() {
overflowTruncated ? L"true" : L"false");
wprintf(L"curl_version_exit=%lu\ncurl_local_health_exit=%lu\n",
curlVersionResult, curlNetworkResult);
wprintf(L"disk_fill_script_valid=%s\ndisk_fill_exit=%lu\ndisk_quota_limit_bytes=%llu\ndisk_quota_observed_bytes=%llu\ndisk_quota_exceeded=%s\n",
diskFillScriptReady ? L"true" : L"false", diskFillRunResult,
kMaxAppContainerDataBytes, diskQuotaObservedBytes,
diskQuotaExceeded ? L"true" : L"false");
wprintf(L"job_io_rate_control_set=%s\njob_io_rate_control_error=%lu\n",
jobIoControlResult != 0 ? L"true" : L"false", jobIoControlError);
if (!jobReady || shellResult != 0 || readResult == 0 || writeResult == 0 ||
!hostSecretPreserved || !writeWasBlocked || !workspaceFolderReady ||
allowedWriteResult != 0 || !allowedWorkspaceWriteVisible ||
@@ -836,6 +936,8 @@ int wmain() {
!pythonVersionVisible ||
overflowRunResult != 0 || overflowOutput.size() != 64 * 1024 ||
!overflowTruncated ||
!curlCopied || curlVersionResult != 0 || curlNetworkResult == 0) return 20;
!curlCopied || curlVersionResult != 0 || curlNetworkResult == 0 ||
!diskFillScriptReady || diskFillRunResult != ERROR_DISK_FULL ||
!diskQuotaExceeded || diskQuotaObservedBytes <= kMaxAppContainerDataBytes) return 20;
return 0;
}
@@ -20,7 +20,11 @@ def main() -> int:
repository = REPOSITORY
os.environ["SOVEREIGNAI_ALLOWED_WORKSPACES"] = str(repository)
os.environ["SOVEREIGNAI_WORKSPACE"] = str(repository)
files = ["README.md", "scripts/appcontainer_user_code_smoke.py"]
files = [
"README.md",
"scripts/appcontainer_user_code_smoke.py",
"scripts/appcontainer_disk_fill_smoke.py",
]
with stage_selected_files(repository, files) as snapshot:
environment = os.environ.copy()
environment["SOVEREIGNAI_STAGING_ROOT"] = str(snapshot.root)