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

Fall back to not sorting large dirs in pscp -ls or psftp 'ls'.

This mitigates a borderline-DoS in which a malicious SFTP server sends
a ludicrously large number of file names in response to a SFTP
opendir/readdir request sequence, causing the client to buffer them
all and use up all the system's memory simply so that it can produce
the output in sorted order.

I call it a 'borderline' DoS because it's very likely that this is the
same server that you'll also trust to actually send you the _contents_
of some entire file or directory, in which case, if they want to DoS
you they can do that anyway at that point and you have no way to tell
a legit very large file from a bad one. So it's unclear to me that
anyone would get any real advantage out of 'exploiting' this that they
couldn't have got anyway by other means.

That said, it may have practical benefits in the occasional case.
Imagine a _legit_ gigantic directory (something like a maildir,
perhaps, and perhaps stored on a server-side filesystem specialising
in not choking on really huge single directories), together with a
client workflow that involves listing the whole directory but then
downloading only one particular file in it.

For the moment, the threshold size is fixed at 8Mb of total data
(counting the lengths of the file names as well as just the number of
files). If that needs to become configurable later, we can always add
an option.
This commit is contained in:
Simon Tatham 2019-05-15 14:57:06 +01:00
parent dbd9a07fd0
commit 70fd577e40
5 changed files with 164 additions and 69 deletions

2
Recipe
View File

@ -279,7 +279,7 @@ WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc
UXSSH = SSH uxnoise uxagentc uxgss uxshare
# SFTP implementation (pscp, psftp).
SFTP = sftp sftpcommon logging cmdline
SFTP = psftpcommon sftp sftpcommon logging cmdline
# Miscellaneous objects appearing in all the utilities, or all the
# network ones, or the Unix or Windows subsets of those in turn.

45
pscp.c
View File

@ -599,21 +599,24 @@ size_t sftp_sendbuffer(void)
/* ----------------------------------------------------------------------
* sftp-based replacement for the hacky `pscp -ls'.
*/
static int sftp_ls_compare(const void *av, const void *bv)
void list_directory_from_sftp_warn_unsorted(void)
{
const struct fxp_name *a = (const struct fxp_name *) av;
const struct fxp_name *b = (const struct fxp_name *) bv;
return strcmp(a->filename, b->filename);
fprintf(stderr,
"Directory is too large to sort; writing file names unsorted\n");
}
void list_directory_from_sftp_print(struct fxp_name *name)
{
with_stripctrl(san, name->longname)
printf("%s\n", san);
}
void scp_sftp_listdir(const char *dirname)
{
struct fxp_handle *dirh;
struct fxp_names *names;
struct fxp_name *ournames;
struct sftp_packet *pktin;
struct sftp_request *req;
size_t nnames, namesize;
int i;
if (!fxp_init()) {
tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
@ -631,8 +634,8 @@ void scp_sftp_listdir(const char *dirname)
tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error());
errs++;
} else {
nnames = namesize = 0;
ournames = NULL;
struct list_directory_from_sftp_ctx *ctx =
list_directory_from_sftp_new();
while (1) {
@ -651,33 +654,17 @@ void scp_sftp_listdir(const char *dirname)
break;
}
sgrowarrayn(ournames, namesize, nnames, names->nnames);
for (size_t i = 0; i < names->nnames; i++)
list_directory_from_sftp_feed(ctx, &names->names[i]);
for (i = 0; i < names->nnames; i++)
ournames[nnames++] = names->names[i];
names->nnames = 0; /* prevent free_names */
fxp_free_names(names);
}
req = fxp_close_send(dirh);
pktin = sftp_wait_for_reply(req);
fxp_close_recv(pktin, req);
/*
* Now we have our filenames. Sort them by actual file
* name, and then output the longname parts.
*/
if (nnames > 0)
qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
/*
* And print them.
*/
for (i = 0; i < nnames; i++) {
with_stripctrl(san, ournames[i].longname)
printf("%s\n", san);
}
sfree(ournames);
list_directory_from_sftp_finish(ctx);
list_directory_from_sftp_free(ctx);
}
}

