CVE-2020-11080 (C, published 2020-06-03T23:15Z)
Description: value: In nghttp2 before version 1.41.0, the overly large HTTP/2 SETTINGS frame payload causes denial of service. The proof of concept attack involves a malicious client constructing a SETTINGS frame with a length of 14,400 bytes (2400 individual settings entries) over and over again. The attack causes the CPU to spike at 100%. nghttp2 v1.41.0 fixes this vulnerability. There is a workaround to this vulnerability. Implement nghttp2_on_frame_recv_callback callback, and if received frame is SETTINGS frame and the number of settings entries are large (e.g., > 32), then drop the connection.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-707 \u2014 Improper Neutralization
The product does not ensure or incorrectly ensures that structured messages or data are well-formed and that certain security properties are met before being read from an upstream component or sent to a downstream component.
Repository: https://github.com/nghttp2/nghttp2
Files changed:
doc/CMakeLists.txt
doc/Makefile.am
lib/includes/nghttp2/nghttp2.h
lib/nghttp2_helper.c
lib/nghttp2_option.c
lib/nghttp2_option.h
lib/nghttp2_session.c
lib/nghttp2_session.h
tests/main.c
tests/nghttp2_session_test.c
tests/nghttp2_session_test.h
Commit message: Implement max settings option


Vulnerable code:

Fixed code:
  nghttp2_option_set_max_settings.rst
	nghttp2_option_set_max_settings.rst \
/**
 * @macro
 *
 * The default max number of settings per SETTINGS frame
 */
#define NGHTTP2_DEFAULT_MAX_SETTINGS 32

  /**
   * When a local endpoint receives too many settings entries
   * in a single SETTINGS frame.
   */
  NGHTTP2_ERR_TOO_MANY_SETTINGS = -537,
/**
 * @function
 *
 * This function sets the maximum number of SETTINGS entries per
 * SETTINGS frame that will be accepted. If more than those entries
 * are received, the peer is considered to be misbehaving and session
 * will be closed. The default value is 32.
 */
NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
                                                    size_t val);

  case NGHTTP2_ERR_TOO_MANY_SETTINGS:
    return "SETTINGS frame contained more than the maximum allowed entries";

void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
  option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
  option->max_settings = val;
}
  NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
  /**
   * NGHTTP2_OPT_MAX_SETTINGS
   */
  size_t max_settings;
  (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS;

    if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) &&
        option->max_settings) {
      (*session_ptr)->max_settings = option->max_settings;
    }
          if (iframe->max_niv - 1 > session->max_settings) {
            rv = nghttp2_session_terminate_session_with_reason(
                session, NGHTTP2_ENHANCE_YOUR_CALM,
                "SETTINGS: too many setting entries");
            if (nghttp2_is_fatal(rv)) {
              return rv;
            }
            return (ssize_t)inlen;
          }

  /* SETTINGS frame contains too many settings */
  if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH
        > session->max_settings) {
    return NGHTTP2_ERR_TOO_MANY_SETTINGS;
  }
  /* The maximum number of settings accepted per SETTINGS frame. */
  size_t max_settings;
      !CU_add_test(pSuite, "session_too_many_settings",
                   test_nghttp2_session_too_many_settings) ||
void test_nghttp2_session_too_many_settings(void) {
  nghttp2_session *session;
  nghttp2_option *option;
  nghttp2_session_callbacks callbacks;
  nghttp2_frame frame;
  nghttp2_bufs bufs;
  nghttp2_buf *buf;
  ssize_t rv;
  my_user_data ud;
  nghttp2_settings_entry iv[3];
  nghttp2_mem *mem;
  nghttp2_outbound_item *item;

  mem = nghttp2_mem_default();
  frame_pack_bufs_init(&bufs);

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.on_frame_recv_callback = on_frame_recv_callback;
  callbacks.send_callback = null_send_callback;

  nghttp2_option_new(&option);
  nghttp2_option_set_max_settings(option, 1);

  nghttp2_session_client_new2(&session, &callbacks, &ud, option);

  CU_ASSERT(1 == session->max_settings);

  nghttp2_option_del(option);

  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[0].value = 3000;

  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
  iv[1].value = 16384;

  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2),
                              2);

  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);

  CU_ASSERT(0 == rv);
  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);

  nghttp2_frame_settings_free(&frame.settings, mem);

  buf = &bufs.head->buf;
  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));

  ud.frame_recv_cb_called = 0;

  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
  CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);

  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);

  nghttp2_bufs_reset(&bufs);
  nghttp2_bufs_free(&bufs);
  nghttp2_session_del(session);
}

void test_nghttp2_session_too_many_settings(void);