1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-25 09:12:24 +00:00
putty-source/windows/utils/security.c
Simon Tatham 04c1617f20 Formatting: realign labels and case/default statements.
My aim has always been to have those back-dented by 2 spaces (half an
indent level) compared to the statements around them, so that in
particular switch statements have distinct alignment for the
statement, the cases and the interior code without consuming two whole
indent levels.

This patch sweeps up all the violations of that principle found by my
bulk-reindentation exercise.
2022-08-03 20:48:46 +01:00

341 lines
9.8 KiB
C

/*
* windows/utils/security.c: implementation of security-api.h.
*/
#include <stdio.h>
#include <stdlib.h>
#include "putty.h"
#include "security-api.h"
/* Initialised once, then kept around to reuse forever */
static PSID worldsid, networksid, usersid;
DEF_WINDOWS_FUNCTION(OpenProcessToken);
DEF_WINDOWS_FUNCTION(GetTokenInformation);
DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor);
DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner);
DEF_WINDOWS_FUNCTION(GetSecurityInfo);
DEF_WINDOWS_FUNCTION(SetSecurityInfo);
DEF_WINDOWS_FUNCTION(SetEntriesInAclA);
bool should_have_security(void)
{
#ifdef LEGACY_WINDOWS
/* Legacy pre-NT platforms are not expected to have any of these APIs */
init_winver();
return (osPlatformId == VER_PLATFORM_WIN32_NT);
#else
/* In the up-to-date PuTTY builds which do not support those
* platforms, unconditionally return true, to minimise the risk of
* compiling out security checks. */
return true;
#endif
}
bool got_advapi(void)
{
static bool attempted = false;
static bool successful;
static HMODULE advapi;
if (!attempted) {
attempted = true;
advapi = load_system32_dll("advapi32.dll");
successful = advapi &&
GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) &&
GET_WINDOWS_FUNCTION(advapi, SetSecurityInfo) &&
GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) &&
GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA);
}
return successful;
}
PSID get_user_sid(void)
{
HANDLE proc = NULL, tok = NULL;
TOKEN_USER *user = NULL;
DWORD toklen, sidlen;
PSID sid = NULL, ret = NULL;
if (usersid)
return usersid;
if (!got_advapi())
goto cleanup;
if ((proc = OpenProcess(MAXIMUM_ALLOWED, false,
GetCurrentProcessId())) == NULL)
goto cleanup;
if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok))
goto cleanup;
if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
goto cleanup;
if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL)
goto cleanup;
if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen))
goto cleanup;
sidlen = GetLengthSid(user->User.Sid);
sid = (PSID)smalloc(sidlen);
if (!CopySid(sidlen, sid, user->User.Sid))
goto cleanup;
/* Success. Move sid into the return value slot, and null it out
* to stop the cleanup code freeing it. */
ret = usersid = sid;
sid = NULL;
cleanup:
if (proc != NULL)
CloseHandle(proc);
if (tok != NULL)
CloseHandle(tok);
if (user != NULL)
LocalFree(user);
if (sid != NULL)
sfree(sid);
return ret;
}
static bool getsids(char **error)
{
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-braces"
#endif
SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
#ifdef __clang__
#pragma clang diagnostic pop
#endif
bool ret = false;
*error = NULL;
if (!usersid) {
if ((usersid = get_user_sid()) == NULL) {
*error = dupprintf("unable to construct SID for current user: %s",
win_strerror(GetLastError()));
goto cleanup;
}
}
if (!worldsid) {
if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0, &worldsid)) {
*error = dupprintf("unable to construct SID for world: %s",
win_strerror(GetLastError()));
goto cleanup;
}
}
if (!networksid) {
if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
0, 0, 0, 0, 0, 0, 0, &networksid)) {
*error = dupprintf("unable to construct SID for "
"local same-user access only: %s",
win_strerror(GetLastError()));
goto cleanup;
}
}
ret = true;
cleanup:
return ret;
}
bool make_private_security_descriptor(DWORD permissions,
PSECURITY_DESCRIPTOR *psd,
PACL *acl,
char **error)
{
EXPLICIT_ACCESS ea[3];
int acl_err;
bool ret = false;
*psd = NULL;
*acl = NULL;
*error = NULL;
if (!getsids(error))
goto cleanup;
memset(ea, 0, sizeof(ea));
ea[0].grfAccessPermissions = permissions;
ea[0].grfAccessMode = REVOKE_ACCESS;
ea[0].grfInheritance = NO_INHERITANCE;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
ea[1].grfAccessPermissions = permissions;
ea[1].grfAccessMode = GRANT_ACCESS;
ea[1].grfInheritance = NO_INHERITANCE;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.ptstrName = (LPTSTR)usersid;
ea[2].grfAccessPermissions = permissions;
ea[2].grfAccessMode = REVOKE_ACCESS;
ea[2].grfInheritance = NO_INHERITANCE;
ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[2].Trustee.ptstrName = (LPTSTR)networksid;
acl_err = p_SetEntriesInAclA(3, ea, NULL, acl);
if (acl_err != ERROR_SUCCESS || *acl == NULL) {
*error = dupprintf("unable to construct ACL: %s",
win_strerror(acl_err));
goto cleanup;
}
*psd = (PSECURITY_DESCRIPTOR)
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if (!*psd) {
*error = dupprintf("unable to allocate security descriptor: %s",
win_strerror(GetLastError()));
goto cleanup;
}
if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) {
*error = dupprintf("unable to initialise security descriptor: %s",
win_strerror(GetLastError()));
goto cleanup;
}
if (!SetSecurityDescriptorOwner(*psd, usersid, false)) {
*error = dupprintf("unable to set owner in security descriptor: %s",
win_strerror(GetLastError()));
goto cleanup;
}
if (!SetSecurityDescriptorDacl(*psd, true, *acl, false)) {
*error = dupprintf("unable to set DACL in security descriptor: %s",
win_strerror(GetLastError()));
goto cleanup;
}
ret = true;
cleanup:
if (!ret) {
if (*psd) {
LocalFree(*psd);
*psd = NULL;
}
if (*acl) {
LocalFree(*acl);
*acl = NULL;
}
} else {
sfree(*error);
*error = NULL;
}
return ret;
}
static bool acl_restricted = false;
bool restricted_acl(void) { return acl_restricted; }
static bool really_restrict_process_acl(char **error)
{
EXPLICIT_ACCESS ea[2];
int acl_err;
bool ret = false;
PACL acl = NULL;
static const DWORD nastyace=WRITE_DAC | WRITE_OWNER |
PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD |
PROCESS_DUP_HANDLE |
PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE |
PROCESS_SUSPEND_RESUME;
if (!getsids(error))
goto cleanup;
memset(ea, 0, sizeof(ea));
/* Everyone: deny */
ea[0].grfAccessPermissions = nastyace;
ea[0].grfAccessMode = DENY_ACCESS;
ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
/* User: user ace */
ea[1].grfAccessPermissions = ~nastyace & 0x1fff;
ea[1].grfAccessMode = GRANT_ACCESS;
ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.ptstrName = (LPTSTR)usersid;
acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl);
if (acl_err != ERROR_SUCCESS || acl == NULL) {
*error = dupprintf("unable to construct ACL: %s",
win_strerror(acl_err));
goto cleanup;
}
if (ERROR_SUCCESS != p_SetSecurityInfo
(GetCurrentProcess(), SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
usersid, NULL, acl, NULL)) {
*error = dupprintf("Unable to set process ACL: %s",
win_strerror(GetLastError()));
goto cleanup;
}
acl_restricted = true;
ret=true;
cleanup:
if (!ret) {
if (acl) {
LocalFree(acl);
acl = NULL;
}
}
return ret;
}
/*
* Lock down our process's ACL, to present an obstacle to malware
* trying to write into its memory. This can't be a full defence,
* because well timed malware could attack us before this code runs -
* even if it was unconditionally run at the very start of main(),
* which we wouldn't want to do anyway because it turns out in practie
* that interfering with other processes in this way has significant
* non-infringing uses on Windows (e.g. screen reader software).
*
* If we've been requested to do this and are unsuccessful, bomb out
* via modalfatalbox rather than continue in a less protected mode.
*
* This function is intentionally outside the #ifndef NO_SECURITY that
* covers the rest of this file, because when PuTTY is compiled
* without the ability to restrict its ACL, we don't want it to
* silently pretend to honour the instruction to do so.
*/
void restrict_process_acl(void)
{
char *error = NULL;
bool ret;
ret = really_restrict_process_acl(&error);
if (!ret)
modalfatalbox("Could not restrict process ACL: %s", error);
}