mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
14203bc54f
If the function name (or expression) in a function call or declaration is itself so long that even the first argument doesn't fit after it on the same line, or if that would leave so little space that it would be silly to try to wrap all the run-on lines into a tall thin column, then I used to do this ludicrously_long_function_name (arg1, arg2, arg3); and now prefer this ludicrously_long_function_name( arg1, arg2, arg3); I picked up the habit from Python, where the latter idiom is required by Python's syntactic significance of newlines (you can write the former if you use a backslash-continuation, but pretty much everyone seems to agree that that's much uglier). But I've found it works well in C as well: it makes it more obvious that the previous line is incomplete, it gives you a tiny bit more space to wrap the following lines into (the old idiom indents the _third_ line one space beyond the second), and I generally turn out to agree with the knock-on indentation decisions made by at least Emacs if you do it in the middle of a complex expression. Plus, of course, using the _same_ idiom between C and Python means less state-switching. So, while I'm making annoying indentation changes in general, this seems like a good time to dig out all the cases of the old idiom in this code, and switch them over to the new.
341 lines
9.8 KiB
C
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);
|
|
}
|