commit ed0d9bd45e4d55cb3ca600c70fa26c6b7bf9bf05
Author: Michael Buckley <michael@panic.com>
Date:   Wed Dec 5 10:35:19 2018 -0800

    Add a required_size parameter to sftp_packet_require et. al. to require callers of these functions to handle packets that are too short.

diff --git src/sftp.c src/sftp.c
index 5316e7c..fd94d39 100644
--- src/sftp.c
+++ src/sftp.c
@@ -512,11 +512,15 @@ sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type,
 static int
 sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type,
                     uint32_t request_id, unsigned char **data,
-                    size_t *data_len)
+                    size_t *data_len, size_t required_size)
 {
     LIBSSH2_SESSION *session = sftp->channel->session;
     int rc;
 
+    if (data == NULL || data_len == NULL || required_size == 0) {
+        return LIBSSH2_ERROR_BAD_USE;
+    }
+
     _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Requiring packet %d id %ld",
                    (int) packet_type, request_id);
 
@@ -524,6 +528,11 @@ sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type,
         /* The right packet was available in the packet brigade */
         _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Got %d",
                        (int) packet_type);
+
+        if (*data_len < required_size) {
+            return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+        }
+
         return LIBSSH2_ERROR_NONE;
     }
 
@@ -537,6 +546,11 @@ sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type,
             /* The right packet was available in the packet brigade */
             _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Got %d",
                            (int) packet_type);
+
+            if (*data_len < required_size) {
+                return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+            }
+
             return LIBSSH2_ERROR_NONE;
         }
     }
@@ -552,11 +566,15 @@ static int
 sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses,
                      const unsigned char *valid_responses,
                      uint32_t request_id, unsigned char **data,
-                     size_t *data_len)
+                     size_t *data_len, size_t required_size)
 {
     int i;
     int rc;
 
+    if (data == NULL || data_len == NULL || required_size == 0) {
+        return LIBSSH2_ERROR_BAD_USE;
+    }
+
     /* If no timeout is active, start a new one */
     if (sftp->requirev_start == 0)
         sftp->requirev_start = time(NULL);
@@ -570,6 +588,11 @@ sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses,
                  * the timeout is not active
                  */
                 sftp->requirev_start = 0;
+
+                if (*data_len < required_size) {
+                    return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+                }
+
                 return LIBSSH2_ERROR_NONE;
             }
         }
@@ -644,36 +667,65 @@ sftp_attr2bin(unsigned char *p, const LIBSSH2_SFTP_ATTRIBUTES * attrs)
 /* sftp_bin2attr
  */
 static int
-sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES * attrs, const unsigned char *p)
+sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES * attrs, const unsigned char *p, size_t data_len)
 {
     const unsigned char *s = p;
 
-    memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
-    attrs->flags = _libssh2_ntohu32(s);
-    s += 4;
+    if (data_len >= 4) {
+        memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
+        attrs->flags = _libssh2_ntohu32(s);
+        s += 4;
+        data_len -= 4;
+    }
+    else {
+        return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+    }
 
     if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) {
-        attrs->filesize = _libssh2_ntohu64(s);
-        s += 8;
+        if (data_len >= 8) {
+            attrs->filesize = _libssh2_ntohu64(s);
+            s += 8;
+            data_len -= 8;
+        }
+        else {
+            return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+        }
     }
 
     if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) {
-        attrs->uid = _libssh2_ntohu32(s);
-        s += 4;
-        attrs->gid = _libssh2_ntohu32(s);
-        s += 4;
+        if (data_len >= 8) {
+            attrs->uid = _libssh2_ntohu32(s);
+            s += 4;
+            attrs->gid = _libssh2_ntohu32(s);
+            s += 4;
+            data_len -= 8;
+        }
+        else {
+            return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+        }
     }
 
     if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
-        attrs->permissions = _libssh2_ntohu32(s);
-        s += 4;
+        if (data_len >= 4) {
+            attrs->permissions = _libssh2_ntohu32(s);
+            s += 4;
+            data_len -= 4;
+        }
+        else {
+            return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+        }
     }
 
     if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) {
-        attrs->atime = _libssh2_ntohu32(s);
-        s += 4;
-        attrs->mtime = _libssh2_ntohu32(s);
-        s += 4;
+        if (data_len >= 8) {
+            attrs->atime = _libssh2_ntohu32(s);
+            s += 4;
+            attrs->mtime = _libssh2_ntohu32(s);
+            s += 4;
+        }
+        else {
+            return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
+        }
     }
 
     return (s - p);
