mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 09:58:01 +00:00
3396c97da9
Now that the new CMake build system is encouraging us to lay out the code like a set of libraries, it seems like a good idea to make them look more _like_ libraries, by putting things into separate modules as far as possible. This fixes several previous annoyances in which you had to link against some object in order to get a function you needed, but that object also contained other functions you didn't need which included link-time symbol references you didn't want to have to deal with. The usual offender was subsidiary supporting programs including misc.c for some innocuous function and then finding they had to deal with the requirements of buildinfo(). This big reorganisation introduces three new subdirectories called 'utils', one at the top level and one in each platform subdir. In each case, the directory contains basically the same files that were previously placed in the 'utils' build-time library, except that the ones that were extremely miscellaneous (misc.c, utils.c, uxmisc.c, winmisc.c, winmiscs.c, winutils.c) have been split up into much smaller pieces.
327 lines
9.4 KiB
C
327 lines
9.4 KiB
C
/*
|
|
* windows/utils/security.c: implementation of winsecur.h.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "putty.h"
|
|
|
|
#include "winsecur.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 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);
|
|
}
|