Commit message: None
CVE: CVE-2012-1573
CWE: CWE-310
Vulnerability description: gnutls_cipher.c in libgnutls in GnuTLS before 2.12.17 and 3.x before 3.0.15 does not properly handle data encrypted with a block cipher, which allows remote attackers to cause a denial of service (heap memory corruption and application crash) via a crafted record, as demonstrated by a crafted GenericBlockCipher structure.

Project: savannah
File: gnutls_cipher.c

_gnutls_compressed2ciphertext (gnutls_session_t session,
                               opaque * cipher_data, int cipher_size,
                               gnutls_datum_t compressed,
                               content_type_t _type, int random_pad,
                               record_parameters_st * params)
{
  uint8_t MAC[MAX_HASH_SIZE];
  uint16_t c_length;
  uint8_t pad;
  int length, ret;
  uint8_t type = _type;
  opaque preamble[PREAMBLE_SIZE];
  int preamble_size;
  int hash_size = _gnutls_hash_get_algo_len (params->mac_algorithm);
  int blocksize = gnutls_cipher_get_block_size (params->cipher_algorithm);
  cipher_type_t block_algo =
    _gnutls_cipher_is_block (params->cipher_algorithm);
  opaque *data_ptr;
  int ver = gnutls_protocol_get_version (session);


  /* Initialize MAC */

  c_length = _gnutls_conv_uint16 (compressed.size);

  if (params->mac_algorithm != GNUTLS_MAC_NULL)
    {                           /* actually when the algorithm in not the NULL one */
      digest_hd_st td;

      ret = mac_init (&td, params->mac_algorithm,
                      params->write.mac_secret.data,
                      params->write.mac_secret.size, ver);

      if (ret < 0)
        {
          gnutls_assert ();
          return ret;
        }
      preamble_size =
        make_preamble (UINT64DATA
                       (params->write.sequence_number),
                       type, c_length, ver, preamble);
      mac_hash (&td, preamble, preamble_size, ver);
      mac_hash (&td, compressed.data, compressed.size, ver);
      mac_deinit (&td, MAC, ver);
    }


  /* Calculate the encrypted length (padding etc.)
   */
  length =
    calc_enc_length (session, compressed.size, hash_size, &pad,
                     random_pad, block_algo, blocksize);
  if (length < 0)
    {
      gnutls_assert ();
      return length;
    }

  /* copy the encrypted data to cipher_data.
   */
  if (cipher_size < length)
    {
      gnutls_assert ();
      return GNUTLS_E_MEMORY_ERROR;
    }

  data_ptr = cipher_data;
  if (block_algo == CIPHER_BLOCK &&
      _gnutls_version_has_explicit_iv (session->security_parameters.version))
    {
      /* copy the random IV.
       */
      ret = _gnutls_rnd (GNUTLS_RND_NONCE, data_ptr, blocksize);
      if (ret < 0)
        {
          gnutls_assert ();
          return ret;
        }

      data_ptr += blocksize;
    }

  memcpy (data_ptr, compressed.data, compressed.size);
  data_ptr += compressed.size;

  if (hash_size > 0)
    {
      memcpy (data_ptr, MAC, hash_size);
      data_ptr += hash_size;
    }
  if (block_algo == CIPHER_BLOCK && pad > 0)
    {
      memset (data_ptr, pad - 1, pad);
    }


  /* Actual encryption (inplace).
   */
  ret =
    _gnutls_cipher_encrypt (&params->write.cipher_state, cipher_data, length);
  if (ret < 0)
    {
      gnutls_assert ();
      return ret;
    }

  return length;
}

Project: savannah
File: gnutls_cipher.c

_gnutls_decrypt (gnutls_session_t session, opaque * ciphertext,
                 size_t ciphertext_size, uint8_t * data,
                 size_t max_data_size, content_type_t type,
                 record_parameters_st * params)
{
  gnutls_datum_t gtxt;
  gnutls_datum_t gcipher;
  int ret;

  if (ciphertext_size == 0)
    return 0;

  gcipher.size = ciphertext_size;
  gcipher.data = ciphertext;

  ret =
    _gnutls_ciphertext2compressed (session, data, max_data_size,
                                   gcipher, type, params);
  if (ret < 0)
    {
      return ret;
    }

  if (ret == 0 || is_read_comp_null (session) == 0)
    {
      /* ret == ret */

    }
  else
    {
      gnutls_datum_t gcomp;

      /* compression has this malloc overhead.
       */

      gcomp.data = data;
      gcomp.size = ret;
      ret = _gnutls_m_compressed2plaintext (session, &gtxt, &gcomp, params);
      if (ret < 0)
        {
          return ret;
        }

      if (gtxt.size > MAX_RECORD_RECV_SIZE)
        {
          gnutls_assert ();
          _gnutls_free_datum (&gtxt);
          /* This shouldn't have happen and
           * is a TLS fatal error.
           */
          return GNUTLS_E_DECOMPRESSION_FAILED;
        }

      /* This check is not really needed */
      if (max_data_size < MAX_RECORD_RECV_SIZE)
        {
          gnutls_assert ();
          _gnutls_free_datum (&gtxt);
          return GNUTLS_E_INTERNAL_ERROR;
        }

      memcpy (data, gtxt.data, gtxt.size);
      ret = gtxt.size;

      _gnutls_free_datum (&gtxt);
    }

  return ret;
}

