mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-08 08:58:00 +00:00
70fd577e40
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.
103 lines
3.1 KiB
C
103 lines
3.1 KiB
C
/*
|
|
* 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]);
|
|
}
|
|
}
|