4242# define SSHBUF_TELL (what )
4343#endif
4444
45- #ifdef SSHBUF_DEBUG
46- # define SSHBUF_TELL (what ) do { \
47- printf("%s:%d %s: %s size %zu alloc %zu off %zu max %zu\n", \
48- __FILE__, __LINE__, __func__, what, \
49- buf->size, buf->alloc, buf->off, buf->max_size); \
50- fflush(stdout); \
51- } while (0)
52- #else
53- # define SSHBUF_TELL (what )
54- #endif
55-
5645struct sshbuf {
5746 u_char * d ; /* Data */
5847 const u_char * cd ; /* Const data */
5948 size_t off ; /* First available byte is buf->d + buf->off */
6049 size_t size ; /* Last byte is buf->d + buf->size - 1 */
6150 size_t max_size ; /* Maximum size of buffer */
62- size_t window_max ; /* channel window max */
63- size_t alloc ; /* Total bytes allocated to buf->d */
51+ size_t alloc ; /* Total bytes allocated to buf->d */
6452 int readonly ; /* Refers to external, const data */
6553 u_int refcount ; /* Tracks self and number of child buffers */
6654 struct sshbuf * parent ; /* If child, pointer to parent */
6755 char label [MAX_LABEL_LEN ]; /* String for buffer label - debugging use */
68- struct timeval buf_ts ; /* creation time of buffer */
56+ int type ; /* type of buffer enum (sshbuf_types) */
6957};
7058
59+ /* update the label string for a given sshbuf. Useful
60+ * for debugging */
7161void
7262sshbuf_relabel (struct sshbuf * buf , const char * label )
7363{
7464 if (label != NULL )
7565 strncpy (buf -> label , label , MAX_LABEL_LEN - 1 );
7666}
7767
78- float time_diff (struct timeval * start , struct timeval * end )
68+ /* set the type (from enum sshbuf_type) of the given sshbuf.
69+ * The purpose is to allow different classes of buffers to
70+ * follow different code paths if necessary */
71+ void
72+ sshbuf_type (struct sshbuf * buf , int type )
7973{
80- return (end -> tv_sec - start -> tv_sec ) + 1e-6 * (end -> tv_usec - start -> tv_usec );
74+ if (type < BUF_MAX_TYPE )
75+ buf -> type = type ;
8176}
8277
8378static inline int
@@ -249,7 +244,7 @@ sshbuf_reset(struct sshbuf *buf)
249244size_t
250245sshbuf_max_size (const struct sshbuf * buf )
251246{
252- return buf -> max_size / 2 ;
247+ return buf -> max_size ;
253248}
254249
255250size_t
@@ -271,18 +266,13 @@ sshbuf_refcount(const struct sshbuf *buf)
271266}
272267
273268int
274- sshbuf_set_max_size (struct sshbuf * buf , size_t requested_size )
269+ sshbuf_set_max_size (struct sshbuf * buf , size_t max_size )
275270{
276- size_t rlen , max_size = requested_size * 2 ;
271+ size_t rlen ;
277272 u_char * dp ;
278273 int r ;
279274
280- /*
281- * Note that we set the actual allocation limit to be 2x the requested
282- * size. This is to avoid some pathological compaction behaviour later
283- * when a buffer is at capacity and has small drains/fills on it.
284- */
285- SSHBUF_DBG (("set max buf = %p requested = %zu" , buf , requested_size ));
275+ SSHBUF_DBG (("set max buf = %p len = %zu" , buf , max_size ));
286276 if ((r = sshbuf_check_sanity (buf )) != 0 )
287277 return r ;
288278 if (max_size == buf -> max_size )
@@ -291,17 +281,9 @@ sshbuf_set_max_size(struct sshbuf *buf, size_t requested_size)
291281 return SSH_ERR_BUFFER_READ_ONLY ;
292282 if (max_size > SSHBUF_SIZE_MAX )
293283 return SSH_ERR_NO_BUFFER_SPACE ;
294- /*
295- * Always pack as it makes everything that follows easier.
296- * Potentially expensive, but this should seldom be called on buffers
297- * that already contain data.
298- */
299- sshbuf_maybe_pack (buf , 1 );
300- /* Refuse setting a maximum below current amount of data in buffer */
301- if (requested_size < buf -> size )
302- return SSH_ERR_NO_BUFFER_SPACE ;
303- /* Shrink alloc if the existing allocation is larger than requested */
304- if (requested_size < buf -> alloc ) {
284+ /* pack and realloc if necessary */
285+ sshbuf_maybe_pack (buf , max_size < buf -> size );
286+ if (max_size < buf -> alloc && max_size > buf -> size ) {
305287 if (buf -> size < SSHBUF_SIZE_INIT )
306288 rlen = SSHBUF_SIZE_INIT ;
307289 else
@@ -315,6 +297,8 @@ sshbuf_set_max_size(struct sshbuf *buf, size_t requested_size)
315297 buf -> alloc = rlen ;
316298 }
317299 SSHBUF_TELL ("new-max" );
300+ if (max_size < buf -> alloc )
301+ return SSH_ERR_NO_BUFFER_SPACE ;
318302 buf -> max_size = max_size ;
319303 return 0 ;
320304}
@@ -332,7 +316,18 @@ sshbuf_avail(const struct sshbuf *buf)
332316{
333317 if (sshbuf_check_sanity (buf ) != 0 || buf -> readonly || buf -> refcount > 1 )
334318 return 0 ;
335- return (buf -> max_size / 2 ) - (buf -> size - buf -> off );
319+ /* we need to reserve a small amount of overhead on the input buffer
320+ * or we can enter into a pathological state during bulk
321+ * data transfers. We use a fraction of the max size as we want it to scale
322+ * with the size of the input buffer. If we do it for all of the buffers
323+ * we fail the regression unit tests. This seems like a reasonable
324+ * solution. Of course, I still need to figure out *why* this is
325+ * happening and come up with an actual fix. TODO
326+ * cjr 4/19/2024 */
327+ if (buf -> type == BUF_CHANNEL_INPUT )
328+ return buf -> max_size / 1.05 - (buf -> size - buf -> off );
329+ else
330+ return buf -> max_size - (buf -> size - buf -> off );
336331}
337332
338333const u_char *
@@ -361,19 +356,12 @@ sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
361356 if (buf -> readonly || buf -> refcount > 1 )
362357 return SSH_ERR_BUFFER_READ_ONLY ;
363358 SSHBUF_TELL ("check" );
364- /* Check that len is reasonable and that max size + available < len */
365- if (len > (buf -> max_size / 2 ) ||
366- (buf -> max_size / 2 ) - len < buf -> size - buf -> off )
359+ /* Check that len is reasonable and that max_size + available < len */
360+ if (len > buf -> max_size || buf -> max_size - len < buf -> size - buf -> off )
367361 return SSH_ERR_NO_BUFFER_SPACE ;
368362 return 0 ;
369363}
370364
371- void
372- sshbuf_set_window_max (struct sshbuf * buf , size_t len )
373- {
374- buf -> window_max = len ;
375- }
376-
377365int
378366sshbuf_allocate (struct sshbuf * buf , size_t len )
379367{
@@ -403,38 +391,32 @@ sshbuf_allocate(struct sshbuf *buf, size_t len)
403391 * slowly. It's knows that it needs to grow but it only does so 32K
404392 * at a time. This means a lot of calls to realloc and memcpy which
405393 * kills performance until the buffer reaches some maximum size.
406- * so we explicitly test for a buffer that's trying to grow and
407- * if it is then we push the growth to whatever the adjusted value of
408- * local_window_max happens to be. This significantly reduces overhead
394+ * So we explicitly test for a buffer that's trying to grow and
395+ * if it is then we push the growth by 4MB at a time. This can result in
396+ * the buffer being over allocated (in terms of actual needs) but the
397+ * process is fast. This significantly reduces overhead
409398 * and improves performance. In this case we look for a buffer that is trying
410399 * to grow larger than BUF_WATERSHED (256*1024 taken from PACKET_MAX_SIZE)
411- * and where the local_window_max isn't zero (which is usally in the Channels
412- * struct but we copied it into the shhbuf as window_max). If it is zero or
413- * the buffer is smaller than BUF_WATERSHED we just use the
414- * normal value for need. We also don't want to grow the buffer past
415- * what we need (the size of window_max) so if the current allocation (in
416- * buf->alloc) is greater than window_max we skip it.
417- *
418- * Turns out the extra functions on the following conditional aren't needed
419- * -cjr 04/06/23
400+ * and explcitly check that the buffer is being used for inbound outbound
401+ * channel buffering.
402+ * Updated for 18.4.1 -cjr 04/20/24
420403 */
421- if (rlen > BUF_WATERSHED ) {
422- /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu win_max is %zu max_size is %zu",
423- buf->label, buf, rlen, need, buf->window_max, buf-> max_size); */
404+ if (rlen > BUF_WATERSHED && ( buf -> type == BUF_CHANNEL_OUTPUT || buf -> type == BUF_CHANNEL_INPUT ) ) {
405+ /* debug_f ("Prior: label: %s, %p, rlen is %zu need is %zu max_size is %zu",
406+ buf->label, buf, rlen, need, buf->max_size); */
424407 /* easiest thing to do is grow the nuffer by 4MB each time. It might end
425408 * up being somewhat overallocated but works quickly */
426409 need = (4 * 1024 * 1024 );
427410 rlen = ROUNDUP (buf -> alloc + need , SSHBUF_SIZE_INC );
428- /* debug_f ("Post: label: %s, %p, rlen is %zu need is %zu win_max is %zu max_size is %zu", */
429- /* buf->label, buf, rlen, need, buf->window_max, buf-> max_size); */
411+ /* debug_f ("Post: label: %s, %p, rlen is %zu need is %zu max_size is %zu", */
412+ /* buf->label, buf, rlen, need, buf->max_size); */
430413 }
431414 SSHBUF_DBG (("need %zu initial rlen %zu" , need , rlen ));
432415
433416 /* rlen might be above the max allocation */
434- if (rlen > buf -> max_size ) {
417+ if (rlen > buf -> max_size )
435418 rlen = buf -> max_size ;
436- /* debug_f("set rlen to %zu", buf->max_size);*/
437- }
419+
438420 SSHBUF_DBG (("adjusted rlen %zu" , rlen ));
439421 if ((dp = recallocarray (buf -> d , buf -> alloc , rlen , 1 )) == NULL ) {
440422 SSHBUF_DBG (("realloc fail" ));
@@ -484,9 +466,8 @@ sshbuf_consume(struct sshbuf *buf, size_t len)
484466 return SSH_ERR_MESSAGE_INCOMPLETE ;
485467 buf -> off += len ;
486468 /* deal with empty buffer */
487- if (buf -> off == buf -> size ) {
469+ if (buf -> off == buf -> size )
488470 buf -> off = buf -> size = 0 ;
489- }
490471 SSHBUF_TELL ("done" );
491472 return 0 ;
492473}
0 commit comments