Commit message: _dbus_header_byteswap: change the first byte of the message, not just the struct member

This has been wrong approximately forever, for instance see:
http://lists.freedesktop.org/archives/dbus/2007-March/007357.html

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=38120
Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=629938
Reviewed-by: Will Thompson <will.thompson@collabora.co.uk>
CVE: CVE-2011-2200
CWE: CWE-20
Vulnerability description: The _dbus_header_byteswap function in dbus-marshal-header.c in D-Bus (aka DBus) 1.2.x before 1.2.28, 1.4.x before 1.4.12, and 1.5.x before 1.5.4 does not properly handle a non-native byte order, which allows local users to cause a denial of service (connection loss), obtain potentially sensitive information, or conduct unspecified state-modification attacks via crafted messages.

Project: dbus
File: dbus-marshal-header.c

_dbus_header_cache_check (DBusHeader    *header,
                          int            field)
{
  _dbus_assert (field <= DBUS_HEADER_FIELD_LAST);

  if (header->fields[field].value_pos == _DBUS_HEADER_FIELD_VALUE_UNKNOWN)
    _dbus_header_cache_revalidate (header);

  if (header->fields[field].value_pos == _DBUS_HEADER_FIELD_VALUE_NONEXISTENT)
    return FALSE;

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_cache_invalidate_all (DBusHeader *header)
{
  int i;

  i = 0;
  while (i <= DBUS_HEADER_FIELD_LAST)
    {
      header->fields[i].value_pos = _DBUS_HEADER_FIELD_VALUE_UNKNOWN;
      ++i;
    }
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_cache_known_nonexistent (DBusHeader    *header,
                                      int            field)
{
  _dbus_assert (field <= DBUS_HEADER_FIELD_LAST);

  return (header->fields[field].value_pos == _DBUS_HEADER_FIELD_VALUE_NONEXISTENT);
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_cache_one (DBusHeader     *header,
                        int             field_code,
                        DBusTypeReader *variant_reader)
{
  header->fields[field_code].value_pos =
    _dbus_type_reader_get_value_pos (variant_reader);

#if 0
  _dbus_verbose ("cached value_pos %d for field %d\n",
                 header->fields[field_code].value_pos, field_code)
#endif
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_cache_revalidate (DBusHeader *header)
{
  DBusTypeReader array;
  DBusTypeReader reader;
  int i;

  i = 0;
  while (i <= DBUS_HEADER_FIELD_LAST)
    {
      header->fields[i].value_pos = _DBUS_HEADER_FIELD_VALUE_NONEXISTENT;
      ++i;
    }

  _dbus_type_reader_init (&reader,
                          header->byte_order,
                          &_dbus_header_signature_str,
                          FIELDS_ARRAY_SIGNATURE_OFFSET,
                          &header->data,
                          FIELDS_ARRAY_LENGTH_OFFSET);

  _dbus_type_reader_recurse (&reader, &array);

  while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID)
    {
      DBusTypeReader sub;
      DBusTypeReader variant;
      unsigned char field_code;

      _dbus_type_reader_recurse (&array, &sub);

      _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE);
      _dbus_type_reader_read_basic (&sub, &field_code);

      /* Unknown fields should be ignored */
      if (field_code > DBUS_HEADER_FIELD_LAST)
        goto next_field;

      _dbus_type_reader_next (&sub);

      _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_VARIANT);
      _dbus_type_reader_recurse (&sub, &variant);

      _dbus_header_cache_one (header, field_code, &variant);

    next_field:
      _dbus_type_reader_next (&array);
    }
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_copy (const DBusHeader *header,
                   DBusHeader       *dest)
{
  *dest = *header;

  if (!_dbus_string_init_preallocated (&dest->data,
                                       _dbus_string_get_length (&header->data)))
    return FALSE;

  if (!_dbus_string_copy (&header->data, 0, &dest->data, 0))
    {
      _dbus_string_free (&dest->data);
      return FALSE;
    }

  /* Reset the serial */
  _dbus_header_set_serial (dest, 0);

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_create (DBusHeader  *header,
                     int          message_type,
                     const char  *destination,
                     const char  *path,
                     const char  *interface,
                     const char  *member,
                     const char  *error_name)
{
  unsigned char v_BYTE;
  dbus_uint32_t v_UINT32;
  DBusTypeWriter writer;
  DBusTypeWriter array;

  _dbus_assert (((interface || message_type != DBUS_MESSAGE_TYPE_SIGNAL) && member) ||
                (error_name) ||
                !(interface || member || error_name));
  _dbus_assert (_dbus_string_get_length (&header->data) == 0);

  if (!reserve_header_padding (header))
    return FALSE;

  _dbus_type_writer_init_values_only (&writer, header->byte_order,
                                      &_dbus_header_signature_str, 0,
                                      &header->data,
                                      HEADER_END_BEFORE_PADDING (header));

  v_BYTE = header->byte_order;
  if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE,
                                      &v_BYTE))
    goto oom;

  v_BYTE = message_type;
  if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE,
                                      &v_BYTE))
    goto oom;

  v_BYTE = 0; /* flags */
  if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE,
                                      &v_BYTE))
    goto oom;

  v_BYTE = DBUS_MAJOR_PROTOCOL_VERSION;
  if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE,
                                      &v_BYTE))
    goto oom;

  v_UINT32 = 0; /* body length */
  if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_UINT32,
                                      &v_UINT32))
    goto oom;

  v_UINT32 = 0; /* serial */
  if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_UINT32,
                                      &v_UINT32))
    goto oom;

  if (!_dbus_type_writer_recurse (&writer, DBUS_TYPE_ARRAY,
                                  &_dbus_header_signature_str,
                                  FIELDS_ARRAY_SIGNATURE_OFFSET,
                                  &array))
    goto oom;

  /* Marshal all the fields (Marshall Fields?) */

  if (path != NULL)
    {
      if (!write_basic_field (&array,
                              DBUS_HEADER_FIELD_PATH,
                              DBUS_TYPE_OBJECT_PATH,
                              &path))
        goto oom;
    }

  if (destination != NULL)
    {
      if (!write_basic_field (&array,
                              DBUS_HEADER_FIELD_DESTINATION,
                              DBUS_TYPE_STRING,
                              &destination))
        goto oom;
    }

  if (interface != NULL)
    {
      if (!write_basic_field (&array,
                              DBUS_HEADER_FIELD_INTERFACE,
                              DBUS_TYPE_STRING,
                              &interface))
        goto oom;
    }

  if (member != NULL)
    {
      if (!write_basic_field (&array,
                              DBUS_HEADER_FIELD_MEMBER,
                              DBUS_TYPE_STRING,
                              &member))
        goto oom;
    }

  if (error_name != NULL)
    {
      if (!write_basic_field (&array,
                              DBUS_HEADER_FIELD_ERROR_NAME,
                              DBUS_TYPE_STRING,
                              &error_name))
        goto oom;
    }

  if (!_dbus_type_writer_unrecurse (&writer, &array))
    goto oom;

  correct_header_padding (header);

  return TRUE;

 oom:
  _dbus_string_delete (&header->data, 0,
                       _dbus_string_get_length (&header->data) - header->padding);
  correct_header_padding (header);

  return FALSE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_delete_field (DBusHeader *header,
                           int         field)
{
  DBusTypeReader reader;
  DBusTypeReader realign_root;

  if (_dbus_header_cache_known_nonexistent (header, field))
    return TRUE; /* nothing to do */

  /* Scan to the field we want, delete and realign, reappend
   * padding. Field may turn out not to exist.
   */
  if (!find_field_for_modification (header, field,
                                    &reader, &realign_root))
    return TRUE; /* nothing to do */

  if (!reserve_header_padding (header))
    return FALSE;

  if (!_dbus_type_reader_delete (&reader,
                                 &realign_root))
    return FALSE;

  correct_header_padding (header);

  _dbus_header_cache_invalidate_all (header);

  _dbus_assert (!_dbus_header_cache_check (header, field)); /* Expensive assertion ... */

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_free (DBusHeader *header)
{
  _dbus_string_free (&header->data);
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_get_field_raw (DBusHeader        *header,
                            int                field,
                            const DBusString **str,
                            int               *pos)
{
  if (!_dbus_header_cache_check (header, field))
    return FALSE;

  if (str)
    *str = &header->data;
  if (pos)
    *pos = header->fields[field].value_pos;

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_get_flag (DBusHeader   *header,
                       dbus_uint32_t flag)
{
  const unsigned char *flags_p;

  flags_p = _dbus_string_get_const_data_len (&header->data, FLAGS_OFFSET, 1);

  return (*flags_p & flag) != 0;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_get_serial (DBusHeader *header)
{
  return _dbus_marshal_read_uint32 (&header->data,
                                    SERIAL_OFFSET,
                                    header->byte_order,
                                    NULL);
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_have_message_untrusted (int                max_message_length,
                                     DBusValidity      *validity,
                                     int               *byte_order,
                                     int               *fields_array_len,
                                     int               *header_len,
                                     int               *body_len,
                                     const DBusString  *str,
                                     int                start,
                                     int                len)

{
  dbus_uint32_t header_len_unsigned;
  dbus_uint32_t fields_array_len_unsigned;
  dbus_uint32_t body_len_unsigned;

  _dbus_assert (start >= 0);
  _dbus_assert (start < _DBUS_INT32_MAX / 2);
  _dbus_assert (len >= 0);

  _dbus_assert (start == (int) _DBUS_ALIGN_VALUE (start, 8));

  *byte_order = _dbus_string_get_byte (str, start + BYTE_ORDER_OFFSET);

  if (*byte_order != DBUS_LITTLE_ENDIAN && *byte_order != DBUS_BIG_ENDIAN)
    {
      *validity = DBUS_INVALID_BAD_BYTE_ORDER;
      return FALSE;
    }

  _dbus_assert (FIELDS_ARRAY_LENGTH_OFFSET + 4 <= len);
  fields_array_len_unsigned = _dbus_marshal_read_uint32 (str, start + FIELDS_ARRAY_LENGTH_OFFSET,
                                                         *byte_order, NULL);

  if (fields_array_len_unsigned > (unsigned) max_message_length)
    {
      *validity = DBUS_INVALID_INSANE_FIELDS_ARRAY_LENGTH;
      return FALSE;
    }

  _dbus_assert (BODY_LENGTH_OFFSET + 4 < len);
  body_len_unsigned = _dbus_marshal_read_uint32 (str, start + BODY_LENGTH_OFFSET,
                                                 *byte_order, NULL);

  if (body_len_unsigned > (unsigned) max_message_length)
    {
      *validity = DBUS_INVALID_INSANE_BODY_LENGTH;
      return FALSE;
    }

  header_len_unsigned = FIRST_FIELD_OFFSET + fields_array_len_unsigned;
  header_len_unsigned = _DBUS_ALIGN_VALUE (header_len_unsigned, 8);

  /* overflow should be impossible since the lengths aren't allowed to
   * be huge.
   */
  _dbus_assert (max_message_length < _DBUS_INT32_MAX / 2);
  if (body_len_unsigned + header_len_unsigned > (unsigned) max_message_length)
    {
      *validity = DBUS_INVALID_MESSAGE_TOO_LONG;
      return FALSE;
    }

  _dbus_assert (body_len_unsigned < (unsigned) _DBUS_INT32_MAX);
  _dbus_assert (fields_array_len_unsigned < (unsigned) _DBUS_INT32_MAX);
  _dbus_assert (header_len_unsigned < (unsigned) _DBUS_INT32_MAX);

  *body_len = body_len_unsigned;
  *fields_array_len = fields_array_len_unsigned;
  *header_len = header_len_unsigned;

  *validity = DBUS_VALID;

  _dbus_verbose ("have %d bytes, need body %u + header %u = %u\n",
                 len, body_len_unsigned, header_len_unsigned,
                 body_len_unsigned + header_len_unsigned);

  return (body_len_unsigned + header_len_unsigned) <= (unsigned) len;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_init (DBusHeader *header,
                   int         byte_order)
{
  if (!_dbus_string_init_preallocated (&header->data, 32))
    return FALSE;

  _dbus_header_reinit (header, byte_order);

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_reinit (DBusHeader *header,
                     int         byte_order)
{
  _dbus_string_set_length (&header->data, 0);

  header->byte_order = byte_order;
  header->padding = 0;

  _dbus_header_cache_invalidate_all (header);
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_set_field_basic (DBusHeader       *header,
                              int               field,
                              int               type,
                              const void       *value)
{
  _dbus_assert (field <= DBUS_HEADER_FIELD_LAST);

  if (!reserve_header_padding (header))
    return FALSE;

  /* If the field exists we set, otherwise we append */
  if (_dbus_header_cache_check (header, field))
    {
      DBusTypeReader reader;
      DBusTypeReader realign_root;

      if (!find_field_for_modification (header, field,
                                        &reader, &realign_root))
        _dbus_assert_not_reached ("field was marked present in cache but wasn't found");

      if (!set_basic_field (&reader, field, type, value, &realign_root))
        return FALSE;
    }
  else
    {
      DBusTypeWriter writer;
      DBusTypeWriter array;

      _dbus_type_writer_init_values_only (&writer,
                                          header->byte_order,
                                          &_dbus_header_signature_str,
                                          FIELDS_ARRAY_SIGNATURE_OFFSET,
                                          &header->data,
                                          FIELDS_ARRAY_LENGTH_OFFSET);

      /* recurse into array without creating a new length, and jump to
       * end of array.
       */
      if (!_dbus_type_writer_append_array (&writer,
                                           &_dbus_header_signature_str,
                                           FIELDS_ARRAY_ELEMENT_SIGNATURE_OFFSET,
                                           &array))
        _dbus_assert_not_reached ("recurse into ARRAY should not have used memory");

      _dbus_assert (array.u.array.len_pos == FIELDS_ARRAY_LENGTH_OFFSET);
      _dbus_assert (array.u.array.start_pos == FIRST_FIELD_OFFSET);
      _dbus_assert (array.value_pos == HEADER_END_BEFORE_PADDING (header));

      if (!write_basic_field (&array,
                              field, type, value))
        return FALSE;

      if (!_dbus_type_writer_unrecurse (&writer, &array))
        _dbus_assert_not_reached ("unrecurse from ARRAY should not have used memory");
    }

  correct_header_padding (header);

  /* We could be smarter about this (only invalidate fields after the
   * one we modified, or even only if the one we modified changed
   * length). But this hack is a start.
   */
  _dbus_header_cache_invalidate_all (header);

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_set_serial (DBusHeader    *header,
                         dbus_uint32_t  serial)
{
  /* we use this function to set the serial on outgoing
   * messages, and to reset the serial in dbus_message_copy;
   * this assertion should catch a double-set on outgoing.
   */
  _dbus_assert (_dbus_header_get_serial (header) == 0 ||
                serial == 0);

  _dbus_marshal_set_uint32 (&header->data,
                            SERIAL_OFFSET,
			    serial,
                            header->byte_order);
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_toggle_flag (DBusHeader   *header,
                          dbus_uint32_t flag,
                          dbus_bool_t   value)
{
  unsigned char *flags_p;

  flags_p = _dbus_string_get_data_len (&header->data, FLAGS_OFFSET, 1);

  if (value)
    *flags_p |= flag;
  else
    *flags_p &= ~flag;
}

Project: dbus
File: dbus-marshal-header.c

_dbus_header_update_lengths (DBusHeader *header,
                             int         body_len)
{
  _dbus_marshal_set_uint32 (&header->data,
                            BODY_LENGTH_OFFSET,
                            body_len,
                            header->byte_order);
}

Project: dbus
File: dbus-marshal-header.c

_dbus_marshal_header_test (void)
{

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

find_field_for_modification (DBusHeader     *header,
                             int             field,
                             DBusTypeReader *reader,
                             DBusTypeReader *realign_root)
{
  dbus_bool_t retval;

  retval = FALSE;

  _dbus_type_reader_init (realign_root,
                          header->byte_order,
                          &_dbus_header_signature_str,
                          FIELDS_ARRAY_SIGNATURE_OFFSET,
                          &header->data,
                          FIELDS_ARRAY_LENGTH_OFFSET);

  _dbus_type_reader_recurse (realign_root, reader);

  while (_dbus_type_reader_get_current_type (reader) != DBUS_TYPE_INVALID)
    {
      DBusTypeReader sub;
      unsigned char field_code;

      _dbus_type_reader_recurse (reader, &sub);

      _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE);
      _dbus_type_reader_read_basic (&sub, &field_code);

      if (field_code == (unsigned) field)
        {
          _dbus_assert (_dbus_type_reader_get_current_type (reader) == DBUS_TYPE_STRUCT);
          retval = TRUE;
          goto done;
        }

      _dbus_type_reader_next (reader);
    }

 done:
  return retval;
}

Project: dbus
File: dbus-marshal-header.c

reserve_header_padding (DBusHeader *header)
{
  _dbus_assert (header->padding <= MAX_POSSIBLE_HEADER_PADDING);

  if (!_dbus_string_lengthen (&header->data,
                              MAX_POSSIBLE_HEADER_PADDING - header->padding))
    return FALSE;
  header->padding = MAX_POSSIBLE_HEADER_PADDING;
  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

set_basic_field (DBusTypeReader       *reader,
                 int                   field,
                 int                   type,
                 const void           *value,
                 const DBusTypeReader *realign_root)
{
  DBusTypeReader sub;
  DBusTypeReader variant;

  _dbus_type_reader_recurse (reader, &sub);

  _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE);
#ifndef DBUS_DISABLE_ASSERT
 {
   unsigned char v_BYTE;
   _dbus_type_reader_read_basic (&sub, &v_BYTE);
   _dbus_assert (((int) v_BYTE) == field);
 }
#endif

  if (!_dbus_type_reader_next (&sub))
    _dbus_assert_not_reached ("no variant field?");

  _dbus_type_reader_recurse (&sub, &variant);
  _dbus_assert (_dbus_type_reader_get_current_type (&variant) == type);

  if (!_dbus_type_reader_set_basic (&variant, value, realign_root))
    return FALSE;

  return TRUE;
}

Project: dbus
File: dbus-marshal-header.c

write_basic_field (DBusTypeWriter *writer,
                   int             field,
                   int             type,
                   const void     *value)
{
  DBusTypeWriter sub;
  DBusTypeWriter variant;
  int start;
  int padding;
  unsigned char field_byte;
  DBusString contained_type;
  char buf[2];

  start = writer->value_pos;
  padding = _dbus_string_get_length (writer->value_str) - start;

  if (!_dbus_type_writer_recurse (writer, DBUS_TYPE_STRUCT,
                                  NULL, 0, &sub))
    goto append_failed;

  field_byte = field;
  if (!_dbus_type_writer_write_basic (&sub, DBUS_TYPE_BYTE,
                                      &field_byte))
    goto append_failed;

  buf[0] = type;
  buf[1] = '\0';
  _dbus_string_init_const_len (&contained_type, buf, 1);

  if (!_dbus_type_writer_recurse (&sub, DBUS_TYPE_VARIANT,
                                  &contained_type, 0, &variant))
    goto append_failed;

  if (!_dbus_type_writer_write_basic (&variant, type, value))
    goto append_failed;

  if (!_dbus_type_writer_unrecurse (&sub, &variant))
    goto append_failed;

  if (!_dbus_type_writer_unrecurse (writer, &sub))
    goto append_failed;

  return TRUE;

 append_failed:
  _dbus_string_delete (writer->value_str,
                       start,
                       _dbus_string_get_length (writer->value_str) - start - padding);
  return FALSE;
}