mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
90dcef3d9e
All spotted by Coverity.
243 lines
7.1 KiB
C
243 lines
7.1 KiB
C
/*
|
|
* Windows implementation of SSH connection-sharing IPC setup.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#if !defined NO_SECURITY
|
|
|
|
#define DEFINE_PLUG_METHOD_MACROS
|
|
#include "tree234.h"
|
|
#include "putty.h"
|
|
#include "network.h"
|
|
#include "proxy.h"
|
|
#include "ssh.h"
|
|
|
|
#include "winsecur.h"
|
|
|
|
#ifdef COVERITY
|
|
/*
|
|
* The hack I use to build for Coverity scanning, using winegcc and
|
|
* Makefile.cyg, didn't provide some defines in wincrypt.h last time I
|
|
* looked. Therefore, define them myself here, but enclosed in #ifdef
|
|
* COVERITY to ensure I don't make up random nonsense values for any
|
|
* real build.
|
|
*/
|
|
#ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE
|
|
#define CRYPTPROTECTMEMORY_BLOCK_SIZE 16
|
|
#endif
|
|
#ifndef CRYPTPROTECTMEMORY_CROSS_PROCESS
|
|
#define CRYPTPROTECTMEMORY_CROSS_PROCESS 1
|
|
#endif
|
|
#endif
|
|
|
|
#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
|
|
#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
|
|
|
|
static char *obfuscate_name(const char *realname)
|
|
{
|
|
/*
|
|
* Windows's named pipes all live in the same namespace, so one
|
|
* user can see what pipes another user has open. This is an
|
|
* undesirable privacy leak and in particular permits one user to
|
|
* know what username@host another user is SSHing to, so we
|
|
* protect that information by using CryptProtectMemory (which
|
|
* uses a key built in to each user's account).
|
|
*/
|
|
char *cryptdata;
|
|
int cryptlen;
|
|
SHA256_State sha;
|
|
unsigned char lenbuf[4];
|
|
unsigned char digest[32];
|
|
char retbuf[65];
|
|
int i;
|
|
|
|
cryptlen = strlen(realname) + 1;
|
|
cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
|
|
cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
|
|
cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
|
|
|
|
cryptdata = snewn(cryptlen, char);
|
|
memset(cryptdata, 0, cryptlen);
|
|
strcpy(cryptdata, realname);
|
|
|
|
/*
|
|
* CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
|
|
* use the same key in all processes with this user id, meaning
|
|
* that the next PuTTY process calling this function with the same
|
|
* input will get the same data.
|
|
*
|
|
* (Contrast with CryptProtectData, which invents a new session
|
|
* key every time since its API permits returning more data than
|
|
* was input, so calling _that_ and hashing the output would not
|
|
* be stable.)
|
|
*
|
|
* We don't worry too much if this doesn't work for some reason.
|
|
* Omitting this step still has _some_ privacy value (in that
|
|
* another user can test-hash things to confirm guesses as to
|
|
* where you might be connecting to, but cannot invert SHA-256 in
|
|
* the absence of any plausible guess). So we don't abort if we
|
|
* can't call CryptProtectMemory at all, or if it fails.
|
|
*/
|
|
if (got_crypt())
|
|
p_CryptProtectMemory(cryptdata, cryptlen,
|
|
CRYPTPROTECTMEMORY_CROSS_PROCESS);
|
|
|
|
/*
|
|
* We don't want to give away the length of the hostname either,
|
|
* so having got it back out of CryptProtectMemory we now hash it.
|
|
*/
|
|
SHA256_Init(&sha);
|
|
PUT_32BIT_MSB_FIRST(lenbuf, cryptlen);
|
|
SHA256_Bytes(&sha, lenbuf, 4);
|
|
SHA256_Bytes(&sha, cryptdata, cryptlen);
|
|
SHA256_Final(&sha, digest);
|
|
|
|
sfree(cryptdata);
|
|
|
|
/*
|
|
* Finally, make printable.
|
|
*/
|
|
for (i = 0; i < 32; i++) {
|
|
sprintf(retbuf + 2*i, "%02x", digest[i]);
|
|
/* the last of those will also write the trailing NUL */
|
|
}
|
|
|
|
return dupstr(retbuf);
|
|
}
|
|
|
|
static char *make_name(const char *prefix, const char *name)
|
|
{
|
|
char *username, *retname;
|
|
|
|
username = get_username();
|
|
retname = dupprintf("%s.%s.%s", prefix, username, name);
|
|
sfree(username);
|
|
|
|
return retname;
|
|
}
|
|
|
|
Socket new_named_pipe_client(const char *pipename, Plug plug);
|
|
Socket new_named_pipe_listener(const char *pipename, Plug plug);
|
|
|
|
int platform_ssh_share(const char *pi_name, Conf *conf,
|
|
Plug downplug, Plug upplug, Socket *sock,
|
|
char **logtext, char **ds_err, char **us_err,
|
|
int can_upstream, int can_downstream)
|
|
{
|
|
char *name, *mutexname, *pipename;
|
|
HANDLE mutex;
|
|
Socket retsock;
|
|
PSECURITY_DESCRIPTOR psd;
|
|
PACL acl;
|
|
|
|
/*
|
|
* Transform the platform-independent version of the connection
|
|
* identifier into the obfuscated version we'll use for our
|
|
* Windows named pipe and mutex. A side effect of doing this is
|
|
* that it also eliminates any characters illegal in Windows pipe
|
|
* names.
|
|
*/
|
|
name = obfuscate_name(pi_name);
|
|
if (!name) {
|
|
*logtext = dupprintf("Unable to call CryptProtectMemory: %s",
|
|
win_strerror(GetLastError()));
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
/*
|
|
* Make a mutex name out of the connection identifier, and lock it
|
|
* while we decide whether to be upstream or downstream.
|
|
*/
|
|
{
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name);
|
|
if (!make_private_security_descriptor(MUTEX_ALL_ACCESS,
|
|
&psd, &acl, logtext)) {
|
|
sfree(mutexname);
|
|
sfree(name);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = psd;
|
|
sa.bInheritHandle = FALSE;
|
|
|
|
mutex = CreateMutex(&sa, FALSE, mutexname);
|
|
|
|
if (!mutex) {
|
|
*logtext = dupprintf("CreateMutex(\"%s\") failed: %s",
|
|
mutexname, win_strerror(GetLastError()));
|
|
sfree(mutexname);
|
|
sfree(name);
|
|
LocalFree(psd);
|
|
LocalFree(acl);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
sfree(mutexname);
|
|
LocalFree(psd);
|
|
LocalFree(acl);
|
|
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
}
|
|
|
|
pipename = make_name(CONNSHARE_PIPE_PREFIX, name);
|
|
|
|
*logtext = NULL;
|
|
|
|
if (can_downstream) {
|
|
retsock = new_named_pipe_client(pipename, downplug);
|
|
if (sk_socket_error(retsock) == NULL) {
|
|
sfree(*logtext);
|
|
*logtext = pipename;
|
|
*sock = retsock;
|
|
sfree(name);
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
return SHARE_DOWNSTREAM;
|
|
}
|
|
sfree(*ds_err);
|
|
*ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
|
|
sk_close(retsock);
|
|
}
|
|
|
|
if (can_upstream) {
|
|
retsock = new_named_pipe_listener(pipename, upplug);
|
|
if (sk_socket_error(retsock) == NULL) {
|
|
sfree(*logtext);
|
|
*logtext = pipename;
|
|
*sock = retsock;
|
|
sfree(name);
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
return SHARE_UPSTREAM;
|
|
}
|
|
sfree(*us_err);
|
|
*us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
|
|
sk_close(retsock);
|
|
}
|
|
|
|
/* One of the above clauses ought to have happened. */
|
|
assert(*logtext || *ds_err || *us_err);
|
|
|
|
sfree(pipename);
|
|
sfree(name);
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
void platform_ssh_share_cleanup(const char *name)
|
|
{
|
|
}
|
|
|
|
#else /* !defined NO_SECURITY */
|
|
|
|
#include "noshare.c"
|
|
|
|
#endif /* !defined NO_SECURITY */
|