884 lines
26 KiB
C++
884 lines
26 KiB
C++
|
/* 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 __reg3
|
||
|
__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);
|
||
|
}
|