1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 01:18:00 +00:00
putty-source/windows/sftp.c
Simon Tatham 98200d1bfe Arm: turn on PSTATE.DIT if available and needed.
DIT, for 'Data-Independent Timing', is a bit you can set in the
processor state on sufficiently new Arm CPUs, which promises that a
long list of instructions will deliberately avoid varying their timing
based on the input register values. Just what you want for keeping
your constant-time crypto primitives constant-time.

As far as I'm aware, no CPU has _yet_ implemented any data-dependent
optimisations, so DIT is a safety precaution against them doing so in
future. It would be embarrassing to be caught without it if a future
CPU does do that, so we now turn on DIT in the PuTTY process state.

I've put a call to the new enable_dit() function at the start of every
main() and WinMain() belonging to a program that might do
cryptography (even testcrypt, in case someone uses it for something!),
and in case I missed one there, also added a second call at the first
moment that any cryptography-using part of the code looks as if it
might become active: when an instance of the SSH protocol object is
configured, when the system PRNG is initialised, and when selecting
any cryptographic authentication protocol in an HTTP or SOCKS proxy
connection. With any luck those precautions between them should ensure
it's on whenever we need it.

Arm's own recommendation is that you should carefully choose the
granularity at which you enable and disable DIT: there's a potential
time cost to turning it on and off (I'm not sure what, but plausibly
something of the order of a pipeline flush), so it's a performance hit
to do it _inside_ each individual crypto function, but if CPUs start
supporting significant data-dependent optimisation in future, then it
will also become a noticeable performance hit to just leave it on
across the whole process. So you'd like to do it somewhere in the
middle: for example, you might turn on DIT once around the whole
process of verifying and decrypting an SSH packet, instead of once for
decryption and once for MAC.

With all respect to that recommendation as a strategy for maximum
performance, I'm not following it here. I turn on DIT at the start of
the PuTTY process, and then leave it on. Rationale:

 1. PuTTY is not otherwise a performance-critical application: it's
    not likely to max out your CPU for any purpose _other_ than
    cryptography. The most CPU-intensive non-cryptographic thing I can
    imagine a PuTTY process doing is the complicated computation of
    font rendering in the terminal, and that will normally be cached
    (you don't recompute each glyph from its outline and hints for
    every time you display it).

 2. I think a bigger risk lies in accidental side channels from having
    DIT turned off when it should have been on. I can imagine lots of
    causes for that. Missing a crypto operation in some unswept corner
    of the code; confusing control flow (like my coroutine macros)
    jumping with DIT clear into the middle of a region of code that
    expected DIT to have been set at the beginning; having a reference
    counter of DIT requests and getting it out of sync.