54
psftp.c
View File

@ -211,20 +211,6 @@ char *canonify(const char *name)
}
}
/*
* qsort comparison routine for fxp_name structures. Sorts by real
* file name.
*/
static int sftp_name_compare(const void *av, const void *bv)
{
const struct fxp_name *const *a = (const struct fxp_name *const *) av;
const struct fxp_name *const *b = (const struct fxp_name *const *) bv;
return strcmp((*a)->filename, (*b)->filename);
}
/*
* Likewise, but for a bare char *.
*/
static int bare_name_compare(const void *av, const void *bv)
{
const char **a = (const char **) av;
@ -1024,6 +1010,17 @@ int sftp_cmd_close(struct sftp_command *cmd)
return 0;
}
void list_directory_from_sftp_warn_unsorted(void)
{
printf("Directory is too large to sort; writing file names unsorted\n");
}
void list_directory_from_sftp_print(struct fxp_name *name)
{
with_stripctrl(san, name->longname)
printf("%s\n", san);
}
/*
* List a directory. If no arguments are given, list pwd; otherwise
* list the directory given in words[1].
@ -1032,8 +1029,6 @@ int sftp_cmd_ls(struct sftp_command *cmd)
{
struct fxp_handle *dirh;
struct fxp_names *names;
struct fxp_name **ournames;
size_t nnames, namesize;
const char *dir;
char *cdir, *unwcdir, *wildcard;
struct sftp_packet *pktin;
@ -1091,8 +1086,8 @@ int sftp_cmd_ls(struct sftp_command *cmd)
sfree(unwcdir);
return 0;
} else {
nnames = namesize = 0;
ournames = NULL;
struct list_directory_from_sftp_ctx *ctx =
list_directory_from_sftp_new();
while (1) {
@ -1111,34 +1106,19 @@ int sftp_cmd_ls(struct sftp_command *cmd)
break;
}
sgrowarrayn(ournames, namesize, nnames, names->nnames);
for (size_t i = 0; i < names->nnames; i++)
if (!wildcard || wc_match(wildcard, names->names[i].filename))
ournames[nnames++] = fxp_dup_name(&names->names[i]);
list_directory_from_sftp_feed(ctx, &names->names[i]);
fxp_free_names(names);
}
req = fxp_close_send(dirh);
pktin = sftp_wait_for_reply(req);
fxp_close_recv(pktin, req);
/*
* Now we have our filenames. Sort them by actual file
* name, and then output the longname parts.
*/
if (nnames > 0)
qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);
/*
* And print them.
*/
for (size_t i = 0; i < nnames; i++) {
with_stripctrl(san, ournames[i]->longname)
printf("%s\n", san);
fxp_free_name(ournames[i]);
}
sfree(ournames);
list_directory_from_sftp_finish(ctx);
list_directory_from_sftp_free(ctx);
}
sfree(cdir);

30
psftp.h
View File

@ -1,6 +1,6 @@
/*
* psftp.h: interface between psftp.c / scp.c and each
* platform-specific SFTP module.
* psftp.h: interface between psftp.c / pscp.c, psftpcommon.c, and
* each platform-specific SFTP module.
*/
#ifndef PUTTY_PSFTP_H
@ -198,4 +198,30 @@ char *dir_file_cat(const char *dir, const char *file);
*/
char *stripslashes(const char *str, bool local);
/* ----------------------------------------------------------------------
* In psftpcommon.c
*/
/*
* qsort comparison routine for fxp_name structures. Sorts by real
* file name.
*/
int sftp_name_compare(const void *av, const void *bv);
/*
* Shared code for outputting a directory listing in response to a
* stream of name structures from FXP_READDIR operations. Used by
* psftp's ls command and pscp -ls.
*/
struct list_directory_from_sftp_ctx;
struct fxp_name; /* in sftp.h */
struct list_directory_from_sftp_ctx *list_directory_from_sftp_new(void);
void list_directory_from_sftp_feed(struct list_directory_from_sftp_ctx *ctx,
struct fxp_name *name);
void list_directory_from_sftp_finish(struct list_directory_from_sftp_ctx *ctx);
void list_directory_from_sftp_free(struct list_directory_from_sftp_ctx *ctx);
/* Callbacks provided by the tool front end */
void list_directory_from_sftp_warn_unsorted(void);
void list_directory_from_sftp_print(struct fxp_name *name);
#endif /* PUTTY_PSFTP_H */