@@ -843,18 +895,23 @@ static LIBSSH2_SFTP *sftp_init(LIBSSH2_SESSION *session)
     }
 
     rc = sftp_packet_require(sftp_handle, SSH_FXP_VERSION,
-                             0, &data, &data_len);
-    if (rc == LIBSSH2_ERROR_EAGAIN)
+                             0, &data, &data_len, 5);
+    if (rc == LIBSSH2_ERROR_EAGAIN) {
+        _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+                       "Would block receiving SSH_FXP_VERSION");
         return NULL;
-    else if (rc) {
-        _libssh2_error(session, rc,
-                       "Timeout waiting for response from SFTP subsystem");
-        goto sftp_init_error;
     }
-    if (data_len < 5) {
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
         _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
                        "Invalid SSH_FXP_VERSION response");
-        LIBSSH2_FREE(session, data);
+        goto sftp_init_error;
+    }
+    else if (rc) {
+        _libssh2_error(session, rc,
+                       "Timeout waiting for response from SFTP subsystem");
         goto sftp_init_error;
     }
 
@@ -1120,12 +1177,20 @@ sftp_open(LIBSSH2_SFTP *sftp, const char *filename,
             { SSH_FXP_HANDLE, SSH_FXP_STATUS };
         rc = sftp_packet_requirev(sftp, 2, fopen_responses,
                                   sftp->open_request_id, &data,
-                                  &data_len);
+                                  &data_len, 1);
         if (rc == LIBSSH2_ERROR_EAGAIN) {
             _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
                            "Would block waiting for status message");
             return NULL;
         }
+        else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+            if (data_len > 0) {
+                LIBSSH2_FREE(session, data);
+            }
+            _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                           "Response too small");
+            return NULL;
+        }
         sftp->open_state = libssh2_NB_state_idle;
         if (rc) {
             _libssh2_error(session, rc, "Timeout waiting for status message");
@@ -1156,12 +1221,20 @@ sftp_open(LIBSSH2_SFTP *sftp, const char *filename,
                 /* silly situation, but check for a HANDLE */
                 rc = sftp_packet_require(sftp, SSH_FXP_HANDLE,
                                          sftp->open_request_id, &data,
-                                         &data_len);
+                                         &data_len, 10);
                 if(rc == LIBSSH2_ERROR_EAGAIN) {
                     /* go back to sent state and wait for something else */
                     sftp->open_state = libssh2_NB_state_sent;
                     return NULL;
                 }
+                else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+                    if (data_len > 0) {
+                        LIBSSH2_FREE(session, data);
+                    }
+                    _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                                   "Too small FXP_HANDLE");
+                    return NULL;
+                }
                 else if(!rc)
                     /* we got the handle so this is not a bad situation */
                     badness = 0;
@@ -1488,15 +1561,21 @@ static ssize_t sftp_read(LIBSSH2_SFTP_HANDLE * handle, char *buffer,
             }
 
             rc = sftp_packet_requirev(sftp, 2, read_responses,
-                                      chunk->request_id, &data, &data_len);
-
-            if (rc==LIBSSH2_ERROR_EAGAIN && bytes_in_buffer != 0) {
+                                      chunk->request_id, &data, &data_len, 9);
+            if (rc == LIBSSH2_ERROR_EAGAIN && bytes_in_buffer != 0) {
                 /* do not return EAGAIN if we have already
                  * written data into the buffer */
                 return bytes_in_buffer;
             }
 
-            if (rc < 0) {
+            if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+                if (data_len > 0) {
+                    LIBSSH2_FREE(session, data);
+                }
+                return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                                      "Response too small");
+            }
+            else if(rc < 0) {
                 sftp->read_state = libssh2_NB_state_sent2;
                 return rc;
             }
@@ -1706,7 +1785,7 @@ static ssize_t sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer,
             if (attrs)
                 memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
 
