Files
ycode/native/sandbox/probe.cpp
T

248 lines
16 KiB
C++

// Test-only executable. Never distributed as the sandbox runner.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <winsock2.h>
#include <windows.h>
#include <userenv.h>
#include <sddl.h>
#include <cstdio>
#include <string>
#include <vector>
static bool registryId(const std::wstring& value) {
if (value.size() != 36 || value[8] != L'-' || value[13] != L'-' || value[18] != L'-' || value[23] != L'-') return false;
for (size_t index = 0; index < value.size(); ++index) {
if (index == 8 || index == 13 || index == 18 || index == 23) continue;
if ((value[index] < L'0' || value[index] > L'9') && (value[index] < L'a' || value[index] > L'f')) return false;
}
return true;
}
static std::wstring pipeName(const std::wstring& id) { return L"\\\\.\\pipe\\YCodeSandboxTest." + id; }
static int pipeServer(const std::wstring& id, const std::wstring& profile) {
if (!registryId(id) || profile.size() != 42 || profile.substr(0, 10) != L"YCode.Run." || profile.substr(10).find_first_not_of(L"0123456789abcdef") != std::wstring::npos) return 2;
PSID expected = nullptr;
if (FAILED(DeriveAppContainerSidFromAppContainerName(profile.c_str(), &expected))) return 29;
HANDLE token = nullptr; DWORD bytes = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { FreeSid(expected); return 29; }
GetTokenInformation(token, TokenUser, nullptr, 0, &bytes); std::vector<BYTE> user(bytes);
const bool gotUser = GetTokenInformation(token, TokenUser, user.data(), bytes, &bytes) != FALSE; CloseHandle(token);
LPWSTR owner = nullptr, package = nullptr;
if (!gotUser || !ConvertSidToStringSidW(reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid, &owner) || !ConvertSidToStringSidW(expected, &package)) { if (owner) LocalFree(owner); FreeSid(expected); return 29; }
const std::wstring sddl = L"D:P(A;;GA;;;SY)(A;;GA;;;" + std::wstring(owner) + L")(A;;0x00120003;;;" + std::wstring(package) + L")S:(ML;;NW;;;LW)";
std::printf("PIPE_EXPECTED_SID %ls\n", package); std::fflush(stdout);
LocalFree(owner); LocalFree(package);
PSECURITY_DESCRIPTOR descriptor = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &descriptor, nullptr)) { FreeSid(expected); return 29; }
SECURITY_ATTRIBUTES security = {sizeof(security), descriptor, FALSE};
HANDLE pipe = CreateNamedPipeW(pipeName(id).c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 5000, &security);
LocalFree(descriptor);
if (pipe == INVALID_HANDLE_VALUE) { FreeSid(expected); return 29; }
const bool connected = ConnectNamedPipe(pipe, nullptr) || GetLastError() == ERROR_PIPE_CONNECTED;
ULONG pid = 0; bool authenticated = false;
if (connected && GetNamedPipeClientProcessId(pipe, &pid)) {
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), clientToken = nullptr;
if (process && OpenProcessToken(process, TOKEN_QUERY, &clientToken)) {
DWORD container = 0, needed = 0;
if (GetTokenInformation(clientToken, TokenIsAppContainer, &container, sizeof(container), &needed) && container) {
GetTokenInformation(clientToken, TokenAppContainerSid, nullptr, 0, &needed); std::vector<BYTE> data(needed);
if (GetTokenInformation(clientToken, TokenAppContainerSid, data.data(), needed, &needed)) authenticated = EqualSid(reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>(data.data())->TokenAppContainer, expected) != FALSE;
}
CloseHandle(clientToken);
}
if (process) CloseHandle(process);
}
FreeSid(expected);
char input[8] = {}; DWORD read = 0, written = 0;
bool ok = authenticated && ReadFile(pipe, input, sizeof(input), &read, nullptr) && read == 4 && std::string(input, read) == "PING";
const char response[] = "BROKER_PONG";
if (ok) ok = WriteFile(pipe, response, sizeof(response) - 1, &written, nullptr) && written == sizeof(response) - 1;
if (ok) FlushFileBuffers(pipe);
DisconnectNamedPipe(pipe); CloseHandle(pipe);
std::printf("PIPE_%s\n", ok ? "AUTHENTICATED" : "REJECTED"); return ok ? 0 : 29;
}
static int pipeClient(const std::wstring& id) {
if (!registryId(id)) return 2;
HANDLE token = nullptr;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
DWORD bytes = 0; GetTokenInformation(token, TokenAppContainerSid, nullptr, 0, &bytes); std::vector<BYTE> data(bytes);
if (GetTokenInformation(token, TokenAppContainerSid, data.data(), bytes, &bytes)) {
const PSID sid = reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>(data.data())->TokenAppContainer;
LPWSTR text = nullptr;
if (sid && ConvertSidToStringSidW(sid, &text)) { std::printf("PIPE_CLIENT_SID %ls\n", text); LocalFree(text); std::fflush(stdout); }
}
CloseHandle(token);
}
HANDLE pipe = INVALID_HANDLE_VALUE; const auto deadline = GetTickCount64() + 4000;
do {
pipe = CreateFileW(pipeName(id).c_str(), FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE, 0, nullptr, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, nullptr);
if (pipe != INVALID_HANDLE_VALUE) break;
if (GetLastError() != ERROR_FILE_NOT_FOUND && GetLastError() != ERROR_PIPE_BUSY) { std::printf("PIPE_DENIED %lu\n", GetLastError()); return 29; }
Sleep(10);
} while (GetTickCount64() < deadline);
if (pipe == INVALID_HANDLE_VALUE) { std::printf("PIPE_UNAVAILABLE %lu\n", GetLastError()); return 29; }
DWORD written = 0, read = 0; char response[64] = {};
const bool ok = WriteFile(pipe, "PING", 4, &written, nullptr) && written == 4 && ReadFile(pipe, response, sizeof(response), &read, nullptr);
CloseHandle(pipe);
if (ok) std::fwrite(response, 1, read, stdout);
return ok && std::string(response, read) == "BROKER_PONG" ? 0 : 29;
}
static int stdioBroker(const std::wstring& mode, const std::wstring& wideId) {
if (!registryId(wideId)) return 2;
std::string id;
for (wchar_t ch : wideId) id += static_cast<char>(ch);
if (mode == L"--broker-oversize") {
const std::string frame = "\x1eYCODE_BROKER_V1 " + std::string(20000, 'x') + "\n";
std::fwrite(frame.data(), 1, frame.size(), stdout); std::fflush(stdout); Sleep(5000); return 30;
}
if (mode == L"--broker-flood") {
for (char index = '0'; index < '9'; ++index) {
auto current = id; current.back() = index;
std::printf("\x1eYCODE_BROKER_V1 {\"requestId\":\"%s\",\"operation\":\"echo\",\"payload\":\"probe-echo\"}\n", current.c_str());
}
std::fflush(stdout); Sleep(5000); return 30;
}
for (int attempt = 0; attempt < (mode == L"--broker-replay" ? 2 : 1); ++attempt) {
std::string frame = "\x1eYCODE_BROKER_V1 {\"requestId\":\"" + id + "\",\"operation\":\"" +
(mode == L"--broker-deny" ? "file_read" : "echo") + "\",\"payload\":\"probe-echo\"";
if (mode == L"--broker-spoof") frame += ",\"runId\":\"unrelated-run\"";
frame += "}\n"; std::fwrite(frame.data(), 1, frame.size(), stdout); std::fflush(stdout);
std::string reply; char buffer[1024] = {}; DWORD read = 0;
while (reply.find('\n') == std::string::npos && reply.size() < 16384 && ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &read, nullptr) && read) reply.append(buffer, read);
const bool rejected = mode == L"--broker-deny" || mode == L"--broker-spoof";
if (reply.find("\"requestId\":\"" + id + "\"") == std::string::npos || reply.find(rejected ? "\"ok\":false" : "\"ok\":true") == std::string::npos) return 30;
}
std::puts(mode == L"--broker-deny" || mode == L"--broker-spoof" ? "BROKER_DENIAL_RECEIVED" : "BROKER_ECHO_RECEIVED"); return 0;
}
static int registryProbe(const std::wstring& mode, const std::wstring& id) {
if (!registryId(id)) return 2;
const std::wstring path = L"Software\\YCodeSandboxTests\\" + id;
if (mode == L"--registry-delete") {
const LSTATUS result = RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str());
std::printf("REG_DELETE %ld\n", result);
RegDeleteKeyW(HKEY_CURRENT_USER, L"Software\\YCodeSandboxTests");
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND ? 0 : 27;
}
HKEY key = nullptr;
const bool read = mode == L"--registry-read";
const LSTATUS opened = read ? RegOpenKeyExW(HKEY_CURRENT_USER, path.c_str(), 0, KEY_QUERY_VALUE, &key)
: RegCreateKeyExW(HKEY_CURRENT_USER, path.c_str(), 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key, nullptr);
if (opened != ERROR_SUCCESS) { std::printf("REG_OPEN_DENIED %ld\n", opened); return 27; }
DWORD value = mode == L"--registry-create" ? 0x123456 : 99, bytes = sizeof(value), type = 0;
const LSTATUS result = read ? RegQueryValueExW(key, L"fixture", nullptr, &type, reinterpret_cast<BYTE*>(&value), &bytes)
: RegSetValueExW(key, L"fixture", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(key);
if (read) std::printf("REG_READ %ld %lu\n", result, value);
else std::printf("REG_WRITE %ld\n", result);
return result == ERROR_SUCCESS ? 0 : 27;
}
static std::wstring quoted(const std::wstring& value) { return L"\"" + value + L"\""; }
static int writeFile(const wchar_t* path) {
HANDLE file = CreateFileW(path, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) { std::printf("WRITE_DENIED %lu\n", GetLastError()); return 23; }
const char text[] = "native-probe"; DWORD written = 0;
const bool ok = WriteFile(file, text, sizeof(text) - 1, &written, nullptr) != FALSE;
CloseHandle(file); return ok ? 0 : 23;
}
static int spawnChild(const std::wstring& arguments, bool wait, DWORD flags = 0) {
wchar_t executable[32768] = {};
if (!GetModuleFileNameW(nullptr, executable, 32768)) return 25;
std::wstring command = quoted(executable) + L" " + arguments;
STARTUPINFOW startup = {}; startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
PROCESS_INFORMATION process = {};
if (!CreateProcessW(executable, command.data(), nullptr, nullptr, TRUE, flags, nullptr, nullptr, &startup, &process)) {
std::printf("SPAWN_DENIED %lu\n", GetLastError()); return 25;
}
CloseHandle(process.hThread);
DWORD exit = 0;
if (wait) { WaitForSingleObject(process.hProcess, 10000); GetExitCodeProcess(process.hProcess, &exit); }
CloseHandle(process.hProcess); return static_cast<int>(exit);
}
int wmain(int argc, wchar_t** argv) {
if (argc < 2) return 2;
const std::wstring mode = argv[1];
if (mode.rfind(L"--broker-", 0) == 0 && argc == 3) return stdioBroker(mode, argv[2]);
if (mode == L"--pipe-server" && argc == 4) return pipeServer(argv[2], argv[3]);
if (mode == L"--pipe-client" && argc == 3) return pipeClient(argv[2]);
if (mode == L"--registry-create" || mode == L"--registry-read" || mode == L"--registry-write" || mode == L"--registry-delete") return argc == 3 ? registryProbe(mode, argv[2]) : 2;
if (mode == L"--hold-directory" && argc == 5) {
HANDLE directory = CreateFileW(argv[2], FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
if (directory == INVALID_HANDLE_VALUE) return 28;
FILE_ATTRIBUTE_TAG_INFO tag = {};
if (!GetFileInformationByHandleEx(directory, FileAttributeTagInfo, &tag, sizeof(tag)) || (tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { CloseHandle(directory); return 28; }
if (writeFile(argv[3])) { CloseHandle(directory); return 28; }
const ULONGLONG deadline = GetTickCount64() + 5000;
while (GetFileAttributesW(argv[4]) == INVALID_FILE_ATTRIBUTES && GetTickCount64() < deadline) Sleep(10);
CloseHandle(directory); return 0;
}
if (mode == L"--host-facts") {
HANDLE token = nullptr; TOKEN_ELEVATION elevation = {}; DWORD bytes = 0, container = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return 3;
const bool elevated = GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &bytes) && elevation.TokenIsElevated;
GetTokenInformation(token, TokenIsAppContainer, &container, sizeof(container), &bytes); CloseHandle(token);
std::printf("{\"elevated\":%s,\"appContainer\":%s}\n", elevated ? "true" : "false", container ? "true" : "false"); return 0;
}
if (mode == L"--info") {
HANDLE token = nullptr; DWORD container = 0, bytes = 0; BOOL job = FALSE;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return 3;
GetTokenInformation(token, TokenIsAppContainer, &container, sizeof(container), &bytes); CloseHandle(token);
IsProcessInJob(GetCurrentProcess(), nullptr, &job);
wchar_t secret[128] = {};
const bool inherited = GetEnvironmentVariableW(L"YCODE_FAKE_SECRET", secret, 128) != 0;
std::printf("{\"appContainer\":%s,\"job\":%s,\"secretInherited\":%s}\n", container ? "true" : "false", job ? "true" : "false", inherited ? "true" : "false");
return container && job && !inherited ? 0 : 4;
}
if (mode == L"--write" && argc == 3) return writeFile(argv[2]);
if (mode == L"--delete" && argc == 3) {
const BOOL deleted = DeleteFileW(argv[2]);
std::printf("DELETE_%s %lu\n", deleted ? "OK" : "DENIED", deleted ? 0 : GetLastError());
return deleted ? 0 : 26;
}
if (mode == L"--paths") {
wchar_t cwd[32768] = {}, full[32768] = {};
GetCurrentDirectoryW(32768, cwd); GetFullPathNameW(L".", 32768, full, nullptr);
std::wprintf(L"CWD=%ls FULL=%ls\n", cwd, full);
const DWORD attributes = GetFileAttributesW(L".");
std::printf("ATTR=%lu ERROR=%lu\n", attributes, GetLastError());
WIN32_FIND_DATAW data = {};
HANDLE found = FindFirstFileW(L".\\*", &data);
std::printf("FIND=%s ERROR=%lu\n", found == INVALID_HANDLE_VALUE ? "DENIED" : "OK", GetLastError());
if (found != INVALID_HANDLE_VALUE) FindClose(found);
return 0;
}
if (mode == L"--read" && argc == 3) {
HANDLE file = CreateFileW(argv[2], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (file == INVALID_HANDLE_VALUE) { std::printf("READ_DENIED %lu\n", GetLastError()); return 22; }
char buffer[256] = {}; DWORD bytes = 0;
const bool ok = ReadFile(file, buffer, sizeof(buffer), &bytes, nullptr) != FALSE; CloseHandle(file);
if (ok) std::fwrite(buffer, 1, bytes, stdout);
return ok ? 0 : 22;
}
if (mode == L"--tcp" && argc == 3) {
WSADATA data = {}; if (WSAStartup(MAKEWORD(2, 2), &data)) return 24;
SOCKET socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket == INVALID_SOCKET) { WSACleanup(); return 24; }
sockaddr_in target = {}; target.sin_family = AF_INET; target.sin_port = htons(static_cast<u_short>(_wtoi(argv[2]))); target.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
u_long nonblocking = 1; ioctlsocket(socket, FIONBIO, &nonblocking);
int connected = connect(socket, reinterpret_cast<sockaddr*>(&target), sizeof(target));
int error = connected == 0 ? 0 : WSAGetLastError();
if (connected != 0 && error == WSAEWOULDBLOCK) {
fd_set ready, failed; FD_ZERO(&ready); FD_ZERO(&failed); FD_SET(socket, &ready); FD_SET(socket, &failed);
timeval timeout = {1, 0};
if (select(0, nullptr, &ready, &failed, &timeout) > 0) {
int length = sizeof(error); getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &length);
connected = error == 0 ? 0 : SOCKET_ERROR;
} else { connected = SOCKET_ERROR; error = WSAETIMEDOUT; }
}
std::printf("TCP_%s %d\n", connected == 0 ? "CONNECTED" : "DENIED", error);
closesocket(socket); WSACleanup(); return connected == 0 ? 0 : 24;
}
if (mode == L"--delay" && argc == 4) { Sleep(static_cast<DWORD>(_wtoi(argv[2]))); return writeFile(argv[3]); }
if (mode == L"--spawn-write" && argc == 3) return spawnChild(L"--write " + quoted(argv[2]), true);
if (mode == L"--spawn-delayed" && argc == 3) return spawnChild(L"--delay 1400 " + quoted(argv[2]), false);
if (mode == L"--breakaway" && argc == 3) return spawnChild(L"--write " + quoted(argv[2]), true, CREATE_BREAKAWAY_FROM_JOB);
return 2;
}