Project: savannah
File: gnutls_cipher.c

_gnutls_encrypt (gnutls_session_t session, const opaque * headers,
                 size_t headers_size, const opaque * data,
                 size_t data_size, opaque * ciphertext,
                 size_t ciphertext_size, content_type_t type, int random_pad,
                 record_parameters_st * params)
{
  gnutls_datum_t plain;
  gnutls_datum_t comp;
  int ret;
  int free_comp = 1;

  plain.data = (opaque *) data;
  plain.size = data_size;

  if (plain.size == 0 || is_write_comp_null (session) == 0)
    {
      comp = plain;
      free_comp = 0;
    }
  else
    {
      /* Here comp is allocated and must be 
       * freed.
       */
      ret = _gnutls_m_plaintext2compressed (session, &comp, &plain, params);
      if (ret < 0)
        {
          gnutls_assert ();
          return ret;
        }
    }

  ret = _gnutls_compressed2ciphertext (session, &ciphertext[headers_size],
                                       ciphertext_size - headers_size,
                                       comp, type, random_pad, params);

  if (free_comp)
    _gnutls_free_datum (&comp);

  if (ret < 0)
    {
      gnutls_assert ();
      return ret;
    }


  /* copy the headers */
  memcpy (ciphertext, headers, headers_size);
  _gnutls_write_uint16 (ret, &ciphertext[3]);

  return ret + headers_size;
}

Project: savannah
File: gnutls_cipher.c

calc_enc_length (gnutls_session_t session, int data_size,
                 int hash_size, uint8_t * pad, int random_pad,
                 cipher_type_t block_algo, uint16_t blocksize)
{
  uint8_t rnd;
  int length, ret;

  *pad = 0;

  switch (block_algo)
    {
    case CIPHER_STREAM:
      length = data_size + hash_size;

      break;
    case CIPHER_BLOCK:
      ret = _gnutls_rnd (GNUTLS_RND_NONCE, &rnd, 1);
      if (ret < 0)
        {
          gnutls_assert ();
          return ret;
        }

      /* make rnd a multiple of blocksize */
      if (session->security_parameters.version == GNUTLS_SSL3 ||
          random_pad == 0)
        {
          rnd = 0;
        }
      else
        {
          rnd = (rnd / blocksize) * blocksize;
          /* added to avoid the case of pad calculated 0
           * seen below for pad calculation.
           */
          if (rnd > blocksize)
            rnd -= blocksize;
        }

      length = data_size + hash_size;

      *pad = (uint8_t) (blocksize - (length % blocksize)) + rnd;

      length += *pad;
      if (_gnutls_version_has_explicit_iv
          (session->security_parameters.version))
        length += blocksize;    /* for the IV */

      break;
    default:
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  return length;
}

Project: savannah
File: gnutls_cipher.c

is_read_comp_null (gnutls_session_t session)
{
  record_parameters_st *record_params;

  _gnutls_epoch_get (session, EPOCH_READ_CURRENT, &record_params);
  if (record_params->compression_algorithm == GNUTLS_COMP_NULL)
    return 0;

  return 1;
}

Project: savannah
File: gnutls_cipher.c

is_write_comp_null (gnutls_session_t session)
{
  record_parameters_st *record_params;

  _gnutls_epoch_get (session, EPOCH_WRITE_CURRENT, &record_params);
  if (record_params->compression_algorithm == GNUTLS_COMP_NULL)
    return 0;

  return 1;
}

Project: savannah
File: gnutls_cipher.c

mac_hash (digest_hd_st * td, void *data, int data_size, int ver)
{
  if (ver == GNUTLS_SSL3)
    {                           /* SSL 3.0 */
      _gnutls_hash (td, data, data_size);
    }
  else
    {
      _gnutls_hmac (td, data, data_size);
    }
}

Project: savannah
File: gnutls_cipher.c

make_preamble (opaque * uint64_data, opaque type, uint16_t c_length,
               opaque ver, opaque * preamble)
{
  opaque minor = _gnutls_version_get_minor (ver);
  opaque major = _gnutls_version_get_major (ver);
  opaque *p = preamble;

  memcpy (p, uint64_data, 8);
  p += 8;
  *p = type;
  p++;
  if (_gnutls_version_has_variable_padding (ver))
    {                           /* TLS 1.0 or higher */
      *p = major;
      p++;
      *p = minor;
      p++;
    }
  memcpy (p, &c_length, 2);
  p += 2;
  return p - preamble;
}