initial commit

This commit is contained in:
2026-09-21 13:41:40 +09:00
commit 855c7328df
411 changed files with 85352 additions and 0 deletions
+205
View File
@@ -0,0 +1,205 @@
// Native-Windows hostile worker fixture. It must pass the production bootstrap.
#include <QtGlobal>
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <windows.h>
#include <userenv.h>
#include <io.h>
#include "common/ipc.h"
#include "common/worker_runtime.h"
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
#include <QtEndian>
#include <cstdlib>
using namespace docview;
namespace {
std::wstring native(const QString &path) { return QDir::toNativeSeparators(path).toStdWString(); }
QCborMap response(const QCborMap &request, const QCborMap &result) {
auto reply = request; reply.remove(QStringLiteral("payload")); reply.insert(QStringLiteral("result"), result); return reply;
}
bool raw(WorkerRuntime &runtime, const QCborMap &reply) {
const auto bytes = QCborValue(reply).toCbor(); char header[4]; qToBigEndian<quint32>(quint32(bytes.size()), header);
return runtime.transport->write(header, 4) == 4 && runtime.transport->write(bytes) == bytes.size() && runtime.flushWrites();
}
bool fixturePaths(const QJsonObject &fixture) {
const QString root = QDir::fromNativeSeparators(fixture.value("fixtureRoot").toString());
if (!QDir::isAbsolutePath(root) || !QFileInfo(root).fileName().startsWith("docview-win-probe-") ||
QDir::cleanPath(root) != root || root.startsWith("//") || root.contains(QChar::Null)) return false;
for (const auto &key : {"sourcePath", "siblingPath", "configPath", "outputPath"}) {
const auto path = QDir::fromNativeSeparators(fixture.value(key).toString());
if (path.contains(QChar::Null) || QDir::cleanPath(path) != path || !path.startsWith(root + '/', Qt::CaseInsensitive)) return false;
}
return true;
}
bool deniedOpen(const QString &path, DWORD access, DWORD disposition, DWORD flags, DWORD *failure) {
const auto wide = native(path);
HANDLE handle = CreateFileW(wide.c_str(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, disposition, flags | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
if (handle != INVALID_HANDLE_VALUE) { CloseHandle(handle); *failure = 0; return false; }
*failure = GetLastError();
return *failure == ERROR_ACCESS_DENIED || *failure == ERROR_PRIVILEGE_NOT_HELD;
}
QCborMap probeOs(WorkerRuntime &runtime, const QJsonObject &fixture, const QString &packageSid) {
QCborMap result;
if (!fixturePaths(fixture)) return {{"fixture_valid", false}};
result.insert(QStringLiteral("fixture_valid"), true);
const auto checkOpen = [&](const char *label, const QString &path, DWORD access, DWORD disposition, DWORD flags = 0) {
DWORD error = 0; const bool denied = deniedOpen(path, access, disposition, flags, &error);
result.insert(QString::fromLatin1(label), denied); result.insert(QString::fromLatin1(label) + "_error", qint64(error));
};
DWORD written = 0;
const bool nativeWrite = WriteFile(reinterpret_cast<HANDLE>(_get_osfhandle(runtime.source.handle())), "X", 1, &written, nullptr);
const DWORD writeError = nativeWrite ? 0 : GetLastError();
result.insert(QStringLiteral("source_handle_write_denied"), !nativeWrite && writeError == ERROR_ACCESS_DENIED);
result.insert(QStringLiteral("source_handle_write_error"), qint64(writeError));
checkOpen("source_reopen_write_denied", fixture.value("sourcePath").toString(), GENERIC_WRITE, OPEN_EXISTING);
if (result.value("source_reopen_write_denied_error").toInteger() == ERROR_SHARING_VIOLATION)
result.insert(QStringLiteral("source_reopen_write_denied"), true); // broker's pinned read-only sharing also enforces this boundary
checkOpen("parent_read_denied", fixture.value("fixtureRoot").toString(), FILE_LIST_DIRECTORY, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS);
checkOpen("sibling_read_denied", fixture.value("siblingPath").toString(), GENERIC_READ, OPEN_EXISTING);
checkOpen("config_read_denied", fixture.value("configPath").toString(), GENERIC_READ, OPEN_EXISTING);
checkOpen("arbitrary_output_denied", fixture.value("outputPath").toString(), GENERIC_WRITE, CREATE_NEW);
bool extraAbsent = true;
const auto sentinels = fixture.value("sentinelHandles").toArray();
if (sentinels.size() != 2) extraAbsent = false;
for (const auto &value : sentinels) {
const auto item = value.toObject(); bool ok = false;
const auto number = item.value("value").toString().toULongLong(&ok);
BY_HANDLE_FILE_INFORMATION info{};
if (!ok || !number) { extraAbsent = false; continue; }
const HANDLE handle = reinterpret_cast<HANDLE>(static_cast<quintptr>(number));
// Handle numbers can be reused inside the child. Compare file identities,
// not merely numeric validity, and never read the sentinel's content.
if (GetFileType(handle) == FILE_TYPE_DISK && GetFileInformationByHandle(handle, &info) &&
info.dwVolumeSerialNumber == quint64(item.value("volume").toInteger()) &&
info.nFileIndexHigh == quint64(item.value("high").toInteger()) && info.nFileIndexLow == quint64(item.value("low").toInteger())) extraAbsent = false;
}
result.insert(QStringLiteral("extra_file_handles_absent"), extraAbsent);
int networkError = 0; bool connected = false;
WSADATA wsa{};
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) networkError = WSAGetLastError();
else {
SOCKET socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket == INVALID_SOCKET) networkError = WSAGetLastError();
else {
u_long nonblocking = 1; ioctlsocket(socket, FIONBIO, &nonblocking);
sockaddr_in address{}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
const int port = fixture.value("loopbackPort").toInt();
if (port < 1 || port > 65535) { closesocket(socket); WSACleanup(); return {{"fixture_valid", false}}; }
address.sin_port = htons(static_cast<u_short>(port));
connected = ::connect(socket, reinterpret_cast<sockaddr *>(&address), sizeof(address)) == 0;
networkError = connected ? 0 : WSAGetLastError();
if (networkError == WSAEWOULDBLOCK) {
fd_set writes, errors; FD_ZERO(&writes); FD_ZERO(&errors); FD_SET(socket, &writes); FD_SET(socket, &errors);
timeval timeout{2, 0};
if (select(0, nullptr, &writes, &errors, &timeout) > 0) {
int size = sizeof(networkError);
if (getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&networkError), &size) == 0) connected = networkError == 0;
}
}
closesocket(socket);
}
WSACleanup();
}
result.insert(QStringLiteral("loopback_denied"), !connected && networkError == WSAEACCES);
result.insert(QStringLiteral("loopback_error"), networkError);
auto image = native(QCoreApplication::applicationFilePath());
auto command = std::wstring(L"\"") + image + L"\" --child-marker";
STARTUPINFOW startup{}; startup.cb = sizeof(startup); PROCESS_INFORMATION child{};
const bool spawned = CreateProcessW(image.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &child);
const DWORD childError = spawned ? 0 : GetLastError();
if (spawned) { TerminateProcess(child.hProcess, 93); WaitForSingleObject(child.hProcess, 5000); CloseHandle(child.hThread); CloseHandle(child.hProcess); }
result.insert(QStringLiteral("child_creation_denied"), !spawned && (childError == ERROR_ACCESS_DENIED || childError == ERROR_CHILD_PROCESS_BLOCKED || childError == ERROR_NOT_ENOUGH_QUOTA));
result.insert(QStringLiteral("child_creation_error"), qint64(childError));
PWSTR folder = nullptr;
const auto sid = packageSid.toStdWString();
if (SUCCEEDED(GetAppContainerFolderPath(sid.c_str(), &folder)) && folder) {
// This is the unique profile created for this protected test worker.
checkOpen("own_profile_write_denied", QString::fromWCharArray(folder) + "/docview-probe-write.tmp", GENERIC_WRITE, CREATE_NEW);
CoTaskMemFree(folder);
} else result.insert(QStringLiteral("own_profile_write_denied"), false);
HKEY registry = nullptr;
const auto registryStatus = GetAppContainerRegistryLocation(KEY_SET_VALUE | KEY_CREATE_SUB_KEY, &registry);
bool registryDenied = registryStatus == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
if (registryStatus == S_OK) {
const DWORD marker = 1;
const auto status = RegSetValueExW(registry, L"DocViewProbe", 0, REG_DWORD, reinterpret_cast<const BYTE *>(&marker), sizeof(marker));
registryDenied = status == ERROR_ACCESS_DENIED;
if (status == ERROR_SUCCESS) RegDeleteValueW(registry, L"DocViewProbe");
RegCloseKey(registry);
}
result.insert(QStringLiteral("own_profile_registry_write_denied"), registryDenied);
return result;
}
}
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
if (app.arguments().contains("--child-marker")) return 92;
QCommandLineParser parser; addWorkerOptions(parser); parser.process(app);
QString error; auto runtime = startWorkerRuntime(parser, {}, &error);
if (!runtime) return 5;
if (runtime->source.size() < 2 || runtime->source.size() > 65536) return 120;
const auto fixture = QJsonDocument::fromJson(runtime->source.readAll()).object();
const auto behavior = fixture.value("mode").toString();
if (behavior.isEmpty() || behavior.size() > 64) return 120;
IpcChannel channel(runtime->transport.get());
QObject::connect(&channel, &IpcChannel::protocolError, &app, [] { std::_Exit(123); });
int received = 0; bool streaming = false;
QObject::connect(&channel, &IpcChannel::messageReceived, &app, [&](const QCborMap &request) {
++received;
// Broker readiness always verifies the production protected bootstrap,
// even when a later request deliberately crashes or corrupts protocol.
if (received == 1) {
if (request.value("operation").toString() != "ping" || !channel.send(response(request, {{"ready", true}})) || !runtime->flushWrites()) std::_Exit(124);
return;
}
if (behavior == "crash") std::_Exit(73);
if (behavior == "sandbox-unavailable") std::_Exit(5);
if (behavior == "close-without-reply") std::_Exit(0);
if (behavior == "close-reply-exit" && request.value("operation").toString() == "close") {
if (!raw(*runtime, response(request, {{"closed", true}}))) std::_Exit(125);
std::_Exit(0);
}
if (behavior == "oversize") {
char header[4]; qToBigEndian<quint32>(MaxControlFrame + 1, header);
if (runtime->transport->write(header, 4) != 4 || !runtime->flushWrites()) std::_Exit(124);
return;
}
QCborMap result{{"received", received}, {"sandboxed", true}, {"echo", request.value("payload")}, {"receivedDuringStream", streaming}};
auto reply = response(request, result);
if (behavior == "probe-os") reply = response(request, probeOs(*runtime, fixture, parser.value("sandbox-sid")));
else if (behavior == "wrong-session") reply.insert(QStringLiteral("sessionId"), "foreign");
else if (behavior == "wrong-generation") reply.insert(QStringLiteral("generation"), request.value("generation").toInteger() + 1);
else if (behavior == "wrong-request") reply.insert(QStringLiteral("requestId"), request.value("requestId").toInteger() + 1);
else if (behavior == "wrong-operation") reply.insert(QStringLiteral("operation"), "metadata");
else if (behavior == "wrong-version") reply.insert(QStringLiteral("protocolVersion"), 2);
else if (behavior == "illegal-more") reply.insert(QStringLiteral("result"), QCborMap{{"more", true}});
else if (behavior == "error") { reply.remove(QStringLiteral("result")); reply.insert(QStringLiteral("error"), QCborMap{{"code", "E_FIXTURE"}, {"message", "fixture error"}}); }
else if (behavior == "stream" && request.value("operation").toString() == "extract") {
streaming = true;
if (!raw(*runtime, response(request, {{"more", true}, {"data", QByteArray("alpha")}}))) std::_Exit(125);
QTimer::singleShot(30, &app, [&, request] { if (!raw(*runtime, response(request, {{"more", true}, {"data", QByteArray("beta")}}))) std::_Exit(125); });
QTimer::singleShot(60, &app, [&, request] {
streaming = false;
if (!raw(*runtime, response(request, {{"more", false}, {"size", 9}}))) std::_Exit(125);
});
return;
}
if (!raw(*runtime, reply)) std::_Exit(125);
});
return app.exec();
}