Files
safetynet/apps/network-broker/native/service-host.cpp
T

1803 lines
70 KiB
C++

// Native SCM boundary host. No Chromium/Electron changes; installation is
// separate.
#define WIN32_LEAN_AND_MEAN
#include "service-pins.h"
#include "wfp-topology.h"
#include <aclapi.h>
#include <array>
#include <atomic>
#include <bcrypt.h>
#include <cstdio>
#include <memory>
#include <sddl.h>
#include <string>
#include <thread>
#include <tlhelp32.h>
#include <vector>
#include <windows.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "bcrypt.lib")
static constexpr wchar_t SERVICE_NAME[] = L"SafetyNetNetworkBroker";
static constexpr wchar_t PIPE_NAME[] = L"\\\\.\\pipe\\SafetyNet.Broker.v1";
struct Handle {
HANDLE h = nullptr;
explicit Handle(HANDLE v = nullptr) : h(v) {}
~Handle() {
if (h && h != INVALID_HANDLE_VALUE)
CloseHandle(h);
}
Handle(const Handle &) = delete;
Handle &operator=(const Handle &) = delete;
operator HANDLE() const { return h; }
};
static HANDLE stopEvent;
static SERVICE_STATUS_HANDLE statusHandle;
static bool hashFile(const std::wstring &file, const char *expected) {
Handle input(CreateFileW(file.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT,
nullptr));
if (input.h == INVALID_HANDLE_VALUE)
return false;
FILE_ATTRIBUTE_TAG_INFO info{};
if (!GetFileInformationByHandleEx(input, FileAttributeTagInfo, &info,
sizeof(info)) ||
(info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
return false;
BCRYPT_ALG_HANDLE algorithm = nullptr;
BCRYPT_HASH_HANDLE hash = nullptr;
bool good = false;
if (BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_SHA256_ALGORITHM, nullptr,
0) < 0)
return false;
if (BCryptCreateHash(algorithm, &hash, nullptr, 0, nullptr, 0, 0) >= 0) {
std::array<BYTE, 65536> buffer;
DWORD read = 0;
bool okay = true;
while (true) {
if (!ReadFile(input, buffer.data(), DWORD(buffer.size()), &read,
nullptr)) {
okay = false;
break;
}
if (!read)
break;
if (BCryptHashData(hash, buffer.data(), read, 0) < 0) {
okay = false;
break;
}
}
std::array<BYTE, 32> digest{};
if (okay && BCryptFinishHash(hash, digest.data(), 32, 0) >= 0) {
char hex[65]{};
for (size_t i = 0; i < 32; i++)
sprintf_s(hex + i * 2, 3, "%02x", digest[i]);
good = strcmp(hex, expected) == 0;
}
BCryptDestroyHash(hash);
}
BCryptCloseAlgorithmProvider(algorithm, 0);
return good;
}
static std::vector<BYTE> tokenInfo(HANDLE token, TOKEN_INFORMATION_CLASS kind) {
DWORD size = 0;
GetTokenInformation(token, kind, nullptr, 0, &size);
if (!size || size > 65536)
return {};
std::vector<BYTE> value(size);
if (!GetTokenInformation(token, kind, value.data(), size, &size))
return {};
return value;
}
static bool serviceIdentity(HANDLE process = GetCurrentProcess()) {
Handle token;
if (!OpenProcessToken(process, TOKEN_QUERY, &token.h))
return false;
auto groups = tokenInfo(token, TokenGroups);
if (groups.empty())
return false;
DWORD sidBytes = 0, domainBytes = 0;
SID_NAME_USE use;
LookupAccountNameW(nullptr, L"NT SERVICE\\SafetyNetNetworkBroker", nullptr,
&sidBytes, nullptr, &domainBytes, &use);
if (!sidBytes || sidBytes > 65536)
return false;
std::vector<BYTE> sid(sidBytes);
std::vector<wchar_t> domain(domainBytes);
if (!LookupAccountNameW(nullptr, L"NT SERVICE\\SafetyNetNetworkBroker",
sid.data(), &sidBytes, domain.data(), &domainBytes,
&use))
return false;
auto list = reinterpret_cast<TOKEN_GROUPS *>(groups.data());
for (DWORD i = 0; i < list->GroupCount; i++)
if (EqualSid(list->Groups[i].Sid, sid.data()) &&
(list->Groups[i].Attributes & SE_GROUP_ENABLED) &&
!(list->Groups[i].Attributes & SE_GROUP_USE_FOR_DENY_ONLY))
return true;
return false;
}
static std::vector<BYTE> serviceSid() {
DWORD size = 0, domainSize = 0;
SID_NAME_USE use;
LookupAccountNameW(nullptr, L"NT SERVICE\\SafetyNetNetworkBroker", nullptr,
&size, nullptr, &domainSize, &use);
if (!size || size > 65536 || domainSize > 32768)
return {};
std::vector<BYTE> sid(size);
std::vector<wchar_t> domain(domainSize);
if (!LookupAccountNameW(nullptr, L"NT SERVICE\\SafetyNetNetworkBroker",
sid.data(), &size, domain.data(), &domainSize, &use))
return {};
return sid;
}
static bool protectedPath(const std::wstring &file) {
// Resources may be readable by employees, but only SYSTEM/Administrators may
// modify code. Unknown write trustees fail closed; inheritable-only ACEs are
// checked on their descendants rather than treated as effective on the
// parent.
PSECURITY_DESCRIPTOR descriptor = nullptr;
PACL dacl = nullptr;
PSID owner = nullptr;
DWORD code = GetNamedSecurityInfoW(
file.c_str(), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, &owner, nullptr,
&dacl, nullptr, &descriptor);
if (code || !dacl)
return false;
BYTE system[SECURITY_MAX_SID_SIZE], admins[SECURITY_MAX_SID_SIZE];
DWORD ss = sizeof(system), as = sizeof(admins);
CreateWellKnownSid(WinLocalSystemSid, nullptr, system, &ss);
CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, admins, &as);
bool good = owner && (EqualSid(owner, system) || EqualSid(owner, admins));
for (DWORD i = 0; i < dacl->AceCount; i++) {
void *raw = nullptr;
if (!GetAce(dacl, i, &raw)) {
good = false;
break;
}
auto header = static_cast<ACE_HEADER *>(raw);
if (header->AceFlags & INHERIT_ONLY_ACE)
continue;
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) {
auto ace = static_cast<ACCESS_ALLOWED_ACE *>(raw);
DWORD write = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA |
FILE_WRITE_ATTRIBUTES | DELETE | WRITE_DAC | WRITE_OWNER |
FILE_DELETE_CHILD | GENERIC_WRITE | GENERIC_ALL;
if ((ace->Mask & write) && !EqualSid(&ace->SidStart, system) &&
!EqualSid(&ace->SidStart, admins)) {
good = false;
break;
}
} else if (header->AceType != ACCESS_DENIED_ACE_TYPE) {
good = false;
break;
}
}
LocalFree(descriptor);
return good;
}
#include "acceptance-state.h"
#include "management-credential.h"
#include "signed-manifest.h"
struct TrustFloor {
ULONGLONG release = 0, deployment = 0;
std::string releaseDigest, deploymentDigest, stateCanonical;
};
static TrustFloor trustFloor() {
HKEY key = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\SafetyNet\\Trust", 0,
KEY_READ | READ_CONTROL | KEY_WOW64_64KEY,
&key) != ERROR_SUCCESS)
throw std::runtime_error("fixed-trust-unavailable");
// A writable counter store would turn signature checking into rollback.
PSECURITY_DESCRIPTOR descriptor = nullptr;
PACL dacl = nullptr;
PSID owner = nullptr;
DWORD status =
GetSecurityInfo(key, SE_REGISTRY_KEY,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
&owner, nullptr, &dacl, nullptr, &descriptor);
BYTE system[SECURITY_MAX_SID_SIZE], admins[SECURITY_MAX_SID_SIZE];
DWORD ss = sizeof(system), as = sizeof(admins);
CreateWellKnownSid(WinLocalSystemSid, nullptr, system, &ss);
CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, admins, &as);
bool valid = !status && dacl && owner &&
(EqualSid(owner, system) || EqualSid(owner, admins));
auto service = serviceSid();
if (valid)
for (DWORD i = 0; i < dacl->AceCount; i++) {
void *raw = nullptr;
if (!GetAce(dacl, i, &raw)) {
valid = false;
break;
}
auto header = static_cast<ACE_HEADER *>(raw);
if (header->AceFlags & INHERIT_ONLY_ACE)
continue;
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) {
auto ace = static_cast<ACCESS_ALLOWED_ACE *>(raw);
if ((ace->Mask &
(KEY_SET_VALUE | KEY_CREATE_SUB_KEY | DELETE | WRITE_DAC |
WRITE_OWNER | GENERIC_WRITE | GENERIC_ALL)) &&
!EqualSid(&ace->SidStart, system) &&
!EqualSid(&ace->SidStart, admins) &&
!(service.size() && EqualSid(&ace->SidStart, service.data()) &&
!(ace->Mask &
~(KEY_QUERY_VALUE | KEY_SET_VALUE | READ_CONTROL | KEY_NOTIFY |
KEY_ENUMERATE_SUB_KEYS | SYNCHRONIZE)))) {
valid = false;
break;
}
} else if (header->AceType != ACCESS_DENIED_ACE_TYPE) {
valid = false;
break;
}
}
if (descriptor)
LocalFree(descriptor);
DWORD type = 0, size = 0;
bool okay = valid &&
RegQueryValueExW(key, L"AcceptanceState", nullptr, &type, nullptr,
&size) == ERROR_SUCCESS &&
type == REG_BINARY && size > 0 && size <= 2097152;
std::string canonical(okay ? size : 0, '\0');
if (okay)
okay = RegQueryValueExW(key, L"AcceptanceState", nullptr, &type,
reinterpret_cast<BYTE *>(canonical.data()),
&size) == ERROR_SUCCESS &&
type == REG_BINARY && size == canonical.size();
RegCloseKey(key);
if (!okay)
throw std::runtime_error("fixed-trust-unavailable");
auto state = acceptance_contract::validate(canonical);
TrustFloor floor;
floor.release = release_contract::number(
state.object.at("release").object.at("minimumSequence"));
floor.deployment = release_contract::number(
state.object.at("deployment").object.at("minimumSequence"));
floor.releaseDigest = release_contract::str(
state.object.at("release").object.at("acceptedDigest"));
floor.deploymentDigest = release_contract::str(
state.object.at("deployment").object.at("acceptedDigest"));
floor.stateCanonical = canonical;
return floor;
}
static release_contract::Release trustedRelease(const std::wstring &root) {
auto floor = trustFloor();
auto release = release_contract::release(
root, release_contract::unhex(RELEASE_ANCHOR_XY), floor.release,
floor.releaseDigest);
auto state = acceptance_contract::validate(floor.stateCanonical);
if (release.tenant !=
release_contract::str(state.object.at("deployment").object.at("tenant")))
throw std::runtime_error("fixed-trust-unavailable");
return release;
}
static std::string currentSid(HANDLE process) {
Handle token;
if (!OpenProcessToken(process, TOKEN_QUERY, &token.h))
throw std::runtime_error("fixed-peer-invalid");
auto value = tokenInfo(token, TokenUser);
if (value.empty())
throw std::runtime_error("fixed-peer-invalid");
LPSTR text = nullptr;
if (!ConvertSidToStringSidA(
reinterpret_cast<TOKEN_USER *>(value.data())->User.Sid, &text))
throw std::runtime_error("fixed-peer-invalid");
std::string result = text;
LocalFree(text);
return result;
}
static Handle *parentProcess(HANDLE process) {
// Called only with a retained actual process handle; no PID supplied by IPC.
DWORD pid = GetProcessId(process);
Handle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
PROCESSENTRY32W item{};
item.dwSize = sizeof(item);
if (snapshot.h == INVALID_HANDLE_VALUE || !Process32FirstW(snapshot, &item))
return nullptr;
do {
if (item.th32ProcessID == pid) {
HANDLE parent =
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE,
item.th32ParentProcessID);
if (!parent)
return nullptr;
FILETIME childCreated{}, parentCreated{}, exit{}, kernel{}, user{};
if (!GetProcessTimes(process, &childCreated, &exit, &kernel, &user) ||
!GetProcessTimes(parent, &parentCreated, &exit, &kernel, &user) ||
CompareFileTime(&parentCreated, &childCreated) > 0) {
CloseHandle(parent);
return nullptr;
}
return new Handle(parent);
}
} while (Process32NextW(snapshot, &item));
return nullptr;
}
static bool verifiedImage(HANDLE process, const std::wstring &root,
const char *relative,
const release_contract::Release &release) {
std::array<wchar_t, 32768> image{};
DWORD size = DWORD(image.size());
std::wstring required =
root + L"\\" + std::wstring(relative, relative + strlen(relative));
auto found = release.hashes.find(relative);
if (found == release.hashes.end() ||
WaitForSingleObject(process, 0) != WAIT_TIMEOUT ||
!QueryFullProcessImageNameW(process, 0, image.data(), &size) ||
CompareStringOrdinal(image.data(), int(size), required.c_str(), -1,
TRUE) != CSTR_EQUAL ||
!protectedPath(required) || !protectedPath(root))
return false;
return hashFile(required, found->second.c_str());
}
static bool sameUserSession(HANDLE left, HANDLE right, bool service = false) {
Handle lt, rt;
if (!OpenProcessToken(left, TOKEN_QUERY, &lt.h) ||
!OpenProcessToken(right, TOKEN_QUERY, &rt.h))
return false;
auto lu = tokenInfo(lt, TokenUser), ru = tokenInfo(rt, TokenUser),
ls = tokenInfo(lt, TokenSessionId), rs = tokenInfo(rt, TokenSessionId),
lc = tokenInfo(lt, TokenIsAppContainer),
rc = tokenInfo(rt, TokenIsAppContainer),
li = tokenInfo(lt, TokenIntegrityLevel),
ri = tokenInfo(rt, TokenIntegrityLevel);
if (lu.empty() || ru.empty() || ls.empty() || rs.empty() || lc.empty() ||
rc.empty() || li.empty() || ri.empty() ||
*reinterpret_cast<DWORD *>(lc.data()) ||
*reinterpret_cast<DWORD *>(rc.data()))
return false;
for (const auto *item : {&li, &ri}) {
auto label = reinterpret_cast<const TOKEN_MANDATORY_LABEL *>(item->data());
DWORD level = *GetSidSubAuthority(
label->Label.Sid, *GetSidSubAuthorityCount(label->Label.Sid) - 1);
if (level < SECURITY_MANDATORY_MEDIUM_RID ||
level > DWORD(service ? SECURITY_MANDATORY_SYSTEM_RID
: SECURITY_MANDATORY_HIGH_RID))
return false;
}
return EqualSid(reinterpret_cast<TOKEN_USER *>(lu.data())->User.Sid,
reinterpret_cast<TOKEN_USER *>(ru.data())->User.Sid) &&
*reinterpret_cast<DWORD *>(ls.data()) ==
*reinterpret_cast<DWORD *>(rs.data());
}
static bool pipeClient(HANDLE pipe, const std::wstring &root,
HANDLE *retained = nullptr) {
ULONG pid = 0;
if (!GetNamedPipeClientProcessId(pipe, &pid) || !pid)
return false;
Handle process(
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid));
if (!process.h || WaitForSingleObject(process, 0) != WAIT_TIMEOUT)
return false;
try {
auto release = trustedRelease(root);
std::unique_ptr<Handle> parent(parentProcess(process));
if (!parent ||
!verifiedImage(process, root, "safetynet-service-host.exe", release) ||
!verifiedImage(*parent, root, "SafetyNet.exe", release) ||
!sameUserSession(process, *parent))
return false;
} catch (...) {
return false;
}
Handle actual;
if (!OpenProcessToken(process, TOKEN_QUERY, &actual.h))
return false;
auto user = tokenInfo(actual, TokenUser),
session = tokenInfo(actual, TokenSessionId),
container = tokenInfo(actual, TokenIsAppContainer),
integrity = tokenInfo(actual, TokenIntegrityLevel);
if (user.empty() || session.empty() || container.empty() ||
integrity.empty() || *reinterpret_cast<DWORD *>(container.data()))
return false;
auto label = reinterpret_cast<TOKEN_MANDATORY_LABEL *>(integrity.data());
DWORD level = *GetSidSubAuthority(
label->Label.Sid, *GetSidSubAuthorityCount(label->Label.Sid) - 1);
if (level < SECURITY_MANDATORY_MEDIUM_RID ||
level > SECURITY_MANDATORY_HIGH_RID)
return false;
if (!ImpersonateNamedPipeClient(pipe))
return false;
Handle peer;
bool opened =
OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &peer.h) != FALSE;
bool reverted = RevertToSelf() != FALSE;
if (!reverted) {
TerminateProcess(GetCurrentProcess(), ERROR_ACCESS_DENIED);
return false;
}
if (!opened)
return false;
auto peerUser = tokenInfo(peer, TokenUser),
peerSession = tokenInfo(peer, TokenSessionId);
if (peerUser.empty() || peerSession.empty())
return false;
bool authenticated =
EqualSid(reinterpret_cast<TOKEN_USER *>(user.data())->User.Sid,
reinterpret_cast<TOKEN_USER *>(peerUser.data())->User.Sid) &&
*reinterpret_cast<DWORD *>(session.data()) ==
*reinterpret_cast<DWORD *>(peerSession.data()) &&
WaitForSingleObject(process, 0) == WAIT_TIMEOUT;
if (authenticated && retained) {
*retained = process.h;
process.h = nullptr;
}
return authenticated;
}
static bool transfer(HANDLE pipe, bool write, void *bytes, DWORD length,
DWORD timeout, ULONGLONG *firstByteAt = nullptr) {
auto cursor = static_cast<BYTE *>(bytes);
ULONGLONG started = GetTickCount64();
while (length) {
Handle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
OVERLAPPED ov{};
ov.hEvent = event;
DWORD done = 0;
BOOL result = write ? WriteFile(pipe, cursor, length, &done, &ov)
: ReadFile(pipe, cursor, length, &done, &ov);
if (!result) {
if (GetLastError() != ERROR_IO_PENDING)
return false;
HANDLE waits[] = {event.h, stopEvent};
ULONGLONG elapsed = GetTickCount64() - started;
DWORD remaining =
timeout == INFINITE
? INFINITE
: (elapsed >= timeout ? 0 : timeout - DWORD(elapsed));
DWORD status = WaitForMultipleObjects(2, waits, FALSE, remaining);
if (status != WAIT_OBJECT_0) {
CancelIoEx(pipe, &ov);
WaitForSingleObject(event, INFINITE);
return false;
}
if (!GetOverlappedResult(pipe, &ov, &done, FALSE))
return false;
}
if (!done)
return false;
if (firstByteAt && !*firstByteAt)
*firstByteAt = GetTickCount64();
if (timeout == INFINITE && done < length) {
timeout = 15000;
started = GetTickCount64();
}
cursor += done;
length -= done;
}
return true;
}
static bool frame(HANDLE pipe, bool write, std::vector<BYTE> &value,
DWORD maximum, DWORD headerTimeout = 15000) {
ULONGLONG started = GetTickCount64();
ULONGLONG firstByteAt = 0;
DWORD size = write ? DWORD(value.size()) : 0;
if (!transfer(pipe, write, &size, 4, headerTimeout, &firstByteAt) || !size ||
size > maximum)
return false;
if (headerTimeout == INFINITE)
started = firstByteAt;
DWORD budget = headerTimeout == INFINITE ? 15000 : headerTimeout;
ULONGLONG elapsed = GetTickCount64() - started;
if (elapsed >= budget)
return false;
if (!write)
value.resize(size);
return transfer(pipe, write, value.data(), size, budget - DWORD(elapsed));
}
static std::wstring rootPath() {
std::array<wchar_t, 32768> image{};
DWORD size = GetModuleFileNameW(nullptr, image.data(), DWORD(image.size()));
if (!size || size >= image.size())
return {};
std::wstring root(image.data(), size);
return root.substr(0, root.find_last_of(L'\\'));
}
static bool resources(const std::wstring &root) {
try {
if (root.empty() || !protectedPath(root) ||
!protectedPath(root + L"\\release-envelope.json") ||
!protectedPath(root + L"\\deployment-envelope.json"))
return false;
auto release = trustedRelease(root);
for (const auto &pin : release.hashes) {
std::wstring relative(pin.first.begin(), pin.first.end());
std::replace(relative.begin(), relative.end(), L'/', L'\\');
std::wstring file = root + std::wstring(1, wchar_t(92)) + relative,
ancestor = file;
while (ancestor.size() > root.size()) {
if (GetFileAttributesW(ancestor.c_str()) &
FILE_ATTRIBUTE_REPARSE_POINT ||
!protectedPath(ancestor))
return false;
ancestor = ancestor.substr(0, ancestor.find_last_of(L'\\'));
}
if (!hashFile(file, pin.second.c_str()))
return false;
}
return true;
} catch (...) {
return false;
}
}
static std::wstring environment(const std::string &sid) {
std::wstring result;
for (const wchar_t *name : {L"SystemRoot", L"WINDIR", L"TEMP", L"TMP",
L"USERPROFILE", L"LOCALAPPDATA", L"APPDATA"}) {
DWORD size = GetEnvironmentVariableW(name, nullptr, 0);
if (size && size < 32768) {
std::vector<wchar_t> value(size);
GetEnvironmentVariableW(name, value.data(), size);
result += name;
result += L'=';
result += value.data();
result += L'\0';
}
}
result += L"SAFETYNET_SERVICE_CLIENT_SID=";
result += std::wstring(sid.begin(), sid.end());
result += L'\0';
result += L'\0';
return result;
}
static bool writeChildFrame(HANDLE pipe, const std::vector<BYTE> &value,
std::atomic<ULONGLONG> &deadline,
DWORD budget = 15000) {
deadline = GetTickCount64() + budget;
DWORD size = DWORD(value.size());
auto exact = [&](const BYTE *bytes, DWORD length) {
while (length) {
DWORD done = 0;
if (!WriteFile(pipe, bytes, length, &done, nullptr) || !done)
return false;
bytes += done;
length -= done;
}
return true;
};
if (!exact(reinterpret_cast<const BYTE *>(&size), 4) ||
!exact(value.data(), size))
return false;
deadline = 0;
return true;
}
static bool verifiedServer(HANDLE pipe, const std::wstring &root,
const release_contract::Release &release,
HANDLE *retained,
const char *role = "safetynet-service-host.exe") {
ULONG pid = 0;
if (!GetNamedPipeServerProcessId(pipe, &pid) || !pid)
return false;
Handle process(
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid));
if (!process.h || !verifiedImage(process, root, role, release) ||
!serviceIdentity(process))
return false;
*retained = process.h;
process.h = nullptr;
return true;
}
static bool boundedStdioWrite(const std::string &text);
static int topologySnapshot() {
try {
const auto root = rootPath();
if (!serviceIdentity())
return ERROR_ACCESS_DENIED;
auto release = trustedRelease(root);
std::unique_ptr<Handle> parent(parentProcess(GetCurrentProcess()));
if (!parent || !serviceIdentity(*parent) ||
!verifiedImage(GetCurrentProcess(), root, "safetynet-service-host.exe",
release) ||
!verifiedImage(*parent, root, "resources/broker/node.exe", release) ||
!sameUserSession(GetCurrentProcess(), *parent, true))
return ERROR_ACCESS_DENIED;
DWORD userLength =
GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", nullptr, 0);
if (!userLength || userLength > 256)
return ERROR_ACCESS_DENIED;
std::string userSid(userLength, '\0');
if (!GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", userSid.data(),
userLength))
return ERROR_ACCESS_DENIED;
userSid.resize(userLength - 1);
auto assignmentFloor = trustFloor();
release_contract::deployment(root, release, userSid,
assignmentFloor.deployment,
assignmentFloor.deploymentDigest);
auto sid = serviceSid();
LPWSTR text = nullptr;
if (sid.empty() || !ConvertSidToStringSidW(sid.data(), &text))
return ERROR_ACCESS_DENIED;
std::wstring serviceText = text;
LocalFree(text);
auto result = topology_contract::inspect(root, serviceText);
const std::string json =
"{\"version\":1,\"brokerPort\":43120,\"accessibleFilters\":" +
std::to_string(result.accessibleFilters) +
",\"ownedFilters\":" + std::to_string(result.ownedFilters) +
",\"ownedVerified\":" + (result.ownedVerified ? "true" : "false") +
",\"collision\":" + (result.collision ? "true" : "false") +
",\"bfeRunning\":" + (result.bfeRunning ? "true" : "false") +
",\"allObjectsReadable\":false,\"code\":" +
std::to_string(result.code) + "}";
return boundedStdioWrite(json) ? 0 : ERROR_ACCESS_DENIED;
} catch (...) {
return ERROR_ACCESS_DENIED;
}
}
static int trustSnapshot() {
try {
const auto root = rootPath();
if (!serviceIdentity())
return ERROR_ACCESS_DENIED;
auto release = trustedRelease(root);
std::unique_ptr<Handle> parent(parentProcess(GetCurrentProcess()));
if (!parent || !serviceIdentity(*parent) ||
!verifiedImage(GetCurrentProcess(), root, "safetynet-service-host.exe",
release) ||
!verifiedImage(*parent, root, "resources/broker/node.exe", release) ||
!sameUserSession(GetCurrentProcess(), *parent, true))
return ERROR_ACCESS_DENIED;
DWORD length =
GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", nullptr, 0);
if (!length || length > 256)
return ERROR_ACCESS_DENIED;
std::string sid(length, '\0');
if (!GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", sid.data(),
length))
return ERROR_ACCESS_DENIED;
sid.resize(length - 1);
auto floor = trustFloor();
release_contract::deployment(root, release, sid, floor.deployment,
floor.deploymentDigest);
auto xy = release_contract::unhex(RELEASE_ANCHOR_XY);
auto der = release_contract::unhex(
"3059301306072a8648ce3d020106082a8648ce3d03010703420004");
der.insert(der.end(), xy.begin(), xy.end());
std::string encoded = release_contract::base64url(der);
std::replace(encoded.begin(), encoded.end(), '-', '+');
std::replace(encoded.begin(), encoded.end(), '_', '/');
while (encoded.size() % 4)
encoded += '=';
std::string anchor = "-----BEGIN PUBLIC KEY-----\n";
for (size_t offset = 0; offset < encoded.size(); offset += 64)
anchor += encoded.substr(offset, 64) + '\n';
anchor += "-----END PUBLIC KEY-----\n";
// All values originate in immutable code/protected Windows state. No
// registry selector, key, SID or path is accepted from command arguments.
std::string json =
"{\"acceptanceState\":" + floor.stateCanonical +
",\"authenticatedSid\":" + release_contract::escaped(sid) +
",\"releaseAnchor\":" + release_contract::escaped(anchor) +
",\"version\":1}";
return boundedStdioWrite(json) ? 0 : ERROR_ACCESS_DENIED;
} catch (...) {
return ERROR_ACCESS_DENIED;
}
}
static bool readStdioFrame(HANDLE input, std::vector<BYTE> &value,
DWORD maximum, std::atomic<ULONGLONG> &deadline) {
DWORD size = 0, done = 0;
if (!ReadFile(input, &size, 4, &done, nullptr) || !done)
return false;
deadline = GetTickCount64() + 15000;
DWORD offset = done;
while (offset < 4) {
if (!ReadFile(input, reinterpret_cast<BYTE *>(&size) + offset, 4 - offset,
&done, nullptr) ||
!done)
return false;
offset += done;
}
if (!size || size > maximum)
return false;
value.resize(size);
offset = 0;
while (offset < size) {
if (!ReadFile(input, value.data() + offset, size - offset, &done,
nullptr) ||
!done)
return false;
offset += done;
}
deadline = 0;
return true;
}
static int clientStdio() {
try {
const auto root = rootPath();
auto release = trustedRelease(root);
std::unique_ptr<Handle> parent(parentProcess(GetCurrentProcess()));
if (!parent ||
!verifiedImage(GetCurrentProcess(), root, "safetynet-service-host.exe",
release) ||
!verifiedImage(*parent, root, "SafetyNet.exe", release) ||
!sameUserSession(GetCurrentProcess(), *parent))
return ERROR_ACCESS_DENIED;
auto floor = trustFloor();
release_contract::deployment(root, release, currentSid(GetCurrentProcess()),
floor.deployment, floor.deploymentDigest);
HANDLE input = GetStdHandle(STD_INPUT_HANDLE),
output = GetStdHandle(STD_OUTPUT_HANDLE);
if (GetFileType(input) != FILE_TYPE_PIPE ||
GetFileType(output) != FILE_TYPE_PIPE)
return ERROR_ACCESS_DENIED;
// Connect exactly once, then authenticate THIS handle's server. No probe
// and reconnect, retry, alternate endpoint, service startup or command
// selector.
Handle pipe(CreateFileW(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
if (pipe.h == INVALID_HANDLE_VALUE)
return ERROR_ACCESS_DENIED;
Handle server;
if (!verifiedServer(pipe, root, release, &server.h))
return ERROR_ACCESS_DENIED;
Handle stop(CreateEventW(nullptr, TRUE, FALSE, nullptr));
stopEvent = stop;
Handle reader;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &reader.h, THREAD_TERMINATE,
FALSE, 0))
return ERROR_ACCESS_DENIED;
std::atomic<bool> good{true};
std::atomic<ULONGLONG> readDeadline{0}, writeDeadline{0};
std::thread outputPump([&] {
while (good) {
std::vector<BYTE> value;
if (!frame(pipe, false, value, 65536, INFINITE) ||
!writeChildFrame(output, value, writeDeadline)) {
good = false;
break;
}
}
CancelSynchronousIo(reader);
SetEvent(stopEvent);
});
std::thread watcher([&] {
while (good) {
if (WaitForSingleObject(stopEvent, 50) == WAIT_OBJECT_0 ||
WaitForSingleObject(*parent, 0) != WAIT_TIMEOUT ||
WaitForSingleObject(server, 0) != WAIT_TIMEOUT ||
(readDeadline.load() && GetTickCount64() >= readDeadline.load()) ||
(writeDeadline.load() &&
GetTickCount64() >= writeDeadline.load())) {
good = false;
SetEvent(stopEvent);
CancelSynchronousIo(reader);
CancelSynchronousIo(outputPump.native_handle());
CancelIoEx(pipe, nullptr);
break;
}
}
});
bool first = true;
while (good) {
std::vector<BYTE> value;
if (!readStdioFrame(input, value, first ? 2097152 : 65536,
readDeadline) ||
!frame(pipe, true, value, first ? 2097152 : 65536))
break;
first = false;
}
good = false;
SetEvent(stopEvent);
CancelIoEx(pipe, nullptr);
CancelSynchronousIo(outputPump.native_handle());
outputPump.join();
watcher.join();
return 0;
} catch (...) {
return ERROR_ACCESS_DENIED;
}
}
static bool boundedStdioRead(HANDLE input, std::vector<BYTE> &value,
DWORD maximum) {
if (GetFileType(input) != FILE_TYPE_PIPE)
return false;
Handle ownThread;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &ownThread.h, THREAD_TERMINATE,
FALSE, 0))
return false;
std::atomic<bool> finished{false};
std::atomic<ULONGLONG> deadline{GetTickCount64() + 15000};
const ULONGLONG expires = GetTickCount64() + 15000;
std::thread watchdog([&] {
while (!finished) {
if (GetTickCount64() >= expires) {
CancelSynchronousIo(ownThread);
break;
}
Sleep(10);
}
});
bool okay = readStdioFrame(input, value, maximum, deadline);
finished = true;
watchdog.join();
return okay;
}
static bool boundedStdioWrite(const std::string &text) {
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
if (GetFileType(output) != FILE_TYPE_PIPE || text.size() > 2097152)
return false;
Handle ownThread;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &ownThread.h, THREAD_TERMINATE,
FALSE, 0))
return false;
std::atomic<bool> finished{false};
std::atomic<ULONGLONG> deadline{0};
const ULONGLONG expires = GetTickCount64() + 15000;
std::thread watchdog([&] {
while (!finished) {
if (GetTickCount64() >= expires) {
CancelSynchronousIo(ownThread);
break;
}
Sleep(10);
}
});
std::vector<BYTE> bytes(text.begin(), text.end());
bool okay = writeChildFrame(output, bytes, deadline);
finished = true;
watchdog.join();
if (!bytes.empty())
SecureZeroMemory(bytes.data(), bytes.size());
return okay;
}
static bool ownedAcceptanceMutex(HANDLE mutex,
const std::vector<BYTE> &service) {
PSECURITY_DESCRIPTOR descriptor = nullptr;
PACL dacl = nullptr;
PSID owner = nullptr;
DWORD result =
GetSecurityInfo(mutex, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&owner, nullptr, &dacl, nullptr, &descriptor);
BYTE system[SECURITY_MAX_SID_SIZE], admins[SECURITY_MAX_SID_SIZE],
localService[SECURITY_MAX_SID_SIZE];
DWORD ss = sizeof(system), as = sizeof(admins), ls = sizeof(localService);
CreateWellKnownSid(WinLocalSystemSid, nullptr, system, &ss);
CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, admins, &as);
CreateWellKnownSid(WinLocalServiceSid, nullptr, localService, &ls);
PSID ownerRights = nullptr;
if (!ConvertStringSidToSidW(L"S-1-3-4", &ownerRights)) {
if (descriptor)
LocalFree(descriptor);
return false;
}
bool valid = result == ERROR_SUCCESS && owner && dacl && service.size() &&
(EqualSid(owner, localService) ||
EqualSid(owner, const_cast<BYTE *>(service.data())));
unsigned ownerRightsCount = 0;
if (valid)
for (DWORD index = 0; index < dacl->AceCount; index++) {
void *raw = nullptr;
if (!GetAce(dacl, index, &raw)) {
valid = false;
break;
}
auto header = static_cast<ACE_HEADER *>(raw);
if (header->AceFlags & INHERIT_ONLY_ACE)
continue;
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) {
auto ace = static_cast<ACCESS_ALLOWED_ACE *>(raw);
if (EqualSid(&ace->SidStart, ownerRights)) {
if (ace->Mask != READ_CONTROL) {
valid = false;
break;
}
ownerRightsCount++;
continue;
}
if (!EqualSid(&ace->SidStart, system) &&
!EqualSid(&ace->SidStart, admins) &&
!EqualSid(&ace->SidStart, const_cast<BYTE *>(service.data()))) {
valid = false;
break;
}
} else if (header->AceType != ACCESS_DENIED_ACE_TYPE) {
valid = false;
break;
}
}
if (descriptor)
LocalFree(descriptor);
LocalFree(ownerRights);
return valid && ownerRightsCount == 1;
}
static bool protectedCredentialKey(HKEY key, const std::vector<BYTE> &service) {
PSECURITY_DESCRIPTOR descriptor = nullptr;
PACL dacl = nullptr;
PSID owner = nullptr;
DWORD status =
GetSecurityInfo(key, SE_REGISTRY_KEY,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
&owner, nullptr, &dacl, nullptr, &descriptor);
BYTE system[SECURITY_MAX_SID_SIZE], admins[SECURITY_MAX_SID_SIZE];
DWORD ss = sizeof(system), as = sizeof(admins);
CreateWellKnownSid(WinLocalSystemSid, nullptr, system, &ss);
CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, admins, &as);
SECURITY_DESCRIPTOR_CONTROL control{};
DWORD revision = 0;
bool okay = !status && dacl && owner &&
(EqualSid(owner, system) || EqualSid(owner, admins)) &&
GetSecurityDescriptorControl(descriptor, &control, &revision) &&
(control & SE_DACL_PROTECTED);
if (okay)
for (DWORD i = 0; i < dacl->AceCount; ++i) {
void *raw = nullptr;
if (!GetAce(dacl, i, &raw)) {
okay = false;
break;
}
auto header = static_cast<ACE_HEADER *>(raw);
if (header->AceFlags & INHERIT_ONLY_ACE)
continue;
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) {
auto ace = static_cast<ACCESS_ALLOWED_ACE *>(raw);
if (!EqualSid(&ace->SidStart, system) &&
!EqualSid(&ace->SidStart, admins) &&
(!EqualSid(&ace->SidStart, const_cast<BYTE *>(service.data())) ||
(ace->Mask & ~(KEY_QUERY_VALUE | KEY_SET_VALUE | READ_CONTROL)))) {
okay = false;
break;
}
} else if (header->AceType != ACCESS_DENIED_ACE_TYPE) {
okay = false;
break;
}
}
if (descriptor)
LocalFree(descriptor);
return okay;
}
static int managementCredential(bool provision) {
try {
const auto root = rootPath();
if (!serviceIdentity())
return ERROR_ACCESS_DENIED;
auto release = trustedRelease(root);
std::unique_ptr<Handle> parent(parentProcess(GetCurrentProcess()));
if (!parent || !serviceIdentity(*parent) ||
!verifiedImage(GetCurrentProcess(), root, "safetynet-service-host.exe",
release) ||
!verifiedImage(*parent, root, "resources/broker/node.exe", release) ||
!sameUserSession(GetCurrentProcess(), *parent, true))
return ERROR_ACCESS_DENIED;
DWORD sidLength =
GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", nullptr, 0);
if (!sidLength || sidLength > 256)
return ERROR_ACCESS_DENIED;
std::string sid(sidLength, '\0');
if (!GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", sid.data(),
sidLength))
return ERROR_ACCESS_DENIED;
sid.resize(sidLength - 1);
credential_contract::Wipe frame;
if (!boundedStdioRead(GetStdHandle(STD_INPUT_HANDLE), frame.bytes,
provision ? 16384 : 4096))
return ERROR_ACCESS_DENIED;
std::string requestText(frame.bytes.begin(), frame.bytes.end());
credential_contract::WipeText wipeRequest{requestText};
auto request = credential_contract::parse(requestText);
SecureZeroMemory(requestText.data(), requestText.size());
struct SecretCleanup {
release_contract::Json &value;
~SecretCleanup() {
auto found = value.object.find("token");
if (found != value.object.end() && !found->second.string.empty())
SecureZeroMemory(found->second.string.data(),
found->second.string.size());
}
} secretCleanup{request};
if (provision ? !release_contract::exact(
request, {"deviceId", "origin", "policyVersion",
"policyKeySha256", "policyDigest",
"expectedPreviousSHA", "token"})
: !release_contract::exact(
request, {"deviceId", "origin", "policyVersion",
"policyKeySha256", "policyDigest"}))
return ERROR_ACCESS_DENIED;
auto service = serviceSid();
LPWSTR serviceText = nullptr;
if (service.empty() ||
!ConvertSidToStringSidW(service.data(), &serviceText))
return ERROR_ACCESS_DENIED;
std::wstring security =
L"D:P(A;;RC;;;OW)(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;" +
std::wstring(serviceText) + L")";
LocalFree(serviceText);
PSECURITY_DESCRIPTOR descriptor = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
security.c_str(), SDDL_REVISION_1, &descriptor, nullptr))
return ERROR_ACCESS_DENIED;
SECURITY_ATTRIBUTES attributes{sizeof(attributes), descriptor, FALSE};
Handle mutex(
CreateMutexW(&attributes, FALSE, L"Global\\SafetyNet.Acceptance.v1"));
LocalFree(descriptor);
if (!mutex.h || !ownedAcceptanceMutex(mutex, service) ||
WaitForSingleObject(mutex, 15000) != WAIT_OBJECT_0)
return ERROR_ACCESS_DENIED;
struct Unlock {
HANDLE h;
~Unlock() { ReleaseMutex(h); }
} unlock{mutex.h};
auto floor = trustFloor();
release = trustedRelease(root);
auto assignment = release_contract::deployment(
root, release, sid, floor.deployment, floor.deploymentDigest);
const auto metadata = credential_contract::binding(
request, assignment,
acceptance_contract::validate(floor.stateCanonical));
HKEY key = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\SafetyNet\\ManagementCredentials", 0,
KEY_QUERY_VALUE | READ_CONTROL | KEY_WOW64_64KEY |
(provision ? KEY_SET_VALUE : 0),
&key))
return ERROR_ACCESS_DENIED;
struct CloseKey {
HKEY h;
~CloseKey() { RegCloseKey(h); }
} closeKey{key};
if (!protectedCredentialKey(key, service))
return ERROR_ACCESS_DENIED;
const auto previous = credential_contract::load(key);
auto store = credential_contract::validateStore(previous);
auto &records = store.object.at("records").array;
const auto selected = credential_contract::selectRecord(metadata, records);
if (provision) {
if (!acceptance_contract::sha(release_contract::str(
request.object.at("expectedPreviousSHA"))) ||
release_contract::digest(previous) !=
release_contract::str(request.object.at("expectedPreviousSHA")))
return ERROR_ACCESS_DENIED;
auto record = metadata;
release_contract::Json cipher;
cipher.kind = release_contract::Json::String;
cipher.string = credential_contract::protect(
request.object.at("token").string, metadata);
record.object["protectedToken"] = cipher;
credential_contract::replaceRecord(records, record);
const auto candidate = release_contract::canonical(store);
credential_contract::validateStore(candidate);
if (RegSetValueExW(key, L"ManagementCredentials", 0, REG_BINARY,
reinterpret_cast<const BYTE *>(candidate.data()),
static_cast<DWORD>(candidate.size())) ||
RegFlushKey(key) || credential_contract::load(key) != candidate)
return ERROR_ACCESS_DENIED;
return boundedStdioWrite("{\"persisted\":true,\"stateSha256\":" +
release_contract::escaped(
release_contract::digest(candidate)) +
"}")
? 0
: ERROR_ACCESS_DENIED;
}
if (selected) {
const auto &record = *selected;
auto bound = record;
bound.object.erase("protectedToken");
std::string token = credential_contract::unprotect(
release_contract::str(record.object.at("protectedToken")), bound);
auto response = request;
release_contract::Json secret;
secret.kind = release_contract::Json::String;
secret.string = token;
response.object["token"] = secret;
std::string reply = release_contract::canonical(response);
SecureZeroMemory(token.data(), token.size());
SecureZeroMemory(secret.string.data(), secret.string.size());
SecureZeroMemory(response.object.at("token").string.data(),
response.object.at("token").string.size());
bool okay = reply.size() <= 8192 && boundedStdioWrite(reply);
SecureZeroMemory(reply.data(), reply.size());
return okay ? 0 : ERROR_ACCESS_DENIED;
}
return ERROR_ACCESS_DENIED;
} catch (...) {
return ERROR_ACCESS_DENIED;
}
}
static int commitAcceptance() {
try {
const auto root = rootPath();
if (!serviceIdentity())
return ERROR_ACCESS_DENIED;
auto release = trustedRelease(root);
std::unique_ptr<Handle> parent(parentProcess(GetCurrentProcess()));
if (!parent || !serviceIdentity(*parent) ||
!verifiedImage(GetCurrentProcess(), root, "safetynet-service-host.exe",
release) ||
!verifiedImage(*parent, root, "resources/broker/node.exe", release) ||
!sameUserSession(GetCurrentProcess(), *parent, true))
return ERROR_ACCESS_DENIED;
DWORD size =
GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", nullptr, 0);
if (!size || size > 256)
return ERROR_ACCESS_DENIED;
std::string sid(size, '\0');
if (!GetEnvironmentVariableA("SAFETYNET_SERVICE_CLIENT_SID", sid.data(),
size))
return ERROR_ACCESS_DENIED;
sid.resize(size - 1);
std::vector<BYTE> frameBytes;
if (!boundedStdioRead(GetStdHandle(STD_INPUT_HANDLE), frameBytes, 2097152))
return ERROR_ACCESS_DENIED;
std::string requestText(frameBytes.begin(), frameBytes.end());
release_contract::Parser parser{requestText};
auto request = parser.parse();
if (parser.offset != requestText.size() ||
!release_contract::exact(request,
{"expectedPreviousSHA", "candidateState"}) ||
!acceptance_contract::sha(
release_contract::str(request.object.at("expectedPreviousSHA"))))
return ERROR_ACCESS_DENIED;
auto candidateText =
release_contract::canonical(request.object.at("candidateState"));
auto candidate = acceptance_contract::validate(candidateText);
auto service = serviceSid();
if (service.empty())
return ERROR_ACCESS_DENIED;
LPWSTR serviceText = nullptr;
if (!ConvertSidToStringSidW(service.data(), &serviceText))
return ERROR_ACCESS_DENIED;
std::wstring security =
L"D:P(A;;RC;;;OW)(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;" +
std::wstring(serviceText) + L")";
LocalFree(serviceText);
PSECURITY_DESCRIPTOR descriptor = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
security.c_str(), SDDL_REVISION_1, &descriptor, nullptr))
return ERROR_ACCESS_DENIED;
SECURITY_ATTRIBUTES attributes{sizeof(attributes), descriptor, FALSE};
Handle mutex(
CreateMutexW(&attributes, FALSE, L"Global\\SafetyNet.Acceptance.v1"));
LocalFree(descriptor);
if (!mutex.h || !ownedAcceptanceMutex(mutex, service) ||
WaitForSingleObject(mutex, 15000) != WAIT_OBJECT_0)
return ERROR_ACCESS_DENIED;
// No commit is attempted on an abandoned mutex: a prior writer may have
// completed its value write without returning a durable receipt.
struct Unlock {
HANDLE value;
~Unlock() { ReleaseMutex(value); }
} unlock{mutex.h};
auto floor = trustFloor();
auto old = acceptance_contract::validate(floor.stateCanonical);
if (release_contract::digest(floor.stateCanonical) !=
release_contract::str(request.object.at("expectedPreviousSHA")) ||
!acceptance_contract::transition(old, candidate))
return ERROR_ACCESS_DENIED;
release = trustedRelease(root);
auto assignment = release_contract::deployment(
root, release, sid, floor.deployment, floor.deploymentDigest);
auto releaseJson = release_contract::signedPayload(
root + L"\\release-envelope.json",
release_contract::unhex(RELEASE_ANCHOR_XY),
std::string("SafetyNet Windows release v1\0",
sizeof("SafetyNet Windows release v1\0") - 1));
auto deploymentJson = release_contract::signedPayload(
root + L"\\deployment-envelope.json",
release_contract::customerKey(release.customerPem),
std::string("SafetyNet Windows deployment v1\0",
sizeof("SafetyNet Windows deployment v1\0") - 1),
262144);
if (release_contract::number(candidate.object.at("release").object.at(
"minimumSequence")) != release.sequence ||
release_contract::str(
candidate.object.at("release").object.at("acceptedDigest")) !=
release_contract::digest(
release_contract::canonical(releaseJson)) ||
release_contract::number(
candidate.object.at("deployment").object.at("minimumSequence")) !=
assignment.deploymentSequence ||
release_contract::str(
candidate.object.at("deployment").object.at("acceptedDigest")) !=
release_contract::digest(
release_contract::canonical(deploymentJson)))
return ERROR_ACCESS_DENIED;
auto key = release_contract::pemDer(assignment.policyPem);
std::string policyKeyHash =
release_contract::digest(std::string(key.begin(), key.end()));
const std::string ownId =
sid + "|" + assignment.deviceId + "|" + policyKeyHash;
std::map<std::string, const release_contract::Json *> previous;
for (const auto &record : old.object.at("policies").array)
previous.emplace(acceptance_contract::identity(record), &record);
bool hasOwn = false;
for (const auto &record : candidate.object.at("policies").array) {
auto id = acceptance_contract::identity(record);
if (id == ownId) {
hasOwn = true;
if (release_contract::number(record.object.at("minimumVersion")) <
assignment.minimumVersion)
return ERROR_ACCESS_DENIED;
} else {
auto found = previous.find(id);
if (found == previous.end() ||
release_contract::canonical(*found->second) !=
release_contract::canonical(record))
return ERROR_ACCESS_DENIED;
}
}
if (!hasOwn)
return ERROR_ACCESS_DENIED;
HKEY registry = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\SafetyNet\\Trust", 0,
KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_64KEY,
&registry) != ERROR_SUCCESS)
return ERROR_ACCESS_DENIED;
// One canonical value, flushed before acknowledgment. These APIs are NOT
// exercised by the uninstalled tests. Caller must have verified the actual
// signed policy through service-acceptance before asking to commit.
DWORD result =
RegSetValueExW(registry, L"AcceptanceState", 0, REG_BINARY,
reinterpret_cast<const BYTE *>(candidateText.data()),
DWORD(candidateText.size()));
if (result == ERROR_SUCCESS)
result = RegFlushKey(registry);
RegCloseKey(registry);
if (result != ERROR_SUCCESS)
return ERROR_ACCESS_DENIED;
auto committed = trustFloor();
if (committed.stateCanonical != candidateText)
return ERROR_ACCESS_DENIED;
return boundedStdioWrite("{\"persisted\":true,\"stateSha256\":" +
release_contract::escaped(
release_contract::digest(candidateText)) +
"}")
? 0
: ERROR_ACCESS_DENIED;
} catch (...) {
return ERROR_ACCESS_DENIED;
}
}
static bool serve(HANDLE pipe, const std::wstring &root) {
Handle clientProcess;
if (!pipeClient(pipe, root, &clientProcess.h) || !resources(root) ||
!serviceIdentity())
return false;
try {
auto release = trustedRelease(root);
auto floor = trustFloor();
release_contract::deployment(root, release, currentSid(clientProcess),
floor.deployment, floor.deploymentDigest);
} catch (...) {
return false;
}
Handle job(CreateJobObjectW(nullptr, nullptr));
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!job.h || !SetInformationJobObject(job, JobObjectExtendedLimitInformation,
&limits, sizeof(limits)))
return false;
SECURITY_ATTRIBUTES sa{sizeof(sa), nullptr, TRUE};
Handle inputRead, inputWrite, outputRead, outputWrite,
nul(CreateFileW(L"NUL", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa, OPEN_EXISTING, 0, nullptr));
if (!CreatePipe(&inputRead.h, &inputWrite.h, &sa, 0) ||
!CreatePipe(&outputRead.h, &outputWrite.h, &sa, 0) ||
nul.h == INVALID_HANDLE_VALUE)
return false;
SetHandleInformation(inputWrite, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(outputRead, HANDLE_FLAG_INHERIT, 0);
SIZE_T bytes = 0;
InitializeProcThreadAttributeList(nullptr, 1, 0, &bytes);
std::vector<BYTE> attributes(bytes);
STARTUPINFOEXW startup{};
startup.StartupInfo.cb = sizeof(startup);
startup.lpAttributeList =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(attributes.data());
if (!InitializeProcThreadAttributeList(startup.lpAttributeList, 1, 0, &bytes))
return false;
HANDLE inherited[] = {inputRead.h, outputWrite.h, nul.h};
bool configured =
UpdateProcThreadAttribute(startup.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited,
sizeof(inherited), nullptr, nullptr) != FALSE;
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
startup.StartupInfo.hStdInput = inputRead;
startup.StartupInfo.hStdOutput = outputWrite;
startup.StartupInfo.hStdError = nul;
std::wstring executable = root + L"\\resources\\broker\\node.exe",
command = L"\"" + executable + L"\" \"" + root +
L"\\resources\\broker\\apps\\network-"
L"broker\\service-entry.cjs\" --service-stdio",
env = environment(currentSid(clientProcess));
PROCESS_INFORMATION information{};
BOOL launched =
configured &&
CreateProcessW(
executable.c_str(), command.data(), nullptr, nullptr, TRUE,
CREATE_SUSPENDED | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT |
EXTENDED_STARTUPINFO_PRESENT,
env.data(), root.c_str(), &startup.StartupInfo, &information);
DeleteProcThreadAttributeList(startup.lpAttributeList);
if (!launched)
return false;
Handle process(information.hProcess), thread(information.hThread);
if (!AssignProcessToJobObject(job, process)) {
TerminateProcess(process, 1);
WaitForSingleObject(process, 5000);
return false;
}
CloseHandle(inputRead.h);
inputRead.h = nullptr;
CloseHandle(outputWrite.h);
outputWrite.h = nullptr;
if (ResumeThread(thread) == DWORD(-1)) {
TerminateJobObject(job, 1);
WaitForSingleObject(process, 5000);
return false;
}
// Native relay carries framed data only. Typed START and v1 sequence/receipt
// validation belongs to the reviewed constant Node service-stdio adapter.
Handle serveThread;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &serveThread.h, THREAD_TERMINATE,
FALSE, 0)) {
TerminateJobObject(job, 1);
return false;
}
std::atomic<ULONGLONG> writeDeadline{0}, readDeadline{0};
std::atomic<bool> good{true};
std::thread watchdog([&] {
while (good) {
if (WaitForSingleObject(stopEvent, 50) == WAIT_OBJECT_0 ||
WaitForSingleObject(clientProcess, 0) != WAIT_TIMEOUT ||
(writeDeadline.load() != 0 &&
GetTickCount64() >= writeDeadline.load()) ||
(readDeadline.load() != 0 &&
GetTickCount64() >= readDeadline.load())) {
good = false;
TerminateJobObject(job, 1);
CancelSynchronousIo(serveThread);
CancelIoEx(pipe, nullptr);
break;
}
}
});
std::thread outgoing([&] {
while (good) {
DWORD size = 0, done = 0;
if (!ReadFile(outputRead, &size, 4, &done, nullptr) || !done) {
good = false;
break;
}
readDeadline = GetTickCount64() + 15000;
DWORD headerOffset = done;
while (headerOffset < 4) {
if (!ReadFile(outputRead,
reinterpret_cast<BYTE *>(&size) + headerOffset,
4 - headerOffset, &done, nullptr) ||
!done) {
good = false;
break;
}
headerOffset += done;
}
if (!good || !size || size > 65536) {
good = false;
break;
}
std::vector<BYTE> value(size);
DWORD offset = 0;
while (offset < size) {
if (!ReadFile(outputRead, value.data() + offset, size - offset, &done,
nullptr) ||
!done) {
good = false;
break;
}
offset += done;
}
if (!good || !frame(pipe, true, value, 65536)) {
good = false;
break;
}
readDeadline = 0;
}
CancelIoEx(pipe, nullptr);
});
bool first = true;
while (good && WaitForSingleObject(stopEvent, 0) == WAIT_TIMEOUT) {
std::vector<BYTE> value;
if (!frame(pipe, false, value, first ? 2097152 : 65536,
first ? 15000 : INFINITE))
break;
first = false;
if (!writeChildFrame(inputWrite, value, writeDeadline))
break;
}
good = false;
TerminateJobObject(job, 1);
CancelSynchronousIo(outgoing.native_handle());
CancelIoEx(pipe, nullptr);
outgoing.join();
watchdog.join();
return WaitForSingleObject(process, 5000) == WAIT_OBJECT_0;
}
static void report(DWORD state, DWORD error = 0) {
SERVICE_STATUS status{};
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
status.dwCurrentState = state;
status.dwWin32ExitCode = error;
status.dwControlsAccepted =
state == SERVICE_RUNNING ? SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN
: 0;
status.dwWaitHint = state == SERVICE_STOP_PENDING ? 5000 : 0;
SetServiceStatus(statusHandle, &status);
}
static DWORD WINAPI control(DWORD code, DWORD, void *, void *) {
if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
report(SERVICE_STOP_PENDING);
SetEvent(stopEvent);
}
return NO_ERROR;
}
static void WINAPI serviceMain(DWORD, wchar_t **) {
statusHandle = RegisterServiceCtrlHandlerExW(SERVICE_NAME, control, nullptr);
if (!statusHandle)
return;
report(SERVICE_START_PENDING);
Handle stop(CreateEventW(nullptr, TRUE, FALSE, nullptr));
stopEvent = stop;
std::wstring root = rootPath();
if (!stop.h || !serviceIdentity() || !resources(root)) {
report(SERVICE_STOPPED, ERROR_ACCESS_DENIED);
return;
}
PSECURITY_DESCRIPTOR security = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
L"D:P(A;;GA;;;SY)(A;;0x0012019b;;;AU)", SDDL_REVISION_1, &security,
nullptr)) {
report(SERVICE_STOPPED, ERROR_ACCESS_DENIED);
return;
}
SECURITY_ATTRIBUTES sa{sizeof(sa), security, FALSE};
Handle pipe(CreateNamedPipeW(PIPE_NAME,
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT |
PIPE_REJECT_REMOTE_CLIENTS,
1, 65536, 65536, 15000, &sa));
LocalFree(security);
if (pipe.h == INVALID_HANDLE_VALUE) {
report(SERVICE_STOPPED, ERROR_ACCESS_DENIED);
return;
}
report(SERVICE_RUNNING);
while (WaitForSingleObject(stopEvent, 0) == WAIT_TIMEOUT) {
Handle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
OVERLAPPED ov{};
ov.hEvent = event;
BOOL connected = ConnectNamedPipe(pipe, &ov);
DWORD code = connected ? ERROR_SUCCESS : GetLastError();
if (code == ERROR_IO_PENDING) {
HANDLE events[] = {event.h, stopEvent};
if (WaitForMultipleObjects(2, events, FALSE, INFINITE) != WAIT_OBJECT_0) {
CancelIoEx(pipe, &ov);
WaitForSingleObject(event, INFINITE);
break;
}
DWORD bytes = 0;
connected = GetOverlappedResult(pipe, &ov, &bytes, FALSE);
} else
connected = code == ERROR_PIPE_CONNECTED;
if (connected)
serve(pipe, root);
DisconnectNamedPipe(pipe);
}
report(SERVICE_STOPPED);
}
#ifdef SAFETYNET_NATIVE_SELF_TEST
static bool ownerRightsTest() {
auto user = currentSid(GetCurrentProcess());
std::wstring sid(user.begin(), user.end());
std::wstring security = L"D:P(A;;0x00120001;;;" + sid + L")(A;;RC;;;OW)";
PSECURITY_DESCRIPTOR descriptor = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
security.c_str(), SDDL_REVISION_1, &descriptor, nullptr))
return false;
SECURITY_ATTRIBUTES attributes{sizeof(attributes), descriptor, FALSE};
auto name = L"Local\\SafetyNet.OwnerRights.Proof." +
std::to_wstring(GetCurrentProcessId());
Handle mutex(CreateMutexW(&attributes, FALSE, name.c_str()));
LocalFree(descriptor);
if (!mutex.h)
return false;
Handle denied(OpenMutexW(WRITE_DAC, FALSE, name.c_str()));
DWORD deniedCode = GetLastError();
if (denied.h || deniedCode != ERROR_ACCESS_DENIED)
return false;
Handle allowed(OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE | READ_CONTROL,
FALSE, name.c_str()));
if (!allowed.h)
return false;
PSECURITY_DESCRIPTOR actual = nullptr;
PSID owner = nullptr;
PACL dacl = nullptr;
if (GetSecurityInfo(allowed, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&owner, nullptr, &dacl, nullptr,
&actual) != ERROR_SUCCESS)
return false;
PSID expected = nullptr;
ConvertStringSidToSidA(user.c_str(), &expected);
PSID ownerRights = nullptr;
ConvertStringSidToSidW(L"S-1-3-4", &ownerRights);
bool valid = owner && expected && EqualSid(owner, expected);
unsigned count = 0;
if (valid && dacl)
for (DWORD i = 0; i < dacl->AceCount; i++) {
void *raw = nullptr;
if (!GetAce(dacl, i, &raw)) {
valid = false;
break;
}
auto header = static_cast<ACE_HEADER *>(raw);
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) {
auto ace = static_cast<ACCESS_ALLOWED_ACE *>(raw);
if (EqualSid(&ace->SidStart, ownerRights)) {
count++;
if (ace->Mask != READ_CONTROL)
valid = false;
}
}
}
if (expected)
LocalFree(expected);
if (ownerRights)
LocalFree(ownerRights);
if (actual)
LocalFree(actual);
return valid && count == 1;
}
static bool writeDeadlineTest() {
Handle read, write;
if (!CreatePipe(&read.h, &write.h, nullptr, 4096))
return false;
std::atomic<ULONGLONG> deadline{0};
std::vector<BYTE> samplePayload(16, 42);
if (!writeChildFrame(write, samplePayload, deadline, 30) ||
deadline.load() != 0)
return false;
DWORD done = 0;
std::array<BYTE, 20> consumed{};
if (!ReadFile(read, consumed.data(), 20, &done, nullptr) || done != 20)
return false;
Sleep(60);
if (deadline.load() != 0)
return false; // healthy idle exceeds test budget
std::atomic<bool> result{true};
std::vector<BYTE> stalledPayload(262144, 43);
std::thread blocked(
[&] { result = writeChildFrame(write, stalledPayload, deadline, 30); });
for (int i = 0; i < 100 && deadline.load() == 0; i++)
Sleep(1);
Sleep(60);
if (deadline.load() == 0) {
CancelSynchronousIo(blocked.native_handle());
blocked.join();
return false;
}
if (!CancelSynchronousIo(blocked.native_handle())) {
CloseHandle(read.h);
read.h = nullptr;
}
blocked.join();
return !result && deadline.load() != 0;
}
static bool pipeNegativeTest() {
std::wstring name = L"\\\\.\\pipe\\SafetyNet.NativeTest." +
std::to_wstring(GetCurrentProcessId());
PSECURITY_DESCRIPTOR security = nullptr;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
L"D:P(A;;GA;;;SY)(A;;0x0012019b;;;AU)", SDDL_REVISION_1, &security,
nullptr))
return false;
SECURITY_ATTRIBUTES sa{sizeof(sa), security, FALSE};
Handle server(CreateNamedPipeW(name.c_str(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE |
PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
2, 4096, 4096, 1000, &sa));
LocalFree(security);
if (server.h == INVALID_HANDLE_VALUE)
return false;
// Attempt a second instance WITHOUT FIRST_PIPE_INSTANCE: DACL rather than
// creation-flag coincidence must deny FILE_CREATE_PIPE_INSTANCE to this user.
Handle attacker(CreateNamedPipeW(name.c_str(), PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_WAIT, 2, 4096, 4096,
1000, nullptr));
if (attacker.h != INVALID_HANDLE_VALUE ||
GetLastError() != ERROR_ACCESS_DENIED)
return false;
Handle client(CreateFileW(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0,
nullptr, OPEN_EXISTING, 0, nullptr));
if (client.h == INVALID_HANDLE_VALUE)
return false;
ULONG actualServerPid = 0;
if (!GetNamedPipeServerProcessId(client, &actualServerPid) ||
actualServerPid != GetCurrentProcessId())
return false;
Handle actualServer(OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, actualServerPid));
if (!actualServer.h || serviceIdentity(actualServer))
return false;
Handle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
OVERLAPPED ov{};
ov.hEvent = event;
BOOL connected = ConnectNamedPipe(server, &ov);
if (!connected && GetLastError() != ERROR_PIPE_CONNECTED)
return false;
if (pipeClient(server, rootPath()))
return false;
DWORD invalid = 0xffffffff, done = 0;
if (!WriteFile(client, &invalid, 4, &done, nullptr) || done != 4)
return false;
std::vector<BYTE> frameBytes;
return !frame(server, false, frameBytes, 65536, 1000);
}
static int selfTest() {
Handle stop(CreateEventW(nullptr, TRUE, FALSE, nullptr));
stopEvent = stop;
if (!ownerRightsTest())
return 84;
if (!pipeNegativeTest())
return 89;
if (!writeDeadlineTest())
return 88;
if (serviceIdentity())
return 90;
Handle job(CreateJobObjectW(nullptr, nullptr));
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limits,
sizeof(limits)))
return 91;
auto root = rootPath();
std::wstring command =
L"\"" + root + L"\\safetynet-service-host-test.exe\" --job-child";
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
PROCESS_INFORMATION information{};
if (!CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE,
CREATE_SUSPENDED | CREATE_NO_WINDOW, nullptr,
root.c_str(), &startup, &information))
return 92;
Handle process(information.hProcess), thread(information.hThread);
if (!AssignProcessToJobObject(job, process))
return 93;
BOOL member = FALSE;
if (!IsProcessInJob(process, job, &member) || !member)
return 94;
ResumeThread(thread);
Handle descendant;
for (int attempt = 0; attempt < 100 && !descendant.h; attempt++) {
Handle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
PROCESSENTRY32W item{};
item.dwSize = sizeof(item);
if (Process32FirstW(snapshot, &item))
do {
if (item.th32ParentProcessID == information.dwProcessId) {
descendant.h =
OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, item.th32ProcessID);
break;
}
} while (Process32NextW(snapshot, &item));
if (!descendant.h)
Sleep(20);
}
if (!descendant.h || !IsProcessInJob(descendant, job, &member) || !member)
return 98;
if (WaitForSingleObject(process, 50) != WAIT_TIMEOUT)
return 95;
CloseHandle(job.h);
job.h = nullptr;
if (WaitForSingleObject(process, 5000) != WAIT_OBJECT_0 ||
WaitForSingleObject(descendant, 5000) != WAIT_OBJECT_0)
return 96;
if (hashFile(
root + L"\\safetynet-service-host-test.exe",
"0000000000000000000000000000000000000000000000000000000000000000"))
return 97;
puts("{\"ownerWriteDacDenied\":true,\"ownerReadSyncModifyAllowed\":true,"
"\"healthyIdleWriteDeadlineCleared\":true,\"stalledWriteCancelled\":"
"true,\"sameConnectedHandleServerIdentified\":true,"
"\"wrongServerServiceIdentityDenied\":true,\"pipeSecondInstanceDenied\":"
"true,\"unauthorizedClientDenied\":true,\"oversizedFrameDenied\":true,"
"\"serviceIdentityRejected\":true,\"jobMembership\":true,"
"\"descendantJobMembership\":true,\"killOnCloseTerminal\":true,"
"\"wrongHashRejected\":true}");
return 0;
}
#endif
int wmain(int argc, wchar_t **argv) {
#ifdef SAFETYNET_NATIVE_SELF_TEST
if (argc == 2 && !wcscmp(argv[1], L"--verify-credential-selection"))
return credential_contract::selectionSelfTest() ? 0 : 86;
if (argc == 2 && !wcscmp(argv[1], L"--verify-state-fixture")) {
try {
auto old = acceptance_contract::validate(
release_contract::read(rootPath() + L"\\acceptance-old.json"));
auto next = acceptance_contract::validate(
release_contract::read(rootPath() + L"\\acceptance-candidate.json"));
return acceptance_contract::transition(old, next) ? 0 : 86;
} catch (...) {
return 86;
}
}
if (argc == 2 && !wcscmp(argv[1], L"--verify-equivocation")) {
try {
release_contract::release(rootPath(),
release_contract::unhex(RELEASE_ANCHOR_XY), 2,
std::string(64, '0'));
return 85;
} catch (...) {
return 86;
}
}
if (argc == 2 && !wcscmp(argv[1], L"--verify-fixture")) {
try {
auto manifest = release_contract::release(
rootPath(), release_contract::unhex(RELEASE_ANCHOR_XY), 1);
auto assignment = release_contract::deployment(rootPath(), manifest,
"S-1-5-21-1-2-3-1001", 1);
if (assignment.minimumVersion != 3 ||
!hashFile(
rootPath() + L"\\safetynet-service-host-test.exe",
manifest.hashes.at("safetynet-service-host-test.exe").c_str()))
return 87;
puts("{\"nativeVendorSignatureVerified\":true,"
"\"nativeDelegatedCustomerSignatureVerified\":true,"
"\"nativeSidAssignmentVerified\":true}");
return 0;
} catch (...) {
return 86;
}
}
if (argc == 2 && !wcscmp(argv[1], L"--self-test"))
return selfTest();
if (argc == 2 && !wcscmp(argv[1], L"--job-grandchild")) {
Sleep(INFINITE);
return 0;
}
if (argc == 2 && !wcscmp(argv[1], L"--job-child")) {
std::wstring command =
L"\"" + rootPath() +
L"\\safetynet-service-host-test.exe\" --job-grandchild";
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
PROCESS_INFORMATION info{};
if (!CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE,
CREATE_NO_WINDOW, nullptr, nullptr, &startup, &info))
return 99;
CloseHandle(info.hProcess);
CloseHandle(info.hThread);
Sleep(INFINITE);
return 0;
}
#endif
if (argc == 2 && !wcscmp(argv[1], L"--client-stdio"))
return clientStdio();
if (argc == 2 && !wcscmp(argv[1], L"--trust-snapshot"))
return trustSnapshot();
if (argc == 2 && !wcscmp(argv[1], L"--topology-snapshot"))
return topologySnapshot();
if (argc == 2 && !wcscmp(argv[1], L"--commit-acceptance"))
return commitAcceptance();
if (argc == 2 && !wcscmp(argv[1], L"--management-credential"))
return managementCredential(false);
if (argc == 2 && !wcscmp(argv[1], L"--provision-management-credential"))
return managementCredential(true);
if (argc != 1)
return ERROR_INVALID_PARAMETER;
SERVICE_TABLE_ENTRYW table[] = {
{const_cast<wchar_t *>(SERVICE_NAME), serviceMain}, {nullptr, nullptr}};
return StartServiceCtrlDispatcherW(table) ? 0 : int(GetLastError());
}