In a more sophisticated programming language, it might be possible to
avoid the risk in #2 by cleverness with the type system. For example,
in Rust, you could have a zero-sized type that acts as a proof token
for DIT being enabled (it would be constructed by a function that also
sets DIT, have a Drop implementation that clears DIT, and be !Send so
you couldn't use it in a thread other than the one where DIT was set),
and then you could require all the actual crypto functions to take a
DitToken as an extra parameter, at zero runtime cost. Then "oops I
forgot to set DIT around this piece of crypto" would become a compile
error. Even so, you'd have to take some care with coroutine-structured
code (what happens if a Rust async function yields while holding a DIT
token?) and with nesting (if you have two DIT tokens, you don't want
dropping the inner one to clear DIT while the outer one is still there
to wrongly convince callees that it's set). Maybe in Rust you could
get this all to work reliably. But not in C!

DIT is an optional feature of the Arm architecture, so we must first
test to see if it's supported. This is done the same way as we already
do for the various Arm crypto accelerators: on ELF-based systems,
check the appropriate bit in the 'hwcap' words in the ELF aux vector;
on Mac, look for an appropriate sysctl flag.

On Windows I don't know of a way to query the DIT feature, _or_ of a
way to write the necessary enabling instruction in an MSVC-compatible
way. I've _heard_ that it might not be necessary, because Windows
might just turn on DIT unconditionally and leave it on, in an even
more extreme version of my own strategy. I don't have a source for
that - I heard it by word of mouth - but I _hope_ it's true, because
that would suit me very well! Certainly I can't write code to enable
DIT without knowing (a) how to do it, (b) how to know if it's safe.
Nonetheless, I've put the enable_dit() call in all the right places in
the Windows main programs as well as the Unix and cross-platform code,
so that if I later find out that I _can_ put in an explicit enable of
DIT in some way, I'll only have to arrange to set HAVE_ARM_DIT and
compile the enable_dit() function appropriately.
2024-12-19 08:52:47 +00:00

659 lines
15 KiB
C

/*
* sftp.c: the Windows-specific parts of PSFTP and PSCP.
*/
#include <winsock2.h> /* need to put this first, for winelib builds */
#include <assert.h>
#define NEED_DECLARATION_OF_SELECT
#include "putty.h"
#include "psftp.h"
#include "ssh.h"
#include "security-api.h"
SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p)
{
/* The file transfer tools don't support Restart Session, so we
* can just have a single static cmdline_get_passwd_input_state
* that's never reset */
static cmdline_get_passwd_input_state cmdline_state =
CMDLINE_GET_PASSWD_INPUT_STATE_INIT;
SeatPromptResult spr;
spr = cmdline_get_passwd_input(p, &cmdline_state, false);
if (spr.kind == SPRK_INCOMPLETE)
spr = console_get_userpass_input(p);
return spr;
}
void platform_get_x11_auth(struct X11Display *display, Conf *conf)
{
/* Do nothing, therefore no auth. */
}
const bool platform_uses_x11_unix_by_default = true;
/* ----------------------------------------------------------------------
* File access abstraction.
*/
/*
* Set local current directory. Returns NULL on success, or else an
* error message which must be freed after printing.
*/
char *psftp_lcd(char *dir)
{
char *ret = NULL;
if (!SetCurrentDirectory(dir)) {
LPVOID message;
int i;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&message, 0, NULL);
i = strcspn((char *)message, "\n");
ret = dupprintf("%.*s", i, (LPCTSTR)message);
LocalFree(message);
}
return ret;
}
/*
* Get local current directory. Returns a string which must be
* freed.
*/
char *psftp_getcwd(void)
{
char *ret = snewn(256, char);
size_t len = GetCurrentDirectory(256, ret);
if (len > 256)
ret = sresize(ret, len, char);
GetCurrentDirectory(len, ret);
return ret;
}
static inline uint64_t uint64_from_words(uint32_t hi, uint32_t lo)
{
return (((uint64_t)hi) << 32) | lo;
}
#define TIME_POSIX_TO_WIN(t, ft) do { \
ULARGE_INTEGER uli; \
uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \
(ft).dwLowDateTime = uli.LowPart; \
(ft).dwHighDateTime = uli.HighPart; \
} while (0)
#define TIME_WIN_TO_POSIX(ft, t) do { \
ULARGE_INTEGER uli; \
uli.LowPart = (ft).dwLowDateTime; \
uli.HighPart = (ft).dwHighDateTime; \
uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \
(t) = (unsigned long) uli.QuadPart; \
} while (0)
struct RFile {
HANDLE h;
};
RFile *open_existing_file(const char *name, uint64_t *size,
unsigned long *mtime, unsigned long *atime,
long *perms)
{
HANDLE h;
RFile *f;
h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, 0);
if (h == INVALID_HANDLE_VALUE)
return NULL;
f = snew(RFile);
f->h = h;
if (size) {
DWORD lo, hi;
lo = GetFileSize(h, &hi);
*size = uint64_from_words(hi, lo);
}
if (mtime || atime) {
FILETIME actime, wrtime;
GetFileTime(h, NULL, &actime, &wrtime);
if (atime)
TIME_WIN_TO_POSIX(actime, *atime);
if (mtime)
TIME_WIN_TO_POSIX(wrtime, *mtime);
}
if (perms)
*perms = -1;
return f;
}
int read_from_file(RFile *f, void *buffer, int length)
{
DWORD read;
if (!ReadFile(f->h, buffer, length, &read, NULL))
return -1; /* error */
else
return read;
}
void close_rfile(RFile *f)
{
CloseHandle(f->h);
sfree(f);
}
struct WFile {
HANDLE h;
};
WFile *open_new_file(const char *name, long perms)
{
HANDLE h;
WFile *f;
h = CreateFile(name, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (h == INVALID_HANDLE_VALUE)
return NULL;
f = snew(WFile);
f->h = h;
return f;
}
WFile *open_existing_wfile(const char *name, uint64_t *size)
{
HANDLE h;
WFile *f;
h = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, 0);
if (h == INVALID_HANDLE_VALUE)
return NULL;
f = snew(WFile);
f->h = h;
if (size) {
DWORD lo, hi;
lo = GetFileSize(h, &hi);
*size = uint64_from_words(hi, lo);
}
return f;
}
int write_to_file(WFile *f, void *buffer, int length)
{
DWORD written;
if (!WriteFile(f->h, buffer, length, &written, NULL))
return -1; /* error */
else
return written;
}
void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
{
FILETIME actime, wrtime;
TIME_POSIX_TO_WIN(atime, actime);
TIME_POSIX_TO_WIN(mtime, wrtime);
SetFileTime(f->h, NULL, &actime, &wrtime);
}
void close_wfile(WFile *f)
{
CloseHandle(f->h);
sfree(f);
}
/* Seek offset bytes through file, from whence, where whence is
FROM_START, FROM_CURRENT, or FROM_END */
int seek_file(WFile *f, uint64_t offset, int whence)
{
DWORD movemethod;
switch (whence) {
case FROM_START:
movemethod = FILE_BEGIN;
break;
case FROM_CURRENT:
movemethod = FILE_CURRENT;
break;
case FROM_END:
movemethod = FILE_END;
break;
default:
return -1;
}
{
LONG lo = offset & 0xFFFFFFFFU, hi = offset >> 32;
SetFilePointer(f->h, lo, &hi, movemethod);
}
if (GetLastError() != NO_ERROR)
return -1;
else
return 0;
}
uint64_t get_file_posn(WFile *f)
{
LONG lo, hi = 0;
lo = SetFilePointer(f->h, 0L, &hi, FILE_CURRENT);
return uint64_from_words(hi, lo);
}
int file_type(const char *name)
{
DWORD attr;
attr = GetFileAttributes(name);
/* We know of no `weird' files under Windows. */
if (attr == (DWORD)-1)
return FILE_TYPE_NONEXISTENT;
else if (attr & FILE_ATTRIBUTE_DIRECTORY)
return FILE_TYPE_DIRECTORY;
else
return FILE_TYPE_FILE;
}
struct DirHandle {
HANDLE h;
char *name;
};
DirHandle *open_directory(const char *name, const char **errmsg)
{
HANDLE h;
WIN32_FIND_DATA fdat;
char *findfile;
DirHandle *dir;
/* Enumerate files in dir `foo'. */
findfile = dupcat(name, "/*");
h = FindFirstFile(findfile, &fdat);
if (h == INVALID_HANDLE_VALUE) {
*errmsg = win_strerror(GetLastError());
return NULL;
}
sfree(findfile);
dir = snew(DirHandle);
dir->h = h;
dir->name = dupstr(fdat.cFileName);
return dir;
}
char *read_filename(DirHandle *dir)
{
do {
if (!dir->name) {
WIN32_FIND_DATA fdat;
if (!FindNextFile(dir->h, &fdat))
return NULL;
else
dir->name = dupstr(fdat.cFileName);
}
assert(dir->name);
if (dir->name[0] == '.' &&
(dir->name[1] == '\0' ||
(dir->name[1] == '.' && dir->name[2] == '\0'))) {
sfree(dir->name);
dir->name = NULL;
}
} while (!dir->name);
if (dir->name) {
char *ret = dir->name;
dir->name = NULL;
return ret;
} else
return NULL;
}
void close_directory(DirHandle *dir)
{
FindClose(dir->h);
if (dir->name)
sfree(dir->name);
sfree(dir);
}
int test_wildcard(const char *name, bool cmdline)
{
HANDLE fh;
WIN32_FIND_DATA fdat;
/* First see if the exact name exists. */
if (GetFileAttributes(name) != (DWORD)-1)
return WCTYPE_FILENAME;
/* Otherwise see if a wildcard match finds anything. */
fh = FindFirstFile(name, &fdat);
if (fh == INVALID_HANDLE_VALUE)
return WCTYPE_NONEXISTENT;
FindClose(fh);
return WCTYPE_WILDCARD;
}
struct WildcardMatcher {
HANDLE h;
char *name;
char *srcpath;
};
char *stripslashes(const char *str, bool local)
{
char *p;
/*
* On Windows, \ / : are all path component separators.
*/
if (local) {
p = strchr(str, ':');
if (p) str = p+1;
}
p = strrchr(str, '/');
if (p) str = p+1;
if (local) {
p = strrchr(str, '\\');
if (p) str = p+1;
}
return (char *)str;
}
WildcardMatcher *begin_wildcard_matching(const char *name)
{
HANDLE h;
WIN32_FIND_DATA fdat;
WildcardMatcher *dir;
char *last;
h = FindFirstFile(name, &fdat);
if (h == INVALID_HANDLE_VALUE)
return NULL;
dir = snew(WildcardMatcher);
dir->h = h;
dir->srcpath = dupstr(name);
last = stripslashes(dir->srcpath, true);
*last = '\0';
if (fdat.cFileName[0] == '.' &&
(fdat.cFileName[1] == '\0' ||
(fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
dir->name = NULL;
else
dir->name = dupcat(dir->srcpath, fdat.cFileName);
return dir;
}
char *wildcard_get_filename(WildcardMatcher *dir)
{
while (!dir->name) {
WIN32_FIND_DATA fdat;
if (!FindNextFile(dir->h, &fdat))
return NULL;
if (fdat.cFileName[0] == '.' &&
(fdat.cFileName[1] == '\0' ||
(fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
dir->name = NULL;
else
dir->name = dupcat(dir->srcpath, fdat.cFileName);
}
if (dir->name) {
char *ret = dir->name;
dir->name = NULL;
return ret;
} else
return NULL;
}
void finish_wildcard_matching(WildcardMatcher *dir)
{
FindClose(dir->h);
if (dir->name)
sfree(dir->name);
sfree(dir->srcpath);
sfree(dir);
}
bool vet_filename(const char *name)
{
if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))
return false;
if (!name[strspn(name, ".")]) /* entirely composed of dots */
return false;
return true;
}
bool create_directory(const char *name)
{
return CreateDirectory(name, NULL) != 0;
}
char *dir_file_cat(const char *dir, const char *file)
{
ptrlen dir_pl = ptrlen_from_asciz(dir);
return dupcat(
dir, (ptrlen_endswith(dir_pl, PTRLEN_LITERAL("\\"), NULL) ||
ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL)) ? "" : "\\",
file);
}
/* ----------------------------------------------------------------------
* Platform-specific network handling.
*/
struct winsftp_cliloop_ctx {
HANDLE other_event;
int toret;
};
static bool winsftp_cliloop_pre(void *vctx, const HANDLE **extra_handles,
size_t *n_extra_handles)
{
struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx;
if (ctx->other_event != INVALID_HANDLE_VALUE) {
*extra_handles = &ctx->other_event;
*n_extra_handles = 1;
}
return true;
}
static bool winsftp_cliloop_post(void *vctx, size_t extra_handle_index)
{
struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx;
if (ctx->other_event != INVALID_HANDLE_VALUE &&
extra_handle_index == 0)
ctx->toret = 1; /* other_event was set */
return false; /* always run only one loop iteration */
}
int do_eventsel_loop(HANDLE other_event)
{
struct winsftp_cliloop_ctx ctx[1];
ctx->other_event = other_event;
ctx->toret = 0;
cli_main_loop(winsftp_cliloop_pre, winsftp_cliloop_post, ctx);
return ctx->toret;
}
/*
* Wait for some network data and process it.
*
* We have two variants of this function. One uses select() so that
* it's compatible with WinSock 1. The other uses WSAEventSelect
* and MsgWaitForMultipleObjects, so that we can consistently use
* WSAEventSelect throughout; this enables us to also implement
* ssh_sftp_get_cmdline() using a parallel mechanism.
*/
int ssh_sftp_loop_iteration(void)
{
if (p_WSAEventSelect == NULL) {
fd_set readfds;
int ret;
unsigned long now = GETTICKCOUNT(), then;
SOCKET skt = winselcli_unique_socket();
if (skt == INVALID_SOCKET)
return -1; /* doom */
if (socket_writable(skt))
select_result((WPARAM) skt, (LPARAM) FD_WRITE);
do {
unsigned long next;
long ticks;
struct timeval tv, *ptv;
if (run_timers(now, &next)) {
then = now;
now = GETTICKCOUNT();
if (now - then > next - then)
ticks = 0;
else
ticks = next - now;
tv.tv_sec = ticks / 1000;
tv.tv_usec = ticks % 1000 * 1000;
ptv = &tv;
} else {
ptv = NULL;
}
FD_ZERO(&readfds);
FD_SET(skt, &readfds);
ret = p_select(1, &readfds, NULL, NULL, ptv);
if (ret < 0)
return -1; /* doom */
else if (ret == 0)
now = next;
else
now = GETTICKCOUNT();
} while (ret == 0);
select_result((WPARAM) skt, (LPARAM) FD_READ);
return 0;
} else {
return do_eventsel_loop(INVALID_HANDLE_VALUE);
}
}
/*
* Read a command line from standard input.
*
* In the presence of WinSock 2, we can use WSAEventSelect to
* mediate between the socket and stdin, meaning we can send
* keepalives and respond to server events even while waiting at
* the PSFTP command prompt. Without WS2, we fall back to a simple
* fgets.
*/
struct command_read_ctx {
HANDLE event;
char *line;
};
static DWORD WINAPI command_read_thread(void *param)
{
struct command_read_ctx *ctx = (struct command_read_ctx *) param;
ctx->line = fgetline(stdin);
SetEvent(ctx->event);
return 0;
}
char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok)
{
int ret;
struct command_read_ctx ctx[1];
DWORD threadid;
HANDLE hThread;
fputs(prompt, stdout);
fflush(stdout);
if ((winselcli_unique_socket() == INVALID_SOCKET && no_fds_ok) ||
p_WSAEventSelect == NULL) {
return fgetline(stdin); /* very simple */
}
/*
* Create a second thread to read from stdin. Process network
* and timing events until it terminates.
*/
ctx->event = CreateEvent(NULL, false, false, NULL);
ctx->line = NULL;
hThread = CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid);
if (!hThread) {
CloseHandle(ctx->event);
fprintf(stderr, "Unable to create command input thread\n");
cleanup_exit(1);
}
do {
ret = do_eventsel_loop(ctx->event);
/* do_eventsel_loop can't return an error (unlike
* ssh_sftp_loop_iteration, which can return -1 if select goes
* wrong or if the socket doesn't exist). */
assert(ret >= 0);
} while (ret == 0);
CloseHandle(hThread);
CloseHandle(ctx->event);
return ctx->line;
}
void platform_psftp_pre_conn_setup(LogPolicy *lp)
{
if (restricted_acl()) {
lp_eventlog(lp, "Running with restricted process ACL");
}
}
/* ----------------------------------------------------------------------
* Main program. Parse arguments etc.
*/
int main(int argc, char *argv[])
{
int ret;
dll_hijacking_protection();
enable_dit();
CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW();
ret = psftp_main(arglist);
return ret;
}