102
psftpcommon.c Normal file
View File

@ -0,0 +1,102 @@
/*
* psftpcommon.c: front-end functionality shared between both file
* transfer tools across platforms. (As opposed to sftpcommon.c, which
* has *protocol*-level common code.)
*/
#include <stdlib.h>
#include <string.h>
#include "putty.h"
#include "sftp.h"
#include "psftp.h"
#define MAX_NAMES_MEMORY ((size_t)8 << 20)
/*
* qsort comparison routine for fxp_name structures. Sorts by real
* file name.
*/
int sftp_name_compare(const void *av, const void *bv)
{
const struct fxp_name *const *a = (const struct fxp_name *const *) av;
const struct fxp_name *const *b = (const struct fxp_name *const *) bv;
return strcmp((*a)->filename, (*b)->filename);
}
struct list_directory_from_sftp_ctx {
size_t nnames, namesize, total_memory;
struct fxp_name **names;
bool sorting;
};
struct list_directory_from_sftp_ctx *list_directory_from_sftp_new(void)
{
struct list_directory_from_sftp_ctx *ctx =
snew(struct list_directory_from_sftp_ctx);
memset(ctx, 0, sizeof(*ctx));
ctx->sorting = true;
return ctx;
}
void list_directory_from_sftp_free(struct list_directory_from_sftp_ctx *ctx)
{
for (size_t i = 0; i < ctx->nnames; i++)
fxp_free_name(ctx->names[i]);
sfree(ctx->names);
sfree(ctx);
}
void list_directory_from_sftp_feed(struct list_directory_from_sftp_ctx *ctx,
struct fxp_name *name)
{
if (ctx->sorting) {
/*
* Accumulate these filenames into an array that we'll sort -
* unless the array gets _really_ big, in which case, to avoid
* consuming all the client's memory, we fall back to
* outputting the directory listing unsorted.
*/
size_t this_name_memory =
sizeof(*ctx->names) + sizeof(**ctx->names) +
strlen(name->filename) +
strlen(name->longname);
if (MAX_NAMES_MEMORY - ctx->total_memory < this_name_memory) {
list_directory_from_sftp_warn_unsorted();
/* Output all the previously stored names. */
for (size_t i = 0; i < ctx->nnames; i++) {
list_directory_from_sftp_print(ctx->names[i]);
fxp_free_name(ctx->names[i]);
}
/* Don't store further names in that array. */
sfree(ctx->names);
ctx->names = NULL;
ctx->nnames = 0;
ctx->namesize = 0;
ctx->sorting = false;
/* And don't forget to output the name passed in this
* actual function call. */
list_directory_from_sftp_print(name);
} else {
sgrowarray(ctx->names, ctx->namesize, ctx->nnames);
ctx->names[ctx->nnames++] = fxp_dup_name(name);
ctx->total_memory += this_name_memory;
}
} else {
list_directory_from_sftp_print(name);
}
}
void list_directory_from_sftp_finish(struct list_directory_from_sftp_ctx *ctx)
{
if (ctx->nnames > 0) {
assert(ctx->sorting);
qsort(ctx->names, ctx->nnames, sizeof(*ctx->names), sftp_name_compare);
for (size_t i = 0; i < ctx->nnames; i++)
list_directory_from_sftp_print(ctx->names[i]);
}
}