1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00

Further tightening up in PSCP. Fixed a couple more holes whereby a

malicious SCP server could have written to areas other than the ones
the user requested; cleared up buffer overruns everywhere. Hopefully
we now do not use arbitrary buffer limits _anywhere_.

[originally from svn r1205]
This commit is contained in:
Simon Tatham 2001-08-26 15:31:29 +00:00
parent f7f96066f7
commit 306a13c025
4 changed files with 172 additions and 101 deletions

54
misc.c
View File

@ -4,7 +4,53 @@
#include <assert.h> #include <assert.h>
#include "putty.h" #include "putty.h"
/* /* ----------------------------------------------------------------------
* String handling routines.
*/
char *dupstr(char *s)
{
int len = strlen(s);
char *p = smalloc(len + 1);
strcpy(p, s);
return p;
}
/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
char *dupcat(char *s1, ...)
{
int len;
char *p, *q, *sn;
va_list ap;
len = strlen(s1);
va_start(ap, s1);
while (1) {
sn = va_arg(ap, char *);
if (!sn)
break;
len += strlen(sn);
}
va_end(ap);
p = smalloc(len + 1);
strcpy(p, s1);
q = p + strlen(p);
va_start(ap, s1);
while (1) {
sn = va_arg(ap, char *);
if (!sn)
break;
strcpy(q, sn);
q += strlen(q);
}
va_end(ap);
return p;
}
/* ----------------------------------------------------------------------
* Generic routines to deal with send buffers: a linked list of * Generic routines to deal with send buffers: a linked list of
* smallish blocks, with the operations * smallish blocks, with the operations
* *
@ -99,7 +145,7 @@ void bufchain_prefix(bufchain *ch, void **data, int *len)
*data = ch->head->buf + ch->head->bufpos; *data = ch->head->buf + ch->head->bufpos;
} }
/* /* ----------------------------------------------------------------------
* My own versions of malloc, realloc and free. Because I want * My own versions of malloc, realloc and free. Because I want
* malloc and realloc to bomb out and exit the program if they run * malloc and realloc to bomb out and exit the program if they run
* out of memory, realloc to reliably call malloc if passed a NULL * out of memory, realloc to reliably call malloc if passed a NULL
@ -411,6 +457,10 @@ void safefree(void *ptr)
#endif #endif
} }
/* ----------------------------------------------------------------------
* Debugging routines.
*/
#ifdef DEBUG #ifdef DEBUG
static FILE *debug_fp = NULL; static FILE *debug_fp = NULL;
static int debug_got_console = 0; static int debug_got_console = 0;

3
misc.h
View File

