/* -*- mode: C; c-file-style: "gnu" -*- */ /* dbus-marshal-header.c Managing marshaling/demarshaling of message headers * * Copyright (C) 2005 Red Hat, Inc. * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "dbus/dbus-shared.h" #include "dbus-marshal-header.h" #include "dbus-marshal-recursive.h" #include "dbus-marshal-byteswap.h" /** * @addtogroup DBusMarshal * * @{ */ /* Not thread locked, but strictly const/read-only so should be OK */ /** Static #DBusString containing the signature of a message header */ _DBUS_STRING_DEFINE_STATIC(_dbus_header_signature_str, DBUS_HEADER_SIGNATURE); /** Static #DBusString containing the local interface */ _DBUS_STRING_DEFINE_STATIC(_dbus_local_interface_str, DBUS_INTERFACE_LOCAL); /** Static #DBusString containing the local path */ _DBUS_STRING_DEFINE_STATIC(_dbus_local_path_str, DBUS_PATH_LOCAL); /** Offset from start of _dbus_header_signature_str to the signature of the fields array */ #define FIELDS_ARRAY_SIGNATURE_OFFSET 6 /** Offset from start of _dbus_header_signature_str to the signature of an element of the fields array */ #define FIELDS_ARRAY_ELEMENT_SIGNATURE_OFFSET 7 /** Offset to byte order from start of header */ #define BYTE_ORDER_OFFSET 0 /** Offset to type from start of header */ #define TYPE_OFFSET 1 /** Offset to flags from start of header */ #define FLAGS_OFFSET 2 /** Offset to version from start of header */ #define VERSION_OFFSET 3 /** Offset to body length from start of header */ #define BODY_LENGTH_OFFSET 4 /** Offset to client serial from start of header */ #define SERIAL_OFFSET 8 /** Offset to fields array length from start of header */ #define FIELDS_ARRAY_LENGTH_OFFSET 12 /** Offset to first field in header */ #define FIRST_FIELD_OFFSET 16 typedef struct { unsigned char code; unsigned char type; } HeaderFieldType; static const HeaderFieldType _dbus_header_field_types[DBUS_HEADER_FIELD_LAST+1] = { { DBUS_HEADER_FIELD_INVALID, DBUS_TYPE_INVALID }, { DBUS_HEADER_FIELD_PATH, DBUS_TYPE_OBJECT_PATH }, { DBUS_HEADER_FIELD_INTERFACE, DBUS_TYPE_STRING }, { DBUS_HEADER_FIELD_MEMBER, DBUS_TYPE_STRING }, { DBUS_HEADER_FIELD_ERROR_NAME, DBUS_TYPE_STRING }, { DBUS_HEADER_FIELD_REPLY_SERIAL, DBUS_TYPE_UINT32 }, { DBUS_HEADER_FIELD_DESTINATION, DBUS_TYPE_STRING }, { DBUS_HEADER_FIELD_SENDER, DBUS_TYPE_STRING }, { DBUS_HEADER_FIELD_SIGNATURE, DBUS_TYPE_SIGNATURE } }; /** Macro to look up the correct type for a field */ #define EXPECTED_TYPE_OF_FIELD(field) (_dbus_header_field_types[field].type) /** The most padding we could ever need for a header */ #define MAX_POSSIBLE_HEADER_PADDING 7 static dbus_bool_t 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; } static void correct_header_padding (DBusHeader *header) { int unpadded_len; _dbus_assert (header->padding == 7); _dbus_string_shorten (&header->data, header->padding); unpadded_len = _dbus_string_get_length (&header->data); if (!_dbus_string_align_length (&header->data, 8)) _dbus_assert_not_reached ("couldn't pad header though enough padding was preallocated"); header->padding = _dbus_string_get_length (&header->data) - unpadded_len; } /** Compute the end of the header, ignoring padding */ #define HEADER_END_BEFORE_PADDING(header) \ (_dbus_string_get_length (&(header)->data) - (header)->padding) /** * Invalidates all fields in the cache. This may be used when the * cache is totally uninitialized (contains junk) so should not * look at what's in there now. * * @param header the header */ static void _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; } } /** * Caches one field * * @param header the header * @param field_code the field * @param variant_reader the reader for the variant in the field */ static void _dbus_header_cache_one (DBusHeader *header, int field_code, DBusTypeReader *variant_reader) { int variant_type; variant_type = _dbus_type_reader_get_current_type (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 } /** * Revalidates the fields cache * * @param header the header */ static void _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); } } /** * Checks for a field, updating the cache if required. * * @param header the header * @param field the field to check * @returns #FALSE if the field doesn't exist */ static dbus_bool_t _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; } /** * Checks whether a field is known not to exist. It may exist * even if it's not known to exist. * * @param header the header * @param field the field to check * @returns #FALSE if the field definitely doesn't exist */ static dbus_bool_t _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); } /** * Writes a struct of { byte, variant } with the given basic type. * * @param writer the writer (should be ready to write a struct) * @param type the type of the value * @param value the value as for _dbus_marshal_set_basic() * @returns #FALSE if no memory */ static dbus_bool_t 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; } /** * Sets a struct of { byte, variant } with the given basic type. * * @param reader the reader (should be iterating over the array pointing at the field to set) * @param type the type of the value * @param value the value as for _dbus_marshal_set_basic() * @param realign_root where to realign from * @returns #FALSE if no memory */ static dbus_bool_t 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; } /** * Gets the type of the message. * * @param header the header * @returns the type */ int _dbus_header_get_message_type (DBusHeader *header) { int type; type = _dbus_string_get_byte (&header->data, TYPE_OFFSET); _dbus_assert (type != DBUS_MESSAGE_TYPE_INVALID); return type; } /** * Sets the serial number of a header. This can only be done once on * a header. * * @param header the header * @param serial the serial */ void _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); } /** * See dbus_message_get_serial() * * @param header the header * @returns the client serial */ dbus_uint32_t _dbus_header_get_serial (DBusHeader *header) { return _dbus_marshal_read_uint32 (&header->data, SERIAL_OFFSET, header->byte_order, NULL); } /** * Re-initializes a header that was previously initialized and never * freed. After this, to make the header valid you have to call * _dbus_header_create(). * * @param header header to re-initialize * @param byte_order byte order of the header */ void _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); } /** * Initializes a header, but doesn't prepare it for use; * to make the header valid, you have to call _dbus_header_create(). * * @param header header to initialize * @param byte_order byte order of the header * @returns #FALSE if not enough memory */ dbus_bool_t _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; } /** * Frees a header. * * @param header the header */ void _dbus_header_free (DBusHeader *header) { _dbus_string_free (&header->data); } /** * Initializes dest with a copy of the given header. * Resets the message serial to 0 on the copy. * * @param header header to copy * @param dest destination for copy * @returns #FALSE if not enough memory */ dbus_bool_t _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; } /** * Fills in the primary fields of the header, so the header is ready * for use. #NULL may be specified for some or all of the fields to * avoid adding those fields. Some combinations of fields don't make * sense, and passing them in will trigger an assertion failure. * * @param header the header * @param message_type the message type * @param destination destination field or #NULL * @param path path field or #NULL * @param interface interface field or #NULL * @param member member field or #NULL * @param error_name error name or #NULL * @returns #FALSE if not enough memory */ dbus_bool_t _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; } /** * Given data long enough to contain the length of the message body * and the fields array, check whether the data is long enough to * contain the entire message (assuming the claimed lengths are * accurate). Also checks that the lengths are in sanity parameters. * * @param max_message_length maximum length of a valid message * @param validity return location for why the data is invalid if it is * @param byte_order return location for byte order * @param fields_array_len return location for claimed fields array length * @param header_len return location for claimed header length * @param body_len return location for claimed body length * @param str the data * @param start start of data, 8-aligned * @param len length of data * @returns #TRUE if the data is long enough for the claimed length, and the lengths were valid */ dbus_bool_t _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; } static DBusValidity check_mandatory_fields (DBusHeader *header) { #define REQUIRE_FIELD(name) do { if (header->fields[DBUS_HEADER_FIELD_##name].value_pos < 0) return DBUS_INVALID_MISSING_##name; } while (0) switch (_dbus_header_get_message_type (header)) { case DBUS_MESSAGE_TYPE_SIGNAL: REQUIRE_FIELD (INTERFACE); /* FALL THRU - signals also require the path and member */ case DBUS_MESSAGE_TYPE_METHOD_CALL: REQUIRE_FIELD (PATH); REQUIRE_FIELD (MEMBER); break; case DBUS_MESSAGE_TYPE_ERROR: REQUIRE_FIELD (ERROR_NAME); REQUIRE_FIELD (REPLY_SERIAL); break; case DBUS_MESSAGE_TYPE_METHOD_RETURN: REQUIRE_FIELD (REPLY_SERIAL); break; default: /* other message types allowed but ignored */ break; } return DBUS_VALID; } static DBusValidity load_and_validate_field (DBusHeader *header, int field, DBusTypeReader *variant_reader) { int type; int expected_type; const DBusString *value_str; int value_pos; int str_data_pos; dbus_uint32_t v_UINT32; int bad_string_code; dbus_bool_t (* string_validation_func) (const DBusString *str, int start, int len); /* Supposed to have been checked already */ _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); _dbus_assert (field != DBUS_HEADER_FIELD_INVALID); /* Before we can cache a field, we need to know it has the right type */ type = _dbus_type_reader_get_current_type (variant_reader); _dbus_assert (_dbus_header_field_types[field].code == field); expected_type = EXPECTED_TYPE_OF_FIELD (field); if (type != expected_type) { _dbus_verbose ("Field %d should have type %d but has %d\n", field, expected_type, type); return DBUS_INVALID_HEADER_FIELD_HAS_WRONG_TYPE; } /* If the field was provided twice, we aren't happy */ if (header->fields[field].value_pos >= 0) { _dbus_verbose ("Header field %d seen a second time\n", field); return DBUS_INVALID_HEADER_FIELD_APPEARS_TWICE; } /* Now we can cache and look at the field content */ _dbus_verbose ("initially caching field %d\n", field); _dbus_header_cache_one (header, field, variant_reader); string_validation_func = NULL; /* make compiler happy that all this is initialized */ v_UINT32 = 0; value_str = NULL; value_pos = -1; str_data_pos = -1; bad_string_code = DBUS_VALID; if (expected_type == DBUS_TYPE_UINT32) { _dbus_header_get_field_basic (header, field, expected_type, &v_UINT32); } else if (expected_type == DBUS_TYPE_STRING || expected_type == DBUS_TYPE_OBJECT_PATH || expected_type == DBUS_TYPE_SIGNATURE) { _dbus_header_get_field_raw (header, field, &value_str, &value_pos); str_data_pos = _DBUS_ALIGN_VALUE (value_pos, 4) + 4; } else { _dbus_assert_not_reached ("none of the known fields should have this type"); } switch (field) { case DBUS_HEADER_FIELD_DESTINATION: string_validation_func = _dbus_validate_bus_name; bad_string_code = DBUS_INVALID_BAD_DESTINATION; break; case DBUS_HEADER_FIELD_INTERFACE: string_validation_func = _dbus_validate_interface; bad_string_code = DBUS_INVALID_BAD_INTERFACE; if (_dbus_string_equal_substring (&_dbus_local_interface_str, 0, _dbus_string_get_length (&_dbus_local_interface_str), value_str, str_data_pos)) { _dbus_verbose ("Message is on the local interface\n"); return DBUS_INVALID_USES_LOCAL_INTERFACE; } break; case DBUS_HEADER_FIELD_MEMBER: string_validation_func = _dbus_validate_member; bad_string_code = DBUS_INVALID_BAD_MEMBER; break; case DBUS_HEADER_FIELD_ERROR_NAME: string_validation_func = _dbus_validate_error_name; bad_string_code = DBUS_INVALID_BAD_ERROR_NAME; break; case DBUS_HEADER_FIELD_SENDER: string_validation_func = _dbus_validate_bus_name; bad_string_code = DBUS_INVALID_BAD_SENDER; break; case DBUS_HEADER_FIELD_PATH: /* OBJECT_PATH was validated generically due to its type */ string_validation_func = NULL; if (_dbus_string_equal_substring (&_dbus_local_path_str, 0, _dbus_string_get_length (&_dbus_local_path_str), value_str, str_data_pos)) { _dbus_verbose ("Message is from the local path\n"); return DBUS_INVALID_USES_LOCAL_PATH; } break; case DBUS_HEADER_FIELD_REPLY_SERIAL: /* Can't be 0 */ if (v_UINT32 == 0) { return DBUS_INVALID_BAD_SERIAL; } break; case DBUS_HEADER_FIELD_SIGNATURE: /* SIGNATURE validated generically due to its type */ string_validation_func = NULL; break; default: _dbus_assert_not_reached ("unknown field shouldn't be seen here"); break; } if (string_validation_func) { dbus_uint32_t len; _dbus_assert (bad_string_code != DBUS_VALID); len = _dbus_marshal_read_uint32 (value_str, value_pos, header->byte_order, NULL); #if 0 _dbus_verbose ("Validating string header field; code %d if fails\n", bad_string_code); #endif if (!(*string_validation_func) (value_str, str_data_pos, len)) return bad_string_code; } return DBUS_VALID; } /** * Creates a message header from potentially-untrusted data. The * return value is #TRUE if there was enough memory and the data was * valid. If it returns #TRUE, the header will be created. If it * returns #FALSE and *validity == #DBUS_VALIDITY_UNKNOWN_OOM_ERROR, * then there wasn't enough memory. If it returns #FALSE * and *validity != #DBUS_VALIDITY_UNKNOWN_OOM_ERROR then the data was * invalid. * * The byte_order, fields_array_len, and body_len args should be from * _dbus_header_have_message_untrusted(). Validation performed in * _dbus_header_have_message_untrusted() is assumed to have been * already done. * * @param header the header (must be initialized) * @param mode whether to do validation * @param validity return location for invalidity reason * @param byte_order byte order from header * @param fields_array_len claimed length of fields array * @param body_len claimed length of body * @param header_len claimed length of header * @param str a string * @param start start of header, 8-aligned * @param len length of string to look at * @returns #FALSE if no memory or data was invalid, #TRUE otherwise */ dbus_bool_t _dbus_header_load (DBusHeader *header, DBusValidationMode mode, DBusValidity *validity, int byte_order, int fields_array_len, int header_len, int body_len, const DBusString *str, int start, int len) { int leftover; DBusValidity v; DBusTypeReader reader; DBusTypeReader array_reader; unsigned char v_byte; dbus_uint32_t v_uint32; dbus_uint32_t serial; int padding_start; int padding_len; int i; _dbus_assert (start == (int) _DBUS_ALIGN_VALUE (start, 8)); _dbus_assert (header_len <= len); _dbus_assert (_dbus_string_get_length (&header->data) == 0); if (!_dbus_string_copy_len (str, start, header_len, &header->data, 0)) { _dbus_verbose ("Failed to copy buffer into new header\n"); *validity = DBUS_VALIDITY_UNKNOWN_OOM_ERROR; return FALSE; } if (mode == DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) { leftover = len - header_len - body_len - start; } else { v = _dbus_validate_body_with_reason (&_dbus_header_signature_str, 0, byte_order, &leftover, str, start, len); if (v != DBUS_VALID) { *validity = v; goto invalid; } } _dbus_assert (leftover < len); padding_len = header_len - (FIRST_FIELD_OFFSET + fields_array_len); padding_start = start + FIRST_FIELD_OFFSET + fields_array_len; _dbus_assert (start + header_len == (int) _DBUS_ALIGN_VALUE (padding_start, 8)); _dbus_assert (start + header_len == padding_start + padding_len); if (mode != DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) { if (!_dbus_string_validate_nul (str, padding_start, padding_len)) { *validity = DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; goto invalid; } } header->padding = padding_len; if (mode == DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) { *validity = DBUS_VALID; return TRUE; } /* We now know the data is well-formed, but we have to check that * it's valid. */ _dbus_type_reader_init (&reader, byte_order, &_dbus_header_signature_str, 0, str, start); /* BYTE ORDER */ _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == BYTE_ORDER_OFFSET); _dbus_type_reader_read_basic (&reader, &v_byte); _dbus_type_reader_next (&reader); _dbus_assert (v_byte == byte_order); header->byte_order = byte_order; /* MESSAGE TYPE */ _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == TYPE_OFFSET); _dbus_type_reader_read_basic (&reader, &v_byte); _dbus_type_reader_next (&reader); /* unknown message types are supposed to be ignored, so only validation here is * that it isn't invalid */ if (v_byte == DBUS_MESSAGE_TYPE_INVALID) { *validity = DBUS_INVALID_BAD_MESSAGE_TYPE; goto invalid; } /* FLAGS */ _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == FLAGS_OFFSET); _dbus_type_reader_read_basic (&reader, &v_byte); _dbus_type_reader_next (&reader); /* unknown flags should be ignored */ /* PROTOCOL VERSION */ _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == VERSION_OFFSET); _dbus_type_reader_read_basic (&reader, &v_byte); _dbus_type_reader_next (&reader); if (v_byte != DBUS_MAJOR_PROTOCOL_VERSION) { *validity = DBUS_INVALID_BAD_PROTOCOL_VERSION; goto invalid; } /* BODY LENGTH */ _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_UINT32); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == BODY_LENGTH_OFFSET); _dbus_type_reader_read_basic (&reader, &v_uint32); _dbus_type_reader_next (&reader); _dbus_assert (body_len == (signed) v_uint32); /* SERIAL */ _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_UINT32); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == SERIAL_OFFSET); _dbus_type_reader_read_basic (&reader, &serial); _dbus_type_reader_next (&reader); if (serial == 0) { *validity = DBUS_INVALID_BAD_SERIAL; goto invalid; } _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_ARRAY); _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == FIELDS_ARRAY_LENGTH_OFFSET); _dbus_type_reader_recurse (&reader, &array_reader); while (_dbus_type_reader_get_current_type (&array_reader) != DBUS_TYPE_INVALID) { DBusTypeReader struct_reader; DBusTypeReader variant_reader; unsigned char field_code; _dbus_assert (_dbus_type_reader_get_current_type (&array_reader) == DBUS_TYPE_STRUCT); _dbus_type_reader_recurse (&array_reader, &struct_reader); _dbus_assert (_dbus_type_reader_get_current_type (&struct_reader) == DBUS_TYPE_BYTE); _dbus_type_reader_read_basic (&struct_reader, &field_code); _dbus_type_reader_next (&struct_reader); if (field_code == DBUS_HEADER_FIELD_INVALID) { _dbus_verbose ("invalid header field code\n"); *validity = DBUS_INVALID_HEADER_FIELD_CODE; goto invalid; } if (field_code > DBUS_HEADER_FIELD_LAST) { _dbus_verbose ("unknown header field code %d, skipping\n", field_code); goto next_field; } _dbus_assert (_dbus_type_reader_get_current_type (&struct_reader) == DBUS_TYPE_VARIANT); _dbus_type_reader_recurse (&struct_reader, &variant_reader); v = load_and_validate_field (header, field_code, &variant_reader); if (v != DBUS_VALID) { _dbus_verbose ("Field %d was invalid\n", field_code); *validity = v; goto invalid; } next_field: _dbus_type_reader_next (&array_reader); } /* Anything we didn't fill in is now known not to exist */ i = 0; while (i <= DBUS_HEADER_FIELD_LAST) { if (header->fields[i].value_pos == _DBUS_HEADER_FIELD_VALUE_UNKNOWN) header->fields[i].value_pos = _DBUS_HEADER_FIELD_VALUE_NONEXISTENT; ++i; } v = check_mandatory_fields (header); if (v != DBUS_VALID) { _dbus_verbose ("Mandatory fields were missing, code %d\n", v); *validity = v; goto invalid; } *validity = DBUS_VALID; return TRUE; invalid: _dbus_string_set_length (&header->data, 0); return FALSE; } /** * Fills in the correct body length. * * @param header the header * @param body_len the length of the body */ void _dbus_header_update_lengths (DBusHeader *header, int body_len) { _dbus_marshal_set_uint32 (&header->data, BODY_LENGTH_OFFSET, body_len, header->byte_order); } static dbus_bool_t 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; } /** * Sets the value of a field with basic type. If the value is a string * value, it isn't allowed to be #NULL. If the field doesn't exist, * it will be created. * * @param header the header * @param field the field to set * @param type the type of the value * @param value the value as for _dbus_marshal_set_basic() * @returns #FALSE if no memory */ dbus_bool_t _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; } /** * Gets the value of a field with basic type. If the field * doesn't exist, returns #FALSE, otherwise returns #TRUE. * * @param header the header * @param field the field to get * @param type the type of the value * @param value the value as for _dbus_marshal_read_basic() * @returns #FALSE if the field doesn't exist */ dbus_bool_t _dbus_header_get_field_basic (DBusHeader *header, int field, int type, void *value) { _dbus_assert (field != DBUS_HEADER_FIELD_INVALID); _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); _dbus_assert (_dbus_header_field_types[field].code == field); /* in light of this you might ask why the type is passed in; * the only rationale I can think of is so the caller has * to specify its expectation and breaks if we change it */ _dbus_assert (type == EXPECTED_TYPE_OF_FIELD (field)); if (!_dbus_header_cache_check (header, field)) return FALSE; _dbus_assert (header->fields[field].value_pos >= 0); _dbus_marshal_read_basic (&header->data, header->fields[field].value_pos, type, value, header->byte_order, NULL); return TRUE; } /** * Gets the raw marshaled data for a field. If the field doesn't * exist, returns #FALSE, otherwise returns #TRUE. Returns the start * of the marshaled data, i.e. usually the byte where the length * starts (for strings and arrays) or for basic types just the value * itself. * * @param header the header * @param field the field to get * @param str return location for the data string * @param pos return location for start of field value * @returns #FALSE if the field doesn't exist */ dbus_bool_t _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; } /** * Deletes a field, if it exists. * * @param header the header * @param field the field to delete * @returns #FALSE if no memory */ dbus_bool_t _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; } /** * Toggles a message flag bit, turning on the bit if value = TRUE and * flipping it off if value = FALSE. * * @param header the header * @param flag the message flag to toggle * @param value toggle on or off */ void _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; } /** * Gets a message flag bit, returning TRUE if the bit is set. * * @param header the header * @param flag the message flag to get * @returns #TRUE if the flag is set */ dbus_bool_t _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; } /** * Swaps the header into the given order if required. * * @param header the header * @param new_order the new byte order */ void _dbus_header_byteswap (DBusHeader *header, int new_order) { if (header->byte_order == new_order) return; _dbus_marshal_byteswap (&_dbus_header_signature_str, 0, header->byte_order, new_order, &header->data, 0); header->byte_order = new_order; } /** @} */ #ifdef DBUS_BUILD_TESTS #include "dbus-test.h" #include dbus_bool_t _dbus_marshal_header_test (void) { return TRUE; } #endif /* DBUS_BUILD_TESTS */