ssh-keyscan bug (not really exploitable)
Joachim Schipper
joachim at joachimschipper.nl
Sun Mar 7 08:05:49 EST 2010
ssh-keyscan may, under very specific circumstances, be vulnerable to
something akin to a buffer overflow. It's probably impossible to
exploit, though, if only because ssh-keyscan is not usually run on very
large untrusted input files.
ssh-keyscan uses an fgets() wrapper that uses an unsigned int to keep
track of the length of a buffer holding the current line. On machines
with sufficient address space to hold UINT_MAX / 2 bytes, e.g. amd64,
one can fill those bytes. The next call will be realloc(buf, 0), which
free()s the buffer and returns a non-NULL zero-size chunk of memory. The
program will then try to write to some point in memory about UINT_MAX /
2 past this newly-returned chunk.
Test case:
$ while true; do echo -n 'AAAAAAAAAAAAAAAA'; done | ssh-keyscan -f -
I stumbled upon this a while ago; I was trying to solve
https://bugzilla.mindrot.org/show_bug.cgi?id=1565 ("ssh-keyscan doesn't
like comment-lines"). The patch below rips out the fgets() wrapper and
correctly handles both comments and ridiculously long lines.
Joachim
Index: ssh-keyscan.c
===================================================================
RCS file: /usr/cvs/src/src/usr.bin/ssh/ssh-keyscan.c,v
retrieving revision 1.81
diff -u -p -r1.81 ssh-keyscan.c
--- ssh-keyscan.c 9 Jan 2010 23:04:13 -0000 1.81
+++ ssh-keyscan.c 6 Mar 2010 20:33:34 -0000
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keyscan.c,v 1.78 2009/01/22 10:02:34 djm Exp $ */
+/* $OpenBSD: ssh-keyscan.c,v 1.81 2010/01/09 23:04:13 dtucker Exp $ */
/*
* Copyright 1995, 1996 by David Mazieres <dm at lcs.mit.edu>.
*
@@ -99,122 +99,6 @@ typedef struct Connection {
TAILQ_HEAD(conlist, Connection) tq; /* Timeout Queue */
con *fdcon;
-/*
- * This is just a wrapper around fgets() to make it usable.
- */
-
-/* Stress-test. Increase this later. */
-#define LINEBUF_SIZE 16
-
-typedef struct {
- char *buf;
- u_int size;
- int lineno;
- const char *filename;
- FILE *stream;
- void (*errfun) (const char *,...);
-} Linebuf;
-
-static Linebuf *
-Linebuf_alloc(const char *filename, void (*errfun) (const char *,...))
-{
- Linebuf *lb;
-
- if (!(lb = malloc(sizeof(*lb)))) {
- if (errfun)
- (*errfun) ("linebuf (%s): malloc failed\n",
- filename ? filename : "(stdin)");
- return (NULL);
- }
- if (filename) {
- lb->filename = filename;
- if (!(lb->stream = fopen(filename, "r"))) {
- xfree(lb);
- if (errfun)
- (*errfun) ("%s: %s\n", filename, strerror(errno));
- return (NULL);
- }
- } else {
- lb->filename = "(stdin)";
- lb->stream = stdin;
- }
-
- if (!(lb->buf = malloc((lb->size = LINEBUF_SIZE)))) {
- if (errfun)
- (*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
- xfree(lb);
- return (NULL);
- }
- lb->errfun = errfun;
- lb->lineno = 0;
- return (lb);
-}
-
-static void
-Linebuf_free(Linebuf * lb)
-{
- fclose(lb->stream);
- xfree(lb->buf);
- xfree(lb);
-}
-
-#if 0
-static void
-Linebuf_restart(Linebuf * lb)
-{
- clearerr(lb->stream);
- rewind(lb->stream);
- lb->lineno = 0;
-}
-
-static int
-Linebuf_lineno(Linebuf * lb)
-{
- return (lb->lineno);
-}
-#endif
-
-static char *
-Linebuf_getline(Linebuf * lb)
-{
- size_t n = 0;
- void *p;
-
- lb->lineno++;
- for (;;) {
- /* Read a line */
- if (!fgets(&lb->buf[n], lb->size - n, lb->stream)) {
- if (ferror(lb->stream) && lb->errfun)
- (*lb->errfun)("%s: %s\n", lb->filename,
- strerror(errno));
- return (NULL);
- }
- n = strlen(lb->buf);
-
- /* Return it or an error if it fits */
- if (n > 0 && lb->buf[n - 1] == '\n') {
- lb->buf[n - 1] = '\0';
- return (lb->buf);
- }
- if (n != lb->size - 1) {
- if (lb->errfun)
- (*lb->errfun)("%s: skipping incomplete last line\n",
- lb->filename);
- return (NULL);
- }
- /* Double the buffer if we need more space */
- lb->size *= 2;
- if ((p = realloc(lb->buf, lb->size)) == NULL) {
- lb->size /= 2;
- if (lb->errfun)
- (*lb->errfun)("linebuf (%s): realloc failed\n",
- lb->filename);
- return (NULL);
- }
- lb->buf = p;
- }
-}
-
static int
fdlim_get(int hard)
{
@@ -709,8 +593,10 @@ int
main(int argc, char **argv)
{
int debug_flag = 0, log_level = SYSLOG_LEVEL_INFO;
- int opt, fopt_count = 0;
- char *tname;
+ int opt, fopt_count = 0, j;
+ char *tname, *line;
+ size_t i, line_len;
+ FILE *fp;
extern int optind;
extern char *optarg;
@@ -808,20 +694,52 @@ main(int argc, char **argv)
read_wait_nfdset = howmany(maxfd, NFDBITS);
read_wait = xcalloc(read_wait_nfdset, sizeof(fd_mask));
- if (fopt_count) {
- Linebuf *lb;
- char *line;
- int j;
-
- for (j = 0; j < fopt_count; j++) {
- lb = Linebuf_alloc(argv[j], error);
- if (!lb)
+ line = NULL;
+
+ for (j = 0; j < fopt_count; j++) {
+ if (line == NULL)
+ line = xmalloc(line_len = BUFSIZ);
+
+ if ((fp = fopen(argv[j], "r")) == NULL)
+ fatal("%s: %s: %s", __progname, argv[j],
+ strerror(errno));
+
+ i = 0;
+ while (fgets(&line[i], line_len - i, fp)) {
+ /* Read a line */
+ i += strcspn(&line[i], "\n");
+ if (line[i] == '\0' && i == line_len - 1) {
+ if (line_len > SIZE_MAX / 2)
+ fatal("%s: %s: line %.20s too long",
+ __progname, argv[j], line);
+ line = xrealloc(line, line_len *= 2, 1);
+ continue;
+ }
+
+ /* Strip off comments and whitespace at end */
+ for (i = strcspn(line, "#\n");
+ i > 0 && strchr(" \t", line[i - 1]);
+ i--);
+ line[i] = '\0';
+
+ /* Skip empty lines, comments */
+ if (i == 0)
continue;
- while ((line = Linebuf_getline(lb)) != NULL)
- do_host(line);
- Linebuf_free(lb);
+
+ do_host(line);
+
+ i = 0;
}
+
+ if (ferror(fp))
+ fatal("%s: %s: %s", __progname, argv[j],
+ strerror(errno));
+
+ fclose(fp);
}
+
+ if (line)
+ xfree(line);
while (optind < argc)
do_host(argv[optind++]);
More information about the openssh-unix-dev
mailing list