-            s += sftp_bin2attr(attrs ? attrs : &attrs_dummy, s);
+            s += sftp_bin2attr(attrs ? attrs : &attrs_dummy, s, 32);
 
             handle->u.dir.next_name = (char *) s;
           end:
@@ -1761,9 +1840,16 @@ static ssize_t sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer,
 
     retcode = sftp_packet_requirev(sftp, 2, read_responses,
                                    sftp->readdir_request_id, &data,
-                                   &data_len);
+                                   &data_len, 9);
     if (retcode == LIBSSH2_ERROR_EAGAIN)
         return retcode;
+    else if (retcode == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+            return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                                  "Status message too short");
+    }
     else if (retcode) {
         sftp->readdir_state = libssh2_NB_state_idle;
         return _libssh2_error(session, retcode,
@@ -1989,8 +2075,15 @@ static ssize_t sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer,
 
             /* we check the packets in order */
             rc = sftp_packet_require(sftp, SSH_FXP_STATUS,
-                                     chunk->request_id, &data, &data_len);
-            if (rc < 0) {
+                                     chunk->request_id, &data, &data_len, 9);
+            if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+                if (data_len > 0) {
+                    LIBSSH2_FREE(session, data);
+                }
+                return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                                      "FXP write packet too short");
+            }
+            else if (rc < 0) {
                 if (rc == LIBSSH2_ERROR_EAGAIN)
                     sftp->write_state = libssh2_NB_state_sent;
                 return rc;
@@ -2132,10 +2225,18 @@ static int sftp_fsync(LIBSSH2_SFTP_HANDLE *handle)
     }
 
     rc = sftp_packet_require(sftp, SSH_FXP_STATUS,
-                             sftp->fsync_request_id, &data, &data_len);
+                             sftp->fsync_request_id, &data, &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
-    } else if (rc) {
+    }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP fsync packet too short");
+    }
+    else if (rc) {
         sftp->fsync_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
                               "Error waiting for FXP EXTENDED REPLY");
@@ -2235,9 +2336,16 @@ static int sftp_fstat(LIBSSH2_SFTP_HANDLE *handle,
 
     rc = sftp_packet_requirev(sftp, 2, fstat_responses,
                               sftp->fstat_request_id, &data,
-                              &data_len);
+                              &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN)
         return rc;
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP fstat packet too short");
+    }
     else if (rc) {
         sftp->fstat_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
@@ -2260,7 +2368,12 @@ static int sftp_fstat(LIBSSH2_SFTP_HANDLE *handle,
         }
     }
 
-    sftp_bin2attr(attrs, data + 5);
+    if (sftp_bin2attr(attrs, data + 5, data_len - 5) < 0) {
+        LIBSSH2_FREE(session, data);
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "Attributes too short in SFTP fstat");
+    }
+
     LIBSSH2_FREE(session, data);
 
     return 0;
@@ -2437,11 +2550,19 @@ sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle)
     if (handle->close_state == libssh2_NB_state_sent) {
         rc = sftp_packet_require(sftp, SSH_FXP_STATUS,
                                  handle->close_request_id, &data,
-                                 &data_len);
+                                 &data_len, 9);
         if (rc == LIBSSH2_ERROR_EAGAIN) {
             return rc;
-
-        } else if (rc) {
+        }
+        else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+            if (data_len > 0) {
+                LIBSSH2_FREE(session, data);
+            }
+            data = NULL;
+            _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                           "Packet too short in FXP_CLOSE command");
+        }
+        else if (rc) {
             _libssh2_error(session, rc,
                            "Error waiting for status message");
         }
@@ -2555,10 +2676,17 @@ static int sftp_unlink(LIBSSH2_SFTP *sftp, const char *filename,
 
     rc = sftp_packet_require(sftp, SSH_FXP_STATUS,
                              sftp->unlink_request_id, &data,
-                             &data_len);
+                             &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
     }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP unlink packet too short");
+    }
     else if (rc) {
         sftp->unlink_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
@@ -2666,10 +2794,18 @@ static int sftp_rename(LIBSSH2_SFTP *sftp, const char *source_filename,
 
     rc = sftp_packet_require(sftp, SSH_FXP_STATUS,
                              sftp->rename_request_id, &data,
-                             &data_len);
+                             &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
-    } else if (rc) {
+    }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP rename packet too short");
+    }
+    else if (rc) {
         sftp->rename_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
                               "Error waiting for FXP STATUS");
@@ -2791,11 +2927,19 @@ static int sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st)
     }
 
     rc = sftp_packet_requirev(sftp, 2, responses, sftp->fstatvfs_request_id,
-                              &data, &data_len);
+                              &data, &data_len, 9);
 
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
-    } else if (rc) {
+    }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP rename packet too short");
+    }
+    else if (rc) {
         sftp->fstatvfs_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
                               "Error waiting for FXP EXTENDED REPLY");
@@ -2918,10 +3062,18 @@ static int sftp_statvfs(LIBSSH2_SFTP *sftp, const char *path,
     }
 
     rc = sftp_packet_requirev(sftp, 2, responses, sftp->statvfs_request_id,
-                              &data, &data_len);
+                              &data, &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
-    } else if (rc) {
+    }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP fstat packet too short");
+    }
+    else if (rc) {
         sftp->statvfs_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
                               "Error waiting for FXP EXTENDED REPLY");
@@ -3048,10 +3200,18 @@ static int sftp_mkdir(LIBSSH2_SFTP *sftp, const char *path,
     }
 
     rc = sftp_packet_require(sftp, SSH_FXP_STATUS, sftp->mkdir_request_id,
-                             &data, &data_len);
+                             &data, &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
-    } else if (rc) {
+    }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP mkdir packet too short");
+    }
+    else if (rc) {
         sftp->mkdir_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
                               "Error waiting for FXP STATUS");
@@ -3142,10 +3302,18 @@ static int sftp_rmdir(LIBSSH2_SFTP *sftp, const char *path,
     }
 
     rc = sftp_packet_require(sftp, SSH_FXP_STATUS,
-                             sftp->rmdir_request_id, &data, &data_len);
+                             sftp->rmdir_request_id, &data, &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN) {
         return rc;
-    } else if (rc) {
+    }
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP rmdir packet too short");
+    }
+    else if (rc) {
         sftp->rmdir_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
                               "Error waiting for FXP STATUS");
@@ -3255,9 +3423,16 @@ static int sftp_stat(LIBSSH2_SFTP *sftp, const char *path,
     }
 
     rc = sftp_packet_requirev(sftp, 2, stat_responses,
-                              sftp->stat_request_id, &data, &data_len);
+                              sftp->stat_request_id, &data, &data_len, 9);
     if (rc == LIBSSH2_ERROR_EAGAIN)
         return rc;
+    else if (rc == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP stat packet too short");
+    }
     else if (rc) {
         sftp->stat_state = libssh2_NB_state_idle;
         return _libssh2_error(session, rc,
@@ -3281,7 +3456,12 @@ static int sftp_stat(LIBSSH2_SFTP *sftp, const char *path,
     }
 
     memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
-    sftp_bin2attr(attrs, data + 5);
+    if (sftp_bin2attr(attrs, data + 5, data_len - 5) < 0) {
+        LIBSSH2_FREE(session, data);
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "Attributes too short in SFTP fstat");
+    }
+
     LIBSSH2_FREE(session, data);
 
     return 0;
@@ -3386,9 +3566,16 @@ static int sftp_symlink(LIBSSH2_SFTP *sftp, const char *path,
 
     retcode = sftp_packet_requirev(sftp, 2, link_responses,
                                    sftp->symlink_request_id, &data,
-                                   &data_len);
+                                   &data_len, 9);
     if (retcode == LIBSSH2_ERROR_EAGAIN)
         return retcode;
+    else if (retcode == LIBSSH2_ERROR_OUT_OF_BOUNDARY) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP symlink packet too short");
+    }
     else if (retcode) {
         sftp->symlink_state = libssh2_NB_state_idle;
         return _libssh2_error(session, retcode,
@@ -3418,6 +3605,14 @@ static int sftp_symlink(LIBSSH2_SFTP *sftp, const char *path,
                               "no name entries");
     }
 
+    if (data_len < 13) {
+        if (data_len > 0) {
+            LIBSSH2_FREE(session, data);
+        }
+        return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL,
+                              "SFTP stat packet too short");
+    }
+
     /* this reads a u32 and stores it into a signed 32bit value */
     link_len = _libssh2_ntohu32(data + 9);
     if (link_len < target_len) {