@ -3,6 +3,9 @@
#include "puttymem.h" #include "puttymem.h"
char *dupstr(char *s);
char *dupcat(char *s1, ...);
struct bufchain_granule; struct bufchain_granule;
typedef struct bufchain_tag { typedef struct bufchain_tag {
struct bufchain_granule *head, *tail; struct bufchain_granule *head, *tail;

46
psftp.c
View File

@ -24,52 +24,6 @@
* send buffer. * send buffer.
*/ */
/* ----------------------------------------------------------------------
* String handling routines.
*/
char *dupstr(char *s)
{
int len = strlen(s);
char *p = smalloc(len + 1);
strcpy(p, s);
return p;
}
/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
char *dupcat(char *s1, ...)
{
int len;
char *p, *q, *sn;
va_list ap;
len = strlen(s1);
va_start(ap, s1);
while (1) {
sn = va_arg(ap, char *);
if (!sn)
break;
len += strlen(sn);
}
va_end(ap);
p = smalloc(len + 1);
strcpy(p, s1);
q = p + strlen(p);
va_start(ap, s1);
while (1) {
sn = va_arg(ap, char *);
if (!sn)
break;
strcpy(q, sn);
q += strlen(q);
}
va_end(ap);
return p;
}
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
* sftp client state. * sftp client state.
*/ */

170
scp.c
View File

@ -672,6 +672,23 @@ static char *colon(char *str)
return (NULL); return (NULL);
} }
/*
* Return a pointer to the portion of str that comes after the last
* slash or backslash.
*/
static char *stripslashes(char *str)
{
char *p;
p = strrchr(str, '/');
if (p) str = p+1;
p = strrchr(str, '\\');
if (p) str = p+1;
return str;
}
/* /*
* Wait for a response from the other side. * Wait for a response from the other side.
* Return 0 if ok, -1 if error. * Return 0 if ok, -1 if error.
@ -909,7 +926,6 @@ static void run_err(const char *fmt, ...)
*/ */
static void source(char *src) static void source(char *src)
{ {
char buf[2048];
unsigned long size; unsigned long size;
char *last; char *last;
HANDLE f; HANDLE f;
@ -1017,8 +1033,7 @@ static void source(char *src)
*/ */
static void rsource(char *src) static void rsource(char *src)
{ {
char buf[2048]; char *last, *findfile;
char *last;
HANDLE dir; HANDLE dir;
WIN32_FIND_DATA fdat; WIN32_FIND_DATA fdat;
int ok; int ok;
@ -1039,32 +1054,32 @@ static void rsource(char *src)
if (scp_send_dirname(last, 0755)) if (scp_send_dirname(last, 0755))
return; return;
sprintf(buf, "%s/*", src); findfile = dupcat(src, "/*", NULL);
dir = FindFirstFile(buf, &fdat); dir = FindFirstFile(findfile, &fdat);
ok = (dir != INVALID_HANDLE_VALUE); ok = (dir != INVALID_HANDLE_VALUE);
while (ok) { while (ok) {
if (strcmp(fdat.cFileName, ".") == 0 || if (strcmp(fdat.cFileName, ".") == 0 ||
strcmp(fdat.cFileName, "..") == 0) { strcmp(fdat.cFileName, "..") == 0) {
} else if (strlen(src) + 1 + strlen(fdat.cFileName) >= sizeof(buf)) { /* ignore . and .. */
run_err("%s/%s: Name too long", src, fdat.cFileName);
} else { } else {
sprintf(buf, "%s/%s", src, fdat.cFileName); char *foundfile = dupcat(src, "/", fdat.cFileName);
source(buf); source(foundfile);
sfree(foundfile);
} }
ok = FindNextFile(dir, &fdat); ok = FindNextFile(dir, &fdat);
} }
FindClose(dir); FindClose(dir);
sfree(findfile);
(void) scp_send_enddir(); (void) scp_send_enddir();
} }
/* /*
* Execute the sink part of the SCP protocol. * Execute the sink part of the SCP protocol.
*/ */
static void sink(char *targ, char *src) static void sink(char *targ, char *src)
{ {
char buf[2048]; char *destfname;
char namebuf[2048];
char ch; char ch;
int targisdir = 0; int targisdir = 0;
int settime; int settime;
@ -1092,44 +1107,87 @@ static void sink(char *targ, char *src)
if (act.action == SCP_SINK_ENDDIR) if (act.action == SCP_SINK_ENDDIR)
return; return;
/* Security fix: ensure the file ends up where we asked for it. */
if (targisdir) { if (targisdir) {
char t[2048]; /*
char *p; * Prevent the remote side from maliciously writing to
strcpy(t, targ); * files outside the target area by sending a filename
* containing `../'. In fact, it shouldn't be sending
* filenames with any slashes in at all; so we'll find
* the last slash or backslash in the filename and use
* only the part after that. (And warn!)
*
* In addition, we also ensure here that if we're
* copying a single file and the target is a directory
* (common usage: `pscp host:filename .') the remote
* can't send us a _different_ file name. We can
* distinguish this case because `src' will be non-NULL
* and the last component of that will fail to match
* (the last component of) the name sent.
*/
char *striptarget, *stripsrc;
striptarget = stripslashes(act.name);
if (striptarget != act.name) {
tell_user(stderr, "warning: remote host sent a compound"
" pathname - possibly malicious! (ignored)");
}
/*
* Also check to see if the target filename is '.' or
* '..', or indeed '...' and so on because Windows
* appears to interpret those like '..'.
*/
if (striptarget[strspn(striptarget, ".")] == '\0') {
bump("security violation: remote host attempted to write to"
" a '.' or '..' path!");
}
if (src) {
stripsrc = stripslashes(src);
if (strcmp(striptarget, stripsrc)) {
tell_user(stderr, "warning: remote host attempted to"
" write to a different filename: disallowing");
}
/* Override the name the server provided with our own. */
striptarget = stripsrc;
}
if (targ[0] != '\0') if (targ[0] != '\0')
strcat(t, "/"); destfname = dupcat(targ, "\\", striptarget, NULL);
p = act.name + strlen(act.name); else
while (p > act.name && p[-1] != '/' && p[-1] != '\\') destfname = dupstr(striptarget);
p--;
strcat(t, p);
strcpy(namebuf, t);
} else { } else {
strcpy(namebuf, targ); /*
* In this branch of the if, the target area is a
* single file with an explicitly specified name in any
* case, so there's no danger.
*/
destfname = dupstr(targ);
} }
attr = GetFileAttributes(namebuf); attr = GetFileAttributes(destfname);
exists = (attr != (DWORD) - 1); exists = (attr != (DWORD) - 1);
if (act.action == SCP_SINK_DIR) { if (act.action == SCP_SINK_DIR) {
if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
run_err("%s: Not a directory", namebuf); run_err("%s: Not a directory", destfname);
continue; continue;
} }
if (!exists) { if (!exists) {
if (!CreateDirectory(namebuf, NULL)) { if (!CreateDirectory(destfname, NULL)) {
run_err("%s: Cannot create directory", namebuf); run_err("%s: Cannot create directory", destfname);
continue; continue;
} }
} }
sink(namebuf, NULL); sink(destfname, NULL);
/* can we set the timestamp for directories ? */ /* can we set the timestamp for directories ? */
continue; continue;
} }
f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL, f = CreateFile(destfname, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (f == INVALID_HANDLE_VALUE) { if (f == INVALID_HANDLE_VALUE) {
run_err("%s: Cannot create file", namebuf); run_err("%s: Cannot create file", destfname);
continue; continue;
} }
@ -1139,12 +1197,7 @@ static void sink(char *targ, char *src)
stat_bytes = 0; stat_bytes = 0;
stat_starttime = time(NULL); stat_starttime = time(NULL);
stat_lasttime = 0; stat_lasttime = 0;
if ((stat_name = strrchr(namebuf, '/')) == NULL) stat_name = stripslashes(destfname);
stat_name = namebuf;
else
stat_name++;
if (strrchr(stat_name, '\\') != NULL)
stat_name = strrchr(stat_name, '\\') + 1;
received = 0; received = 0;
while (received < act.size) { while (received < act.size) {
@ -1188,10 +1241,12 @@ static void sink(char *targ, char *src)
CloseHandle(f); CloseHandle(f);
if (wrerror) { if (wrerror) {
run_err("%s: Write error", namebuf); run_err("%s: Write error", destfname);
continue; continue;
} }
(void) scp_finish_filerecv(); (void) scp_finish_filerecv();
sfree(destfname);
sfree(act.name);
} }
} }
@ -1255,6 +1310,7 @@ static void toremote(int argc, char *argv[])
(void) response(); (void) response();
for (i = 0; i < argc - 1; i++) { for (i = 0; i < argc - 1; i++) {
char *srcpath, *last;
HANDLE dir; HANDLE dir;
WIN32_FIND_DATA fdat; WIN32_FIND_DATA fdat;
src = argv[i]; src = argv[i];
@ -1263,6 +1319,25 @@ static void toremote(int argc, char *argv[])
errs++; errs++;
continue; continue;
} }
/*
* Trim off the last pathname component of `src', to
* provide the base pathname which will be prepended to
* filenames returned from Find{First,Next}File.
*/
srcpath = dupstr(src);
last = stripslashes(srcpath);
if (last == srcpath) {
last = strchr(srcpath, ':');
if (last)
last++;
else
last = srcpath;
}
printf("src=:%s:\nsrcpath=:%s:\nlast=:%s:\n", src, srcpath, last);
*last = '\0';
printf("srcpath=:%s:\n", srcpath);
dir = FindFirstFile(src, &fdat); dir = FindFirstFile(src, &fdat);
if (dir == INVALID_HANDLE_VALUE) { if (dir == INVALID_HANDLE_VALUE) {
run_err("%s: No such file or directory", src); run_err("%s: No such file or directory", src);
@ -1270,7 +1345,7 @@ static void toremote(int argc, char *argv[])
} }
do { do {
char *last; char *last;
char namebuf[2048]; char *filename;
/* /*
* Ensure that . and .. are never matched by wildcards, * Ensure that . and .. are never matched by wildcards,
* but only by deliberate action. * but only by deliberate action.
@ -1292,23 +1367,12 @@ static void toremote(int argc, char *argv[])
} else } else
continue; /* ignore this one */ continue; /* ignore this one */
} }
if (strlen(src) + strlen(fdat.cFileName) >= sizeof(namebuf)) { filename = dupcat(srcpath, fdat.cFileName, NULL);
tell_user(stderr, "%s: Name too long", src); source(filename);
continue; sfree(filename);
}
strcpy(namebuf, src);
if ((last = strrchr(namebuf, '/')) == NULL)
last = namebuf;
else
last++;
if (strrchr(last, '\\') != NULL)
last = strrchr(last, '\\') + 1;
if (last == namebuf && strrchr(namebuf, ':') != NULL)
last = strchr(namebuf, ':') + 1;
strcpy(last, fdat.cFileName);
source(namebuf);
} while (FindNextFile(dir, &fdat)); } while (FindNextFile(dir, &fdat));
FindClose(dir); FindClose(dir);
sfree(srcpath);
} }
} }