diff options
Diffstat (limited to 'winsup/cygwin/sec/helper.cc')
-rw-r--r-- | winsup/cygwin/sec/helper.cc | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/winsup/cygwin/sec/helper.cc b/winsup/cygwin/sec/helper.cc new file mode 100644 index 000000000..750631b2b --- /dev/null +++ b/winsup/cygwin/sec/helper.cc @@ -0,0 +1,883 @@ +/* sec/helper.cc: NT security helper functions + + Written by Corinna Vinschen <corinna@vinschen.de> + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include <stdlib.h> +#include <stdarg.h> +#include <cygwin/acl.h> +#include <sys/queue.h> +#include <authz.h> +#include <wchar.h> +#include "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "pinfo.h" +#include "cygheap.h" +#include "ntdll.h" +#include "ldap.h" + +/* General purpose security attribute objects for global use. */ +static NO_COPY_RO SECURITY_DESCRIPTOR null_sdp = + { SECURITY_DESCRIPTOR_REVISION, 0, SE_DACL_PRESENT, + NULL, NULL, NULL, NULL }; +SECURITY_ATTRIBUTES NO_COPY_RO sec_none = + { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE }; +SECURITY_ATTRIBUTES NO_COPY_RO sec_none_nih = + { sizeof (SECURITY_ATTRIBUTES), NULL, FALSE }; +SECURITY_ATTRIBUTES NO_COPY_RO sec_all = + { sizeof (SECURITY_ATTRIBUTES), &null_sdp, TRUE }; +SECURITY_ATTRIBUTES NO_COPY_RO sec_all_nih = + { sizeof (SECURITY_ATTRIBUTES), &null_sdp, FALSE }; + +MKSID (well_known_null_sid, "S-1-0-0", + SECURITY_NULL_SID_AUTHORITY, 1, SECURITY_NULL_RID); +MKSID (well_known_world_sid, "S-1-1-0", + SECURITY_WORLD_SID_AUTHORITY, 1, SECURITY_WORLD_RID); +MKSID (well_known_local_sid, "S-1-2-0", + SECURITY_LOCAL_SID_AUTHORITY, 1, SECURITY_LOCAL_RID); +MKSID (well_known_console_logon_sid, "S-1-2-1", + SECURITY_LOCAL_SID_AUTHORITY, 1, 1); +MKSID (well_known_creator_owner_sid, "S-1-3-0", + SECURITY_CREATOR_SID_AUTHORITY, 1, SECURITY_CREATOR_OWNER_RID); +MKSID (well_known_creator_group_sid, "S-1-3-1", + SECURITY_CREATOR_SID_AUTHORITY, 1, SECURITY_CREATOR_GROUP_RID); +MKSID (well_known_dialup_sid, "S-1-5-1", + SECURITY_NT_AUTHORITY, 1, SECURITY_DIALUP_RID); +MKSID (well_known_network_sid, "S-1-5-2", + SECURITY_NT_AUTHORITY, 1, SECURITY_NETWORK_RID); +MKSID (well_known_batch_sid, "S-1-5-3", + SECURITY_NT_AUTHORITY, 1, SECURITY_BATCH_RID); +MKSID (well_known_interactive_sid, "S-1-5-4", + SECURITY_NT_AUTHORITY, 1, SECURITY_INTERACTIVE_RID); +MKSID (well_known_service_sid, "S-1-5-6", + SECURITY_NT_AUTHORITY, 1, SECURITY_SERVICE_RID); +MKSID (well_known_authenticated_users_sid, "S-1-5-11", + SECURITY_NT_AUTHORITY, 1, SECURITY_AUTHENTICATED_USER_RID); +MKSID (well_known_this_org_sid, "S-1-5-15", + SECURITY_NT_AUTHORITY, 1, 15); +MKSID (well_known_system_sid, "S-1-5-18", + SECURITY_NT_AUTHORITY, 1, SECURITY_LOCAL_SYSTEM_RID); +MKSID (well_known_local_service_sid, "S-1-5-19", + SECURITY_NT_AUTHORITY, 1, SECURITY_LOCAL_SERVICE_RID); +MKSID (well_known_network_service_sid, "S-1-5-20", + SECURITY_NT_AUTHORITY, 1, SECURITY_NETWORK_SERVICE_RID); +MKSID (well_known_builtin_sid, "S-1-5-32", + SECURITY_NT_AUTHORITY, 1, SECURITY_BUILTIN_DOMAIN_RID); +MKSID (well_known_admins_sid, "S-1-5-32-544", + SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS); +MKSID (well_known_users_sid, "S-1-5-32-545", + SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_USERS); +MKSID (trusted_installer_sid, + "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464", + SECURITY_NT_AUTHORITY, SECURITY_SERVICE_ID_RID_COUNT, + SECURITY_SERVICE_ID_BASE_RID, 956008885U, 3418522649U, 1831038044U, + 1853292631U, 2271478464U); +MKSID (mandatory_medium_integrity_sid, "S-1-16-8192", + SECURITY_MANDATORY_LABEL_AUTHORITY, 1, SECURITY_MANDATORY_MEDIUM_RID); +MKSID (mandatory_high_integrity_sid, "S-1-16-12288", + SECURITY_MANDATORY_LABEL_AUTHORITY, 1, SECURITY_MANDATORY_HIGH_RID); +MKSID (mandatory_system_integrity_sid, "S-1-16-16384", + SECURITY_MANDATORY_LABEL_AUTHORITY, 1, SECURITY_MANDATORY_SYSTEM_RID); +/* UNIX accounts on a Samba server have the SID prefix "S-1-22-1" */ +#define SECURITY_SAMBA_UNIX_AUTHORITY {0,0,0,0,0,22} +MKSID (well_known_samba_unix_user_fake_sid, "S-1-22-1-0", + SECURITY_SAMBA_UNIX_AUTHORITY, 2, 1, 0); + +bool +cygpsid::operator== (const char *nsidstr) const +{ + cygsid nsid (nsidstr); + return psid == nsid; +} + +uid_t +cygpsid::get_id (BOOL search_grp, int *type, cyg_ldap *pldap) +{ + /* First try to get SID from group, then passwd */ + uid_t id = ILLEGAL_UID; + + if (search_grp) + { + struct group *gr; + if (cygheap->user.groups.pgsid == psid) + id = myself->gid; + else if (sid_id_auth (psid) == 22 && cygheap->pg.nss_grp_db ()) + { + /* Samba UNIX group? Try to map to Cygwin gid. If there's no + mapping in the cache, try to fetch it from the configured + RFC 2307 domain (see last comment in cygheap_domain_info::init() + for more information) and add it to the mapping cache. + If this is a user, not a group, make sure to skip the subsequent + internal_getgrsid call, otherwise we end up with a fake group + entry for a UNIX user account. */ + if (sid_sub_auth (psid, 0) == 2) + { + gid_t gid = sid_sub_auth_rid (psid); + gid_t map_gid = cygheap->ugid_cache.get_gid (gid); + if (map_gid == ILLEGAL_GID) + { + if (pldap->open (cygheap->dom.get_rfc2307_domain ()) + == NO_ERROR) + map_gid = pldap->remap_gid (gid); + if (map_gid == ILLEGAL_GID) + map_gid = MAP_UNIX_TO_CYGWIN_ID (gid); + cygheap->ugid_cache.add_gid (gid, map_gid); + } + id = (uid_t) map_gid; + } + } + else if ((gr = internal_getgrsid (*this, pldap))) + id = gr->gr_gid; + if ((gid_t) id != ILLEGAL_GID) + { + if (type) + *type = GROUP; + return id; + } + } + if (!search_grp || type) + { + struct passwd *pw; + if (*this == cygheap->user.sid ()) + id = myself->uid; + else if (sid_id_auth (psid) == 22 && sid_sub_auth (psid, 0) == 1 + && cygheap->pg.nss_pwd_db ()) + { + /* Samba UNIX user. See comment above. */ + uid_t uid = sid_sub_auth_rid (psid); + uid_t map_uid = cygheap->ugid_cache.get_uid (uid); + if (map_uid == ILLEGAL_UID) + { + if (pldap->open (cygheap->dom.get_rfc2307_domain ()) == NO_ERROR) + map_uid = pldap->remap_uid (uid); + if (map_uid == ILLEGAL_UID) + map_uid = MAP_UNIX_TO_CYGWIN_ID (uid); + cygheap->ugid_cache.add_uid (uid, map_uid); + } + id = map_uid; + } + else if ((pw = internal_getpwsid (*this, pldap))) + id = pw->pw_uid; + if (id != ILLEGAL_UID) + { + if (type) + *type = USER; + return id; + } + } + if (type) + *type = 0; /* undefined type */ + return ILLEGAL_UID; +} + +PWCHAR +cygpsid::pstring (PWCHAR nsidstr) const +{ + UNICODE_STRING sid; + + if (!psid || !nsidstr) + return NULL; + RtlInitEmptyUnicodeString (&sid, nsidstr, 256); + RtlConvertSidToUnicodeString (&sid, psid, FALSE); + return nsidstr + sid.Length / sizeof (WCHAR); +} + +PWCHAR +cygpsid::string (PWCHAR nsidstr) const +{ + if (pstring (nsidstr)) + return nsidstr; + return NULL; +} + +char * +cygpsid::pstring (char *nsidstr) const +{ + char *t; + DWORD i; + + if (!psid || !nsidstr) + return NULL; + strcpy (nsidstr, "S-1-"); + t = nsidstr + sizeof ("S-1-") - 1; + t += __small_sprintf (t, "%u", sid_id_auth (psid)); + for (i = 0; i < sid_sub_auth_count (psid); ++i) + t += __small_sprintf (t, "-%lu", sid_sub_auth (psid, i)); + return t; +} + +char * +cygpsid::string (char *nsidstr) const +{ + if (pstring (nsidstr)) + return nsidstr; + return NULL; +} + +PSID +cygsid::get_sid (DWORD s, DWORD cnt, DWORD *r, bool well_known) +{ + DWORD i; + SID_IDENTIFIER_AUTHORITY sid_auth = { SECURITY_NULL_SID_AUTHORITY }; +# define SECURITY_NT_AUTH 5 + + /* 2015-10-22: Note that we let slip SIDs with a subauthority count of 0. + There are systems, which generate the SID S-1-0 as group ownership SID, + see https://cygwin.com/ml/cygwin/2015-10/msg00141.html. */ + if (s > 255 || cnt > SID_MAX_SUB_AUTHORITIES) + { + psid = NO_SID; + return NULL; + } + sid_auth.Value[5] = s; + set (); + RtlInitializeSid (psid, &sid_auth, cnt); + PISID dsid = (PISID) psid; + for (i = 0; i < cnt; ++i) + dsid->SubAuthority[i] = r[i]; + /* If the well_known flag isn't set explicitely, we check the SID + for being a well-known SID ourselves. That's necessary because this + cygsid is created from a SID string, usually from /etc/passwd or + /etc/group. The calling code just doesn't know if the SID is well-known + or not. All SIDs are well-known SIDs, except those in the non-unique NT + authority range. */ + if (well_known) + well_known_sid = well_known; + else + well_known_sid = (s != SECURITY_NT_AUTH + || r[0] != SECURITY_NT_NON_UNIQUE); + return psid; +} + +const PSID +cygsid::getfromstr (PCWSTR nsidstr, bool well_known) +{ + PWCHAR lasts; + DWORD s, cnt = 0; + DWORD r[SID_MAX_SUB_AUTHORITIES]; + + if (nsidstr && !wcsncmp (nsidstr, L"S-1-", 4)) + { + s = wcstoul (nsidstr + 4, &lasts, 10); + while (cnt < SID_MAX_SUB_AUTHORITIES && *lasts == '-') + r[cnt++] = wcstoul (lasts + 1, &lasts, 10); + if (!*lasts) + return get_sid (s, cnt, r, well_known); + } + return psid = NO_SID; +} + +const PSID +cygsid::getfromstr (const char *nsidstr, bool well_known) +{ + char *lasts; + DWORD s, cnt = 0; + DWORD r[SID_MAX_SUB_AUTHORITIES]; + + if (nsidstr && !strncmp (nsidstr, "S-1-", 4)) + { + s = strtoul (nsidstr + 4, &lasts, 10); + while (cnt < SID_MAX_SUB_AUTHORITIES && *lasts == '-') + r[cnt++] = strtoul (lasts + 1, &lasts, 10); + if (!*lasts) + return get_sid (s, cnt, r, well_known); + } + return psid = NO_SID; +} + +const PSID +cygsid::create (DWORD auth, DWORD subauth_cnt, ...) +{ + va_list ap; + PSID sid; + + if (subauth_cnt > SID_MAX_SUB_AUTHORITIES) + return NULL; + + DWORD subauth[subauth_cnt]; + + va_start (ap, subauth_cnt); + for (DWORD i = 0; i < subauth_cnt; ++i) + subauth[i] = va_arg (ap, DWORD); + sid = get_sid (auth, subauth_cnt, subauth, false); + va_end (ap); + return sid; +} + +bool +cygsid::append (DWORD rid) +{ + if (psid == NO_SID) + return false; + PISID dsid = (PISID) psid; + if (dsid->SubAuthorityCount >= SID_MAX_SUB_AUTHORITIES) + return false; + dsid->SubAuthority[dsid->SubAuthorityCount++] = rid; + return true; +} + +cygsid * +cygsidlist::alloc_sids (int n) +{ + if (n > 0) + return (cygsid *) cmalloc (HEAP_STR, n * sizeof (cygsid)); + else + return NULL; +} + +void +cygsidlist::free_sids () +{ + if (sids) + cfree (sids); + sids = NULL; + cnt = maxcnt = 0; + type = cygsidlist_empty; +} + +BOOL +cygsidlist::add (const PSID nsi, bool well_known) +{ + if (contains (nsi)) + return TRUE; + if (cnt >= maxcnt) + { + cygsid *tmp = new cygsid [2 * maxcnt]; + if (!tmp) + return FALSE; + maxcnt *= 2; + for (int i = 0; i < cnt; ++i) + tmp[i] = sids[i]; + delete [] sids; + sids = tmp; + } + if (well_known) + sids[cnt++] *= nsi; + else + sids[cnt++] = nsi; + return TRUE; +} + +PSECURITY_DESCRIPTOR +security_descriptor::malloc (size_t nsize) +{ + free (); + if ((psd = (PSECURITY_DESCRIPTOR) ::malloc (nsize))) + sd_size = nsize; + return psd; +} + +PSECURITY_DESCRIPTOR +security_descriptor::realloc (size_t nsize) +{ + PSECURITY_DESCRIPTOR tmp; + + /* Can't re-use buffer allocated by GetSecurityInfo. */ + if (psd && !sd_size) + free (); + if (!(tmp = (PSECURITY_DESCRIPTOR) ::realloc (psd, nsize))) + return NULL; + sd_size = nsize; + return psd = tmp; +} + +void +security_descriptor::free () +{ + if (psd) + { + if (!sd_size) + LocalFree (psd); + else + ::free (psd); + } + psd = NULL; + sd_size = 0; +} + +#undef TEXT +#define TEXT(q) L##q + +/* Index must match the corresponding foo_PRIVILEGE value, see security.h. */ +static const struct { + const wchar_t *name; + bool high_integrity; /* UAC: High Mandatory Label required to + be allowed to enable this privilege in + the user token. */ +} cygpriv[] = +{ + { L"", false }, + { L"", false }, + { SE_CREATE_TOKEN_NAME, true }, + { SE_ASSIGNPRIMARYTOKEN_NAME, true }, + { SE_LOCK_MEMORY_NAME, false }, + { SE_INCREASE_QUOTA_NAME, true }, + { SE_MACHINE_ACCOUNT_NAME, false }, + { SE_TCB_NAME, true }, + { SE_SECURITY_NAME, true }, + { SE_TAKE_OWNERSHIP_NAME, true }, + { SE_LOAD_DRIVER_NAME, true }, + { SE_SYSTEM_PROFILE_NAME, true }, + { SE_SYSTEMTIME_NAME, true }, + { SE_PROF_SINGLE_PROCESS_NAME, true }, + { SE_INC_BASE_PRIORITY_NAME, true }, + { SE_CREATE_PAGEFILE_NAME, true }, + { SE_CREATE_PERMANENT_NAME, false }, + { SE_BACKUP_NAME, true }, + { SE_RESTORE_NAME, true }, + { SE_SHUTDOWN_NAME, false }, + { SE_DEBUG_NAME, true }, + { SE_AUDIT_NAME, false }, + { SE_SYSTEM_ENVIRONMENT_NAME, true }, + { SE_CHANGE_NOTIFY_NAME, false }, + { SE_REMOTE_SHUTDOWN_NAME, true }, + { SE_UNDOCK_NAME, false }, + { SE_SYNC_AGENT_NAME, false }, + { SE_ENABLE_DELEGATION_NAME, false }, + { SE_MANAGE_VOLUME_NAME, true }, + { SE_IMPERSONATE_NAME, true }, + { SE_CREATE_GLOBAL_NAME, false }, + { SE_TRUSTED_CREDMAN_ACCESS_NAME, false }, + { SE_RELABEL_NAME, true }, + { SE_INC_WORKING_SET_NAME, false }, + { SE_TIME_ZONE_NAME, true }, + { SE_CREATE_SYMBOLIC_LINK_NAME, true } +}; + +bool +privilege_luid (const PWCHAR pname, LUID &luid, bool &high_integrity) +{ + ULONG idx; + for (idx = SE_CREATE_TOKEN_PRIVILEGE; + idx <= SE_MAX_WELL_KNOWN_PRIVILEGE; + ++idx) + if (!wcscmp (cygpriv[idx].name, pname)) + { + luid.HighPart = 0; + luid.LowPart = idx; + high_integrity = cygpriv[idx].high_integrity; + return true; + } + return false; +} + +static const wchar_t * +privilege_name (const LUID &priv_luid) +{ + if (priv_luid.HighPart || priv_luid.LowPart < SE_CREATE_TOKEN_PRIVILEGE + || priv_luid.LowPart > SE_MAX_WELL_KNOWN_PRIVILEGE) + return L"<unknown privilege>"; + return cygpriv[priv_luid.LowPart].name; +} + +int +set_privilege (HANDLE token, DWORD privilege, bool enable) +{ + int ret = -1; + TOKEN_PRIVILEGES new_priv, orig_priv; + ULONG size; + NTSTATUS status; + + new_priv.PrivilegeCount = 1; + new_priv.Privileges[0].Luid.HighPart = 0L; + new_priv.Privileges[0].Luid.LowPart = privilege; + new_priv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + status = NtAdjustPrivilegesToken (token, FALSE, &new_priv, sizeof orig_priv, + &orig_priv, &size); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + goto out; + } + + /* If orig_priv.PrivilegeCount is 0, the privilege hasn't been changed. */ + if (!orig_priv.PrivilegeCount) + ret = enable ? 1 : 0; + else + ret = (orig_priv.Privileges[0].Attributes & SE_PRIVILEGE_ENABLED) ? 1 : 0; + +out: + if (ret < 0) + debug_printf ("%d = set_privilege((token %p) %W, %d)", ret, token, + privilege_name (new_priv.Privileges[0].Luid), enable); + return ret; +} + +/* This is called very early in process initialization. The code must + not depend on anything. */ +void +set_cygwin_privileges (HANDLE token) +{ + /* Setting these rights at process startup allows processes running under + user tokens which are in the administrstors group to have root-like + permissions. */ + /* Allow to access all files, independent of their ACL settings. */ + set_privilege (token, SE_RESTORE_PRIVILEGE, true); + set_privilege (token, SE_BACKUP_PRIVILEGE, true); + /* Allow full access to other user's processes. */ + set_privilege (token, SE_DEBUG_PRIVILEGE, true); +#if 0 + /* Allow to create global shared memory. This isn't required anymore since + Cygwin 1.7. It uses its own subdirectories in the global NT namespace + which isn't affected by the SE_CREATE_GLOBAL_PRIVILEGE restriction. */ + set_privilege (token, SE_CREATE_GLOBAL_PRIVILEGE, true); +#endif +} + +bool +sec_acl (PACL acl, bool original, bool admins, PSID sid1, PSID sid2, DWORD access2) +{ + NTSTATUS status; + size_t acl_len = MAX_DACL_LEN (5); + LPVOID pAce; + cygpsid psid; + +#ifdef DEBUGGING + if ((unsigned long) acl % 4) + api_fatal ("Incorrectly aligned incoming ACL buffer!"); +#endif + status = RtlCreateAcl (acl, acl_len, ACL_REVISION); + if (!NT_SUCCESS (status)) + { + debug_printf ("RtlCreateAcl: %y", status); + return false; + } + if (sid1) + { + status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL, sid1); + if (!NT_SUCCESS (status)) + debug_printf ("RtlAddAccessAllowedAce(sid1) %y", status); + } + if (original && (psid = cygheap->user.saved_sid ()) + && psid != sid1 && psid != well_known_system_sid) + { + status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL, psid); + if (!NT_SUCCESS (status)) + debug_printf ("RtlAddAccessAllowedAce(original) %y", status); + } + if (sid2) + { + status = RtlAddAccessAllowedAce (acl, ACL_REVISION, access2, sid2); + if (!NT_SUCCESS (status)) + debug_printf ("RtlAddAccessAllowedAce(sid2) %y", status); + } + if (admins) + { + status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL, + well_known_admins_sid); + if (!NT_SUCCESS (status)) + debug_printf ("RtlAddAccessAllowedAce(admin) %y", status); + } + status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL, + well_known_system_sid); + if (!NT_SUCCESS (status)) + debug_printf ("RtlAddAccessAllowedAce(system) %y", status); + status = RtlFirstFreeAce (acl, &pAce); + if (NT_SUCCESS (status) && pAce) + acl->AclSize = (char *) pAce - (char *) acl; + else + debug_printf ("RtlFirstFreeAce: %y", status); + + return true; +} + +PSECURITY_ATTRIBUTES +__sec_user (PVOID sa_buf, PSID sid1, PSID sid2, DWORD access2, BOOL inherit) +{ + PSECURITY_ATTRIBUTES psa = (PSECURITY_ATTRIBUTES) sa_buf; + PISECURITY_DESCRIPTOR psd = (PISECURITY_DESCRIPTOR) + ((char *) sa_buf + sizeof (*psa)); + PACL acl = (PACL) ((char *) sa_buf + sizeof (*psa) + sizeof (*psd)); + NTSTATUS status; + +#ifdef DEBUGGING + if ((unsigned long) sa_buf % 4) + api_fatal ("Incorrectly aligned incoming SA buffer!"); +#endif + if (!sec_acl (acl, true, true, sid1, sid2, access2)) + return inherit ? &sec_none : &sec_none_nih; + + RtlCreateSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION); + status = RtlSetDaclSecurityDescriptor (psd, TRUE, acl, FALSE); + if (!NT_SUCCESS (status)) + debug_printf ("RtlSetDaclSecurityDescriptor %y", status); + + psa->nLength = sizeof (SECURITY_ATTRIBUTES); + psa->lpSecurityDescriptor = psd; + psa->bInheritHandle = inherit; + return psa; +} + +/* Helper function to create a file security descriptor which allows + full access to admins, system, and the sid given as parameter. See + try_to_bin for how it's used. */ + +PSECURITY_DESCRIPTOR +_recycler_sd (void *buf, bool users, bool dir) +{ + NTSTATUS status; + PISECURITY_DESCRIPTOR psd = (PISECURITY_DESCRIPTOR) buf; + + if (!psd) + return NULL; + RtlCreateSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION); + PACL dacl = (PACL) (psd + 1); + RtlCreateAcl (dacl, MAX_DACL_LEN (3), ACL_REVISION); + RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, + dir ? SUB_CONTAINERS_AND_OBJECTS_INHERIT + : NO_INHERITANCE, + FILE_ALL_ACCESS, well_known_admins_sid); + RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, + dir ? SUB_CONTAINERS_AND_OBJECTS_INHERIT + : NO_INHERITANCE, + FILE_ALL_ACCESS, well_known_system_sid); + if (users) + RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, INHERIT_NO_PROPAGATE, + FILE_GENERIC_READ | FILE_GENERIC_EXECUTE + | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES, + well_known_users_sid); + else + RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, + dir ? SUB_CONTAINERS_AND_OBJECTS_INHERIT + : NO_INHERITANCE, + FILE_ALL_ACCESS, cygheap->user.sid ()); + LPVOID ace; + status = RtlFirstFreeAce (dacl, &ace); + if (!NT_SUCCESS (status)) + { + debug_printf ("RtlFirstFreeAce: %y", status); + return NULL; + } + dacl->AclSize = (char *) ace - (char *) dacl; + RtlSetDaclSecurityDescriptor (psd, TRUE, dacl, FALSE); + /* If the directory DACL is not marked as protected, shell32 thinks + the recycle dir is corrupted. As soon as Explorer accesses the + Recycler, the user will get a GUI dialog "The Recycle Bin on X:\ + is corrupted. Do you want to empty the Recycle Bin for this drive?" + Of course we want to avoid that. */ + if (dir) + psd->Control |= SE_DACL_PROTECTED; + return psd; +} + +/* Helper function to create an event security descriptor which only allows + specific access to everyone. Only the creating process has all access + rights. */ + +PSECURITY_DESCRIPTOR +_everyone_sd (void *buf, ACCESS_MASK access) +{ + NTSTATUS status; + PISECURITY_DESCRIPTOR psd = (PISECURITY_DESCRIPTOR) buf; + + if (psd) + { + RtlCreateSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION); + PACL dacl = (PACL) (psd + 1); + RtlCreateAcl (dacl, MAX_DACL_LEN (1), ACL_REVISION); + status = RtlAddAccessAllowedAce (dacl, ACL_REVISION, access, + well_known_world_sid); + if (!NT_SUCCESS (status)) + { + debug_printf ("RtlAddAccessAllowedAce: %y", status); + return NULL; + } + LPVOID ace; + status = RtlFirstFreeAce (dacl, &ace); + if (!NT_SUCCESS (status)) + { + debug_printf ("RtlFirstFreeAce: %y", status); + return NULL; + } + dacl->AclSize = (char *) ace - (char *) dacl; + RtlSetDaclSecurityDescriptor (psd, TRUE, dacl, FALSE); + } + return psd; +} + +static NO_COPY muto authz_guard; +static LUID authz_dummy_luid = { 0 }; + +class authz_ctx_cache_entry +{ + SLIST_ENTRY (authz_ctx_cache_entry) ctx_next; + cygsid sid; + AUTHZ_CLIENT_CONTEXT_HANDLE ctx_hdl; + + authz_ctx_cache_entry () + : sid (NO_SID), ctx_hdl (NULL) + { + ctx_next.sle_next = NULL; + } + authz_ctx_cache_entry (bool) + : sid (NO_SID), ctx_hdl (NULL) + { + ctx_next.sle_next = NULL; + } + void set (PSID psid, AUTHZ_CLIENT_CONTEXT_HANDLE hdl) + { + sid = psid; + ctx_hdl = hdl; + } + bool is (PSID psid) const { return RtlEqualSid (sid, psid); } + AUTHZ_CLIENT_CONTEXT_HANDLE context () const { return ctx_hdl; } + + friend class authz_ctx_cache; +}; + +class authz_ctx_cache +{ + SLIST_HEAD (, authz_ctx_cache_entry) ctx_list; + + AUTHZ_CLIENT_CONTEXT_HANDLE context (PSID); + + friend class authz_ctx; +}; + +class authz_ctx +{ + AUTHZ_RESOURCE_MANAGER_HANDLE authz_hdl; + AUTHZ_CLIENT_CONTEXT_HANDLE user_ctx_hdl; + authz_ctx_cache ctx_cache; + operator AUTHZ_RESOURCE_MANAGER_HANDLE (); + + friend class authz_ctx_cache; +public: + bool get_user_attribute (mode_t *, PSECURITY_DESCRIPTOR, PSID); +}; + +/* Authz handles are not inheritable. */ +static NO_COPY authz_ctx authz; + +authz_ctx::operator AUTHZ_RESOURCE_MANAGER_HANDLE () +{ + if (!authz_hdl) + { + /* Create handle to Authz resource manager */ + authz_guard.init ("authz_guard")->acquire (); + if (!authz_hdl + && !AuthzInitializeResourceManager (AUTHZ_RM_FLAG_NO_AUDIT, + NULL, NULL, NULL, NULL, + &authz_hdl)) + debug_printf ("AuthzInitializeResourceManager, %E"); + authz_guard.release (); + } + return authz_hdl; +} + +AUTHZ_CLIENT_CONTEXT_HANDLE +authz_ctx_cache::context (PSID user_sid) +{ + authz_ctx_cache_entry *entry; + AUTHZ_CLIENT_CONTEXT_HANDLE ctx_hdl = NULL; + + SLIST_FOREACH (entry, &ctx_list, ctx_next) + { + if (entry->is (user_sid)) + return entry->context (); + } + entry = new authz_ctx_cache_entry (true); + /* If the user is the current user, prefer to create the context from the + token, as outlined in MSDN. */ + if (RtlEqualSid (user_sid, cygheap->user.sid ()) + && !AuthzInitializeContextFromToken (0, cygheap->user.issetuid () + ? cygheap->user.primary_token () + : hProcToken, + authz, NULL, authz_dummy_luid, + NULL, &ctx_hdl)) + debug_printf ("AuthzInitializeContextFromToken, %E"); + /* In any other case, create the context from the user SID. */ + else if (!AuthzInitializeContextFromSid (0, user_sid, authz, NULL, + authz_dummy_luid, NULL, &ctx_hdl)) + debug_printf ("AuthzInitializeContextFromSid, %E"); + else + { + entry->set (user_sid, ctx_hdl); + authz_guard.acquire (); + SLIST_INSERT_HEAD (&ctx_list, entry, ctx_next); + authz_guard.release (); + return entry->context (); + } + delete entry; + return NULL; +} + +/* Ask Authz for the effective user permissions of the user with SID user_sid + on the object with security descriptor psd. We're caching the handles for + the Authz resource manager and the user contexts. */ +bool +authz_ctx::get_user_attribute (mode_t *attribute, PSECURITY_DESCRIPTOR psd, + PSID user_sid) +{ + /* If the owner is the main user of the process token (not some impersonated + user), cache the user context in the global user_ctx_hdl variable. */ + AUTHZ_CLIENT_CONTEXT_HANDLE ctx_hdl = NULL; + if (RtlEqualSid (user_sid, cygheap->user.sid ()) + && !cygheap->user.issetuid ()) + { + /* Avoid lock in default case. */ + if (!user_ctx_hdl) + { + authz_guard.acquire (); + /* Check user_ctx_hdl again under lock to avoid overwriting + user_ctx_hdl if it has already been initialized. */ + if (!user_ctx_hdl + && !AuthzInitializeContextFromToken (0, hProcToken, authz, NULL, + authz_dummy_luid, NULL, + &user_ctx_hdl)) + debug_printf ("AuthzInitializeContextFromToken, %E"); + authz_guard.release (); + } + if (user_ctx_hdl) + ctx_hdl = user_ctx_hdl; + } + if (!ctx_hdl && !(ctx_hdl = ctx_cache.context (user_sid))) + return false; + /* All set, check access. */ + ACCESS_MASK access = 0; + DWORD error = 0; + AUTHZ_ACCESS_REQUEST req = { + .DesiredAccess = MAXIMUM_ALLOWED, + .PrincipalSelfSid = NULL, + .ObjectTypeList = NULL, + .ObjectTypeListLength = 0, + .OptionalArguments = NULL + }; + AUTHZ_ACCESS_REPLY repl = { + .ResultListLength = 1, + .GrantedAccessMask = &access, + .SaclEvaluationResults = NULL, + .Error = &error + }; + if (AuthzAccessCheck (0, ctx_hdl, &req, NULL, psd, NULL, 0, &repl, NULL)) + { + if (access & FILE_READ_BITS) + *attribute |= S_IROTH; + if (access & FILE_WRITE_BITS) + *attribute |= S_IWOTH; + if (access & FILE_EXEC_BITS) + *attribute |= S_IXOTH; + return true; + } + return false; +} + +bool +authz_get_user_attribute (mode_t *attribute, PSECURITY_DESCRIPTOR psd, + PSID user_sid) +{ + *attribute = 0; + return authz.get_user_attribute (attribute, psd, user_sid); +} |