libc/winsup/cygwin/sec_helper.cc

645 lines
19 KiB
C++

/* sec_helper.cc: NT security helper functions
Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013 Red Hat, Inc.
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 <sys/acl.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 "pwdgrp.h"
#include "ntdll.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_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 (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)
{
/* 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 ((gr = internal_getgrsid (*this)))
id = gr->gr_gid;
if (id != ILLEGAL_UID)
{
if (type)
*type = GROUP;
return id;
}
}
if (!search_grp || type)
{
struct passwd *pw;
if (*this == cygheap->user.sid ())
id = myself->uid;
else if ((pw = internal_getpwsid (*this)))
id = pw->pw_uid;
if (id != ILLEGAL_UID && type)
*type = USER;
}
return id;
}
PWCHAR
cygpsid::string (PWCHAR nsidstr) const
{
UNICODE_STRING sid;
if (!psid || !nsidstr)
return NULL;
RtlInitEmptyUnicodeString (&sid, nsidstr, 256);
RtlConvertSidToUnicodeString (&sid, psid, FALSE);
return nsidstr;
}
char *
cygpsid::string (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", RtlIdentifierAuthoritySid (psid)->Value[5]);
for (i = 0; i < *RtlSubAuthorityCountSid (psid); ++i)
t += __small_sprintf (t, "-%lu", *RtlSubAuthoritySid (psid, i));
return nsidstr;
}
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
if (s > 255 || cnt < 1 || cnt > 8)
{
psid = NO_SID;
return NULL;
}
sid_auth.Value[5] = s;
set ();
RtlInitializeSid (psid, &sid_auth, cnt);
for (i = 0; i < cnt; ++i)
memcpy ((char *) psid + 8 + sizeof (DWORD) * i, &r[i], sizeof (DWORD));
/* 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 (const char *nsidstr, bool well_known)
{
char *lasts;
DWORD s, cnt = 0;
DWORD r[8];
if (nsidstr && !strncmp (nsidstr, "S-1-", 4))
{
s = strtoul (nsidstr + 4, &lasts, 10);
while (cnt < 8 && *lasts == '-')
r[cnt++] = strtoul (lasts + 1, &lasts, 10);
if (!*lasts)
return get_sid (s, cnt, r, well_known);
}
return psid = NO_SID;
}
BOOL
cygsid::getfrompw (const struct passwd *pw)
{
char *sp = (pw && pw->pw_gecos) ? strrchr (pw->pw_gecos, ',') : NULL;
return (*this = sp ? sp + 1 : sp) != NULL;
}
BOOL
cygsid::getfromgr (const struct group *gr)
{
char *sp = (gr && gr->gr_passwd) ? gr->gr_passwd : NULL;
return (*this = sp) != NULL;
}
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;
}
bool
get_sids_info (cygpsid owner_sid, cygpsid group_sid, uid_t * uidret, gid_t * gidret)
{
struct passwd *pw;
struct group *gr = NULL;
bool ret = false;
owner_sid.debug_print ("get_sids_info: owner SID =");
group_sid.debug_print ("get_sids_info: group SID =");
if (group_sid == cygheap->user.groups.pgsid)
*gidret = myself->gid;
else if ((gr = internal_getgrsid (group_sid)))
*gidret = gr->gr_gid;
else
*gidret = ILLEGAL_GID;
if (owner_sid == cygheap->user.sid ())
{
*uidret = myself->uid;
if (*gidret == myself->gid)
ret = true;
else
ret = (internal_getgroups (0, NULL, &group_sid) > 0);
}
else if ((pw = internal_getpwsid (owner_sid)))
{
*uidret = pw->pw_uid;
if (gr || (*gidret != ILLEGAL_GID
&& (gr = internal_getgrgid (*gidret))))
for (int idx = 0; gr->gr_mem[idx]; ++idx)
if ((ret = strcasematch (pw->pw_name, gr->gr_mem[idx])))
break;
}
else
*uidret = ILLEGAL_UID;
return ret;
}
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);
/* Pre-Vista, the per-user recycler dir has a rather too complicated
ACL by default, which has distinct ACEs for inheritable and non-inheritable
permissions. However, this ACL is practically equivalent to the ACL
created since Vista. Therefore we simplify our job here and create the
pre-Vista permissions the same way as on Vista and later. */
RtlCreateAcl (dacl, MAX_DACL_LEN (3), ACL_REVISION);
RtlAddAccessAllowedAceEx (dacl, ACL_REVISION,
dir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
: NO_INHERITANCE,
FILE_ALL_ACCESS, well_known_admins_sid);
RtlAddAccessAllowedAceEx (dacl, ACL_REVISION,
dir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
: NO_INHERITANCE,
FILE_ALL_ACCESS, well_known_system_sid);
if (users)
RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, NO_PROPAGATE_INHERIT_ACE,
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE
| FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES,
well_known_users_sid);
else
RtlAddAccessAllowedAceEx (dacl, ACL_REVISION,
dir ? CONTAINER_INHERIT_ACE
| OBJECT_INHERIT_ACE
: 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;
}