mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-09 09:11:00 +08:00
fix(install): trust the local machine's built-in Administrator; name untrusted owners (#1705, #2023, #1686)
Windows install/daemon runtime directories created by an elevated process are owned by the built-in Administrator ACCOUNT (RID 500 under the local machine account-domain SID), even when that account is renamed or disabled. cbm's directory-owner/ACL trust refused it, breaking install/activation on such machines. #1705: resolve THIS machine's built-in Administrator SID via LSA (LsaOpenPolicy(POLICY_VIEW_LOCAL_INFORMATION) + LsaQueryInformationPolicy(PolicyAccountDomainInformation) -> CreateWellKnownSid(WinAccountAdministratorSid, accountDomainSid)), cached once per process, and accept it as a trusted owner/grantee in the daemon (win_sid_trusted) and the CLI activation transaction. Resolution is deliberately NOT IsWellKnownSid(WinAccountAdministratorSid) and NOT a trailing-RID-500 match: both accept a FOREIGN S-1-5-21-*-500 (a domain administrator, or another machine's built-in Administrator), which would be a cross-machine trust escalation. Any LSA or synthesis failure caches "none" and grants no tolerance (fail closed). owner-not-current-user (the exact-owner target rule) is unchanged. #2023/#1686: an owner-trust refusal now names the offending owner SID (the path was already carried), so a terse "status -3, os 0" becomes actionable. Scope notes: on a domain controller -- which has no separate local SAM, so its account domain IS the AD domain -- this trusts the domain Administrator, by design (on a DC that account is the machine-equivalent top admin). An un-sysprepped cloned Windows image shares the machine account-domain SID, so a sibling clone's built-in Administrator would be non-foreign; this is inherent to machine-SID identity. Verified on a native Windows (arm64) VM: compiles under the sanitized build; the daemon_ipc SID-trust test passes (local RID-500 trusted, a fabricated foreign S-1-5-21-*-500 rejected, BUILTIN\Administrators trusted) at 35 passed / 0 failed with the fix, and 34 passed / 1 failed (ASSERT(local_ok)) with the trust clause reverted -- the test binds precisely to the fix. #1856 (stock ancestor ACEs): the Windows ancestor-ACL guards already pass; no defect reproduced, no code change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
cdff08f313
commit
df579830f3
@@ -18,6 +18,7 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <aclapi.h>
|
||||
#include <ntsecapi.h>
|
||||
#include <sddl.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
@@ -424,6 +425,101 @@ static void activation_windows_security_destroy(activation_windows_security_t *s
|
||||
memset(security, 0, sizeof(*security));
|
||||
}
|
||||
|
||||
/* #1705: THIS machine's built-in Administrator ACCOUNT (RID 500 under the local
|
||||
* machine account-domain SID, S-1-5-21-<machine>-500) is a trusted owner/grantee,
|
||||
* mirroring the daemon's win_sid_trusted (src/daemon/ipc.c). It is resolved via
|
||||
* LSA (the local machine account-domain SID) plus CreateWellKnownSid and compared
|
||||
* with EqualSid. It is deliberately NOT IsWellKnownSid(WinAccountAdministratorSid)
|
||||
* and NOT a trailing-RID-500 test: both accept a FOREIGN S-1-5-21-*-500 (a domain
|
||||
* admin, or another machine's built-in Administrator), opening a cross-machine
|
||||
* bypass. Resolved once and cached for the process; any LSA or synthesis failure
|
||||
* caches "none" and grants NO tolerance (fail closed). advapi32 is already loaded
|
||||
* (this file calls GetSecurityInfo etc.), so the functions are resolved from its
|
||||
* module handle with no new import. CLI activation is single-threaded by
|
||||
* contract, so the cache needs no lock. */
|
||||
typedef NTSTATUS(NTAPI *activation_lsa_open_policy_fn)(PLSA_UNICODE_STRING, PLSA_OBJECT_ATTRIBUTES,
|
||||
ACCESS_MASK, PLSA_HANDLE);
|
||||
typedef NTSTATUS(NTAPI *activation_lsa_query_information_policy_fn)(LSA_HANDLE,
|
||||
POLICY_INFORMATION_CLASS,
|
||||
PVOID *);
|
||||
typedef NTSTATUS(NTAPI *activation_lsa_free_memory_fn)(PVOID);
|
||||
typedef NTSTATUS(NTAPI *activation_lsa_close_fn)(LSA_HANDLE);
|
||||
typedef BOOL(WINAPI *activation_create_well_known_sid_fn)(WELL_KNOWN_SID_TYPE, PSID, PSID, DWORD *);
|
||||
|
||||
static PSID activation_windows_local_admin_sid(void) {
|
||||
static bool resolved = false;
|
||||
static PSID cached = NULL;
|
||||
if (resolved) {
|
||||
return cached;
|
||||
}
|
||||
resolved = true;
|
||||
HMODULE advapi = GetModuleHandleW(L"advapi32.dll");
|
||||
if (!advapi) {
|
||||
return NULL;
|
||||
}
|
||||
activation_lsa_open_policy_fn lsa_open =
|
||||
(activation_lsa_open_policy_fn)(void (*)(void))GetProcAddress(advapi, "LsaOpenPolicy");
|
||||
activation_lsa_query_information_policy_fn lsa_query =
|
||||
(activation_lsa_query_information_policy_fn)(void (*)(void))GetProcAddress(
|
||||
advapi, "LsaQueryInformationPolicy");
|
||||
activation_lsa_free_memory_fn lsa_free =
|
||||
(activation_lsa_free_memory_fn)(void (*)(void))GetProcAddress(advapi, "LsaFreeMemory");
|
||||
activation_lsa_close_fn lsa_close =
|
||||
(activation_lsa_close_fn)(void (*)(void))GetProcAddress(advapi, "LsaClose");
|
||||
activation_create_well_known_sid_fn create_sid =
|
||||
(activation_create_well_known_sid_fn)(void (*)(void))GetProcAddress(advapi,
|
||||
"CreateWellKnownSid");
|
||||
if (!lsa_open || !lsa_query || !lsa_free || !lsa_close || !create_sid) {
|
||||
return NULL;
|
||||
}
|
||||
LSA_OBJECT_ATTRIBUTES attributes;
|
||||
memset(&attributes, 0, sizeof(attributes));
|
||||
LSA_HANDLE policy = NULL;
|
||||
/* STATUS_SUCCESS is 0; any other status is treated as failure (fail closed). */
|
||||
if (lsa_open(NULL, &attributes, POLICY_VIEW_LOCAL_INFORMATION, &policy) != 0 || !policy) {
|
||||
return NULL;
|
||||
}
|
||||
POLICY_ACCOUNT_DOMAIN_INFO *domain = NULL;
|
||||
if (lsa_query(policy, PolicyAccountDomainInformation, (PVOID *)&domain) == 0 && domain &&
|
||||
domain->DomainSid && IsValidSid(domain->DomainSid)) {
|
||||
DWORD needed = 0;
|
||||
(void)create_sid(WinAccountAdministratorSid, domain->DomainSid, NULL, &needed);
|
||||
if (needed > 0U) {
|
||||
PSID admin = malloc(needed);
|
||||
if (admin && create_sid(WinAccountAdministratorSid, domain->DomainSid, admin, &needed) &&
|
||||
IsValidSid(admin)) {
|
||||
cached = admin;
|
||||
} else {
|
||||
free(admin);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (domain) {
|
||||
(void)lsa_free(domain);
|
||||
}
|
||||
(void)lsa_close(policy);
|
||||
return cached;
|
||||
}
|
||||
|
||||
/* #2023/#1686: an owner refusal must name WHICH owner, not just "status -3, os 0".
|
||||
* The path is already carried by g_activation_refusal_object; this appends the
|
||||
* offending owner's SID string, mirroring the daemon's ACL/owner diagnostics, so
|
||||
* the operator sees the exact identity to remove or the directory to move. */
|
||||
static void activation_windows_note_untrusted_owner(const char *predicate, PSID owner,
|
||||
DWORD os_error) {
|
||||
char label[192];
|
||||
LPSTR owner_text = NULL;
|
||||
(void)snprintf(
|
||||
label, sizeof(label), "%s; owner=%s", predicate,
|
||||
(owner && IsValidSid(owner) && ConvertSidToStringSidA(owner, &owner_text) && owner_text)
|
||||
? owner_text
|
||||
: "unresolved-sid");
|
||||
if (owner_text) {
|
||||
(void)LocalFree(owner_text);
|
||||
}
|
||||
activation_note_refusal(label, os_error);
|
||||
}
|
||||
|
||||
/* Trusted-owner acceptance for SOURCE-side objects: a downloaded release
|
||||
* bundle is owned by whatever the machine's default-owner policy dictates
|
||||
* (Administrators on GitHub-runner-class images). Its integrity is enforced
|
||||
@@ -440,11 +536,13 @@ static bool activation_windows_owner_is_trusted(HANDLE handle) {
|
||||
PSECURITY_DESCRIPTOR descriptor = NULL;
|
||||
DWORD result = GetSecurityInfo(handle, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &owner, NULL,
|
||||
NULL, NULL, &descriptor);
|
||||
PSID local_admin = activation_windows_local_admin_sid();
|
||||
bool trusted = result == ERROR_SUCCESS && owner && IsValidSid(owner) &&
|
||||
(EqualSid(owner, user_sid) || IsWellKnownSid(owner, WinLocalSystemSid) ||
|
||||
IsWellKnownSid(owner, WinBuiltinAdministratorsSid));
|
||||
IsWellKnownSid(owner, WinBuiltinAdministratorsSid) ||
|
||||
(local_admin && EqualSid(owner, local_admin)));
|
||||
if (!trusted) {
|
||||
activation_note_refusal("owner-not-trusted", result);
|
||||
activation_windows_note_untrusted_owner("owner-not-trusted", owner, result);
|
||||
}
|
||||
if (descriptor) {
|
||||
(void)LocalFree(descriptor);
|
||||
@@ -522,7 +620,7 @@ static bool activation_windows_owner_is_current(HANDLE handle) {
|
||||
}
|
||||
}
|
||||
if (!same) {
|
||||
activation_note_refusal("owner-not-current-user", result);
|
||||
activation_windows_note_untrusted_owner("owner-not-current-user", owner, result);
|
||||
}
|
||||
if (descriptor) {
|
||||
(void)LocalFree(descriptor);
|
||||
@@ -596,10 +694,15 @@ static bool activation_windows_acl_check(HANDLE handle, DWORD tolerated_untruste
|
||||
PSID sid = (PSID)&ace->SidStart;
|
||||
size_t sid_capacity = (size_t)header->AceSize - sid_offset;
|
||||
DWORD sid_length = GetSidLengthRequired(((SID *)sid)->SubAuthorityCount);
|
||||
PSID local_admin = activation_windows_local_admin_sid();
|
||||
bool trusted = sid_length <= sid_capacity && IsValidSid(sid) &&
|
||||
GetLengthSid(sid) == sid_length &&
|
||||
(EqualSid(sid, user_sid) || IsWellKnownSid(sid, WinLocalSystemSid) ||
|
||||
IsWellKnownSid(sid, WinBuiltinAdministratorsSid) ||
|
||||
/* #1705: THIS machine's built-in Administrator (RID-500),
|
||||
* resolved via LSA — never a foreign S-1-5-21-*-500; same
|
||||
* tolerance as the daemon's win_sid_trusted. */
|
||||
(local_admin && EqualSid(sid, local_admin)) ||
|
||||
/* OWNER RIGHTS modulates whoever owns the object; the
|
||||
* owner is separately validated in every chain that
|
||||
* reaches here (same tolerance as the daemon IPC and
|
||||
|
||||
Reference in New Issue
Block a user