summaryrefslogtreecommitdiff
path: root/dbus/dbus-message.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2009-04-22 03:41:05 +0200
committerLennart Poettering <lennart@poettering.net>2009-05-20 02:09:03 +0200
commita0cc21f8bb6752ffe0ee5f4f5b575dc50d6d46ae (patch)
tree950cbd47691d09ecef51dab996e93163d938a2f1 /dbus/dbus-message.c
parentba7daa606cf20ff3b5e992907f380a425feaef01 (diff)
unix-fd: add message encoding/decoding for unix fds
When appending unix fds to the message a new entry in the fd array will be allocated and the index to it will be written to the message payload. When parsing unix fds from the message the index will be read from the payload and then looked up in the fd array. When we read fds we put them in a queue first. Since each message knows how many fds are attached to it we will then pop enough fds from this queue each time we decode a message from the stream. This should make sending and receiving more portable since we don't make any strong requirements on the exact semantics of the SCM_RIGHTS implementation: as long as fds are recieved in order, none or lost and the arrive at the same time as at least one byte from the actual message dat we should be able to handle them correctly.
Diffstat (limited to 'dbus/dbus-message.c')
-rw-r--r--dbus/dbus-message.c434
1 files changed, 424 insertions, 10 deletions
diff --git a/dbus/dbus-message.c b/dbus/dbus-message.c
index edae4258..54bceb0d 100644
--- a/dbus/dbus-message.c
+++ b/dbus/dbus-message.c
@@ -33,6 +33,10 @@
#include "dbus-memory.h"
#include "dbus-list.h"
#include "dbus-threads-internal.h"
+#ifdef HAVE_UNIX_FD_PASSING
+#include "dbus-sysdeps-unix.h"
+#endif
+
#include <string.h>
static void dbus_message_finalize (DBusMessage *message);
@@ -160,6 +164,30 @@ _dbus_message_get_network_data (DBusMessage *message,
}
/**
+ * Gets the unix fds to be sent over the network for this message.
+ * This function is guaranteed to always return the same data once a
+ * message is locked (with dbus_message_lock()).
+ *
+ * @param message the message.
+ * @param fds return location of unix fd array
+ * @param n_fds return number of entries in array
+ */
+void _dbus_message_get_unix_fds(DBusMessage *message,
+ const int **fds,
+ unsigned *n_fds)
+{
+ _dbus_assert (message->locked);
+
+#ifdef HAVE_UNIX_FD_PASSING
+ *fds = message->unix_fds;
+ *n_fds = message->n_unix_fds;
+#else
+ *fds = NULL;
+ *n_fds = 0;
+#endif
+}
+
+/**
* Sets the serial number of a message.
* This can only be done once on a message.
*
@@ -494,6 +522,33 @@ dbus_message_get_cached (void)
return message;
}
+#ifdef HAVE_UNIX_FD_PASSING
+static void
+close_unix_fds(int *fds, unsigned *n_fds)
+{
+ DBusError e;
+ int i;
+
+ if (*n_fds <= 0)
+ return;
+
+ dbus_error_init(&e);
+
+ for (i = 0; i < *n_fds; i++)
+ {
+ if (!_dbus_close(fds[i], &e))
+ {
+ _dbus_warn("Failed to close file descriptor: %s\n", e.message);
+ dbus_error_free(&e);
+ }
+ }
+
+ *n_fds = 0;
+
+ /* We don't free the array here, in case we can recycle it later */
+}
+#endif
+
static void
free_size_counter (void *element,
void *data)
@@ -528,6 +583,10 @@ dbus_message_cache_or_finalize (DBusMessage *message)
free_size_counter, message);
_dbus_list_clear (&message->size_counters);
+#ifdef HAVE_UNIX_FD_PASSING
+ close_unix_fds(message->unix_fds, &message->n_unix_fds);
+#endif
+
was_cached = FALSE;
_DBUS_LOCK (message_cache);
@@ -634,6 +693,12 @@ _dbus_message_iter_check (DBusMessageRealIter *iter)
* dbus_message_get_args() is the place to go for complete
* documentation.
*
+ * Unix file descriptors that are read with this function will have
+ * the FD_CLOEXEC flag set. If you need them without this flag set,
+ * make sure to unset it with fcntl().
+ *
+ * @todo This may leak memory and file descriptors if parsing fails. See #21259
+ *
* @see dbus_message_get_args
* @param iter the message iter
* @param error error to be filled in
@@ -673,7 +738,38 @@ _dbus_message_iter_get_args_valist (DBusMessageIter *iter,
goto out;
}
- if (dbus_type_is_basic (spec_type))
+ if (spec_type == DBUS_TYPE_UNIX_FD)
+ {
+#ifdef HAVE_UNIX_FD_PASSING
+ DBusBasicValue idx;
+ int *pfd, nfd;
+
+ pfd = va_arg (var_args, int*);
+ _dbus_assert(pfd);
+
+ _dbus_type_reader_read_basic(&real->u.reader, &idx);
+
+ if (idx.u32 >= real->message->n_unix_fds)
+ {
+ dbus_set_error (error, DBUS_ERROR_INCONSISTENT_MESSAGE,
+ "Message refers to file descriptor at index %i,"
+ "but has only %i descriptors attached.\n",
+ idx.u32,
+ real->message->n_unix_fds);
+ goto out;
+ }
+
+ if ((nfd = _dbus_dup(real->message->unix_fds[idx.u32], error)) < 0)
+ goto out;
+
+ *pfd = nfd;
+#else
+ dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED,
+ "Platform does not support file desciptor passing.\n");
+ goto out;
+#endif
+ }
+ else if (dbus_type_is_basic (spec_type))
{
DBusBasicValue *ptr;
@@ -707,7 +803,8 @@ _dbus_message_iter_get_args_valist (DBusMessageIter *iter,
goto out;
}
- if (dbus_type_is_fixed (spec_element_type))
+ if (dbus_type_is_fixed (spec_element_type) &&
+ element_type != DBUS_TYPE_UNIX_FD)
{
ptr = va_arg (var_args, const DBusBasicValue**);
n_elements_p = va_arg (var_args, int*);
@@ -943,6 +1040,11 @@ dbus_message_finalize (DBusMessage *message)
_dbus_header_free (&message->header);
_dbus_string_free (&message->body);
+#ifdef HAVE_UNIX_FD_PASSING
+ close_unix_fds(message->unix_fds, &message->n_unix_fds);
+ dbus_free(message->unix_fds);
+#endif
+
_dbus_assert (message->refcount.value == 0);
dbus_free (message);
@@ -969,6 +1071,11 @@ dbus_message_new_empty_header (void)
#ifndef DBUS_DISABLE_CHECKS
message->generation = _dbus_current_generation;
#endif
+
+#ifdef HAVE_UNIX_FD_PASSING
+ message->unix_fds = NULL;
+ message->n_unix_fds_allocated = 0;
+#endif
}
message->refcount.value = 1;
@@ -981,6 +1088,10 @@ dbus_message_new_empty_header (void)
message->size_counter_delta = 0;
message->changed_stamp = 0;
+#ifdef HAVE_UNIX_FD_PASSING
+ message->n_unix_fds = 0;
+#endif
+
if (!from_cache)
_dbus_data_slot_list_init (&message->slot_list);
@@ -1308,8 +1419,10 @@ dbus_message_new_error_printf (DBusMessage *reply_to,
* outgoing message queue and thus not modifiable) the new message
* will not be locked.
*
+ * @todo This function can't be used in programs that try to recover from OOM errors.
+ *
* @param message the message
- * @returns the new message.or #NULL if not enough memory
+ * @returns the new message.or #NULL if not enough memory or Unix file descriptors (in case the message to copy includes Unix file descriptors) can be allocated.
*/
DBusMessage *
dbus_message_copy (const DBusMessage *message)
@@ -1347,11 +1460,36 @@ dbus_message_copy (const DBusMessage *message)
&retval->body, 0))
goto failed_copy;
+#ifdef HAVE_UNIX_FD_PASSING
+ retval->unix_fds = dbus_new(int, message->n_unix_fds);
+ if (retval->unix_fds == NULL && message->n_unix_fds > 0)
+ goto failed_copy;
+
+ retval->n_unix_fds_allocated = message->n_unix_fds;
+
+ for (retval->n_unix_fds = 0;
+ retval->n_unix_fds < message->n_unix_fds;
+ retval->n_unix_fds++)
+ {
+ retval->unix_fds[retval->n_unix_fds] = _dbus_dup(message->unix_fds[retval->n_unix_fds], NULL);
+
+ if (retval->unix_fds[retval->n_unix_fds] < 0)
+ goto failed_copy;
+ }
+
+#endif
+
return retval;
failed_copy:
_dbus_header_free (&retval->header);
_dbus_string_free (&retval->body);
+
+#ifdef HAVE_UNIX_FD_PASSING
+ close_unix_fds(retval->unix_fds, &retval->n_unix_fds);
+ dbus_free(retval->unix_fds);
+#endif
+
dbus_free (retval);
return NULL;
@@ -1555,8 +1693,9 @@ dbus_message_append_args_valist (DBusMessage *message,
buf,
&array))
goto failed;
-
- if (dbus_type_is_fixed (element_type))
+
+ if (dbus_type_is_fixed (element_type) &&
+ element_type != DBUS_TYPE_UNIX_FD)
{
const DBusBasicValue **value;
int n_elements;
@@ -1973,8 +2112,30 @@ dbus_message_iter_get_basic (DBusMessageIter *iter,
_dbus_return_if_fail (_dbus_message_iter_check (real));
_dbus_return_if_fail (value != NULL);
- _dbus_type_reader_read_basic (&real->u.reader,
- value);
+ if (dbus_message_iter_get_arg_type (iter) == DBUS_TYPE_UNIX_FD)
+ {
+#ifdef HAVE_UNIX_FD_PASSING
+ DBusBasicValue idx;
+
+ _dbus_type_reader_read_basic(&real->u.reader, &idx);
+
+ if (idx.u32 >= real->message->n_unix_fds) {
+ /* Hmm, we cannot really signal an error here, so let's make
+ sure to return an invalid fd. */
+ *((int*) value) = -1;
+ return;
+ }
+
+ *((int*) value) = _dbus_dup(real->message->unix_fds[idx.u32], NULL);
+#else
+ *((int*) value) = -1;
+#endif
+ }
+ else
+ {
+ _dbus_type_reader_read_basic (&real->u.reader,
+ value);
+ }
}
/**
@@ -2047,7 +2208,7 @@ dbus_message_iter_get_fixed_array (DBusMessageIter *iter,
_dbus_return_if_fail (_dbus_message_iter_check (real));
_dbus_return_if_fail (value != NULL);
_dbus_return_if_fail ((subtype == DBUS_TYPE_INVALID) ||
- dbus_type_is_fixed (subtype));
+ (dbus_type_is_fixed (subtype) && subtype != DBUS_TYPE_UNIX_FD));
_dbus_type_reader_read_fixed_multi (&real->u.reader,
value, n_elements);
@@ -2217,6 +2378,40 @@ _dbus_message_iter_append_check (DBusMessageRealIter *iter)
}
#endif /* DBUS_DISABLE_CHECKS */
+#ifdef HAVE_UNIX_FD_PASSING
+static int *
+expand_fd_array(DBusMessage *m,
+ unsigned n)
+{
+ _dbus_assert(m);
+
+ /* This makes space for adding n new fds to the array and returns a
+ pointer to the place were the first fd should be put. */
+
+ if (m->n_unix_fds + n > m->n_unix_fds_allocated)
+ {
+ unsigned k;
+ int *p;
+
+ /* Make twice as much space as necessary */
+ k = (m->n_unix_fds + n) * 2;
+
+ /* Allocate at least four */
+ if (k < 4)
+ k = 4;
+
+ p = dbus_realloc(m->unix_fds, k * sizeof(int));
+ if (p == NULL)
+ return NULL;
+
+ m->unix_fds = p;
+ m->n_unix_fds_allocated = k;
+ }
+
+ return m->unix_fds + m->n_unix_fds;
+}
+#endif
+
/**
* Appends a basic-typed value to the message. The basic types are the
* non-container types such as integer and string.
@@ -2248,7 +2443,50 @@ dbus_message_iter_append_basic (DBusMessageIter *iter,
if (!_dbus_message_iter_open_signature (real))
return FALSE;
- ret = _dbus_type_writer_write_basic (&real->u.writer, type, value);
+ if (type == DBUS_TYPE_UNIX_FD)
+ {
+#ifdef HAVE_UNIX_FD_PASSING
+ int *fds;
+ dbus_uint32_t u;
+
+ /* First step, include the fd in the fd list of this message */
+ if (!(fds = expand_fd_array(real->message, 1)))
+ return FALSE;
+
+ *fds = _dbus_dup(*(int*) value, NULL);
+ if (*fds < 0)
+ return FALSE;
+
+ u = real->message->n_unix_fds;
+
+ /* Second step, write the index to the fd */
+ if (!(ret = _dbus_type_writer_write_basic (&real->u.writer, DBUS_TYPE_UNIX_FD, &u))) {
+ _dbus_close(*fds, NULL);
+ return FALSE;
+ }
+
+ real->message->n_unix_fds += 1;
+ u += 1;
+
+ /* Final step, update the header accordingly */
+ ret = _dbus_header_set_field_basic (&real->message->header,
+ DBUS_HEADER_FIELD_UNIX_FDS,
+ DBUS_TYPE_UINT32,
+ &u);
+
+ /* If any of these operations fail the message is
+ hosed. However, no memory or fds should be leaked since what
+ has been added to message has been added to the message, and
+ can hence be accounted for when the message is being
+ freed. */
+#else
+ ret = FALSE;
+#endif
+ }
+ else
+ {
+ ret = _dbus_type_writer_write_basic (&real->u.writer, type, value);
+ }
if (!_dbus_message_iter_close_signature (real))
ret = FALSE;
@@ -2302,7 +2540,7 @@ dbus_message_iter_append_fixed_array (DBusMessageIter *iter,
_dbus_return_val_if_fail (_dbus_message_iter_append_check (real), FALSE);
_dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER, FALSE);
- _dbus_return_val_if_fail (dbus_type_is_fixed (element_type), FALSE);
+ _dbus_return_val_if_fail (dbus_type_is_fixed (element_type) && element_type != DBUS_TYPE_UNIX_FD, FALSE);
_dbus_return_val_if_fail (real->u.writer.container_type == DBUS_TYPE_ARRAY, FALSE);
_dbus_return_val_if_fail (value != NULL, FALSE);
_dbus_return_val_if_fail (n_elements >= 0, FALSE);
@@ -3321,6 +3559,12 @@ _dbus_message_loader_new (void)
/* this can be configured by the app, but defaults to the protocol max */
loader->max_message_size = DBUS_MAXIMUM_MESSAGE_LENGTH;
+ /* We set a very relatively conservative default here since due to how
+ SCM_RIGHTS works we need to preallocate an fd array of the maximum
+ number of unix fds we want to receive in advance. A
+ try-and-reallocate loop is not possible. */
+ loader->max_message_unix_fds = 1024;
+
if (!_dbus_string_init (&loader->data))
{
dbus_free (loader);
@@ -3331,6 +3575,12 @@ _dbus_message_loader_new (void)
_dbus_string_set_length (&loader->data, INITIAL_LOADER_DATA_LEN);
_dbus_string_set_length (&loader->data, 0);
+#ifdef HAVE_UNIX_FD_PASSING
+ loader->unix_fds = NULL;
+ loader->n_unix_fds = loader->n_unix_fds_allocated = 0;
+ loader->unix_fds_outstanding = FALSE;
+#endif
+
return loader;
}
@@ -3360,6 +3610,10 @@ _dbus_message_loader_unref (DBusMessageLoader *loader)
loader->refcount -= 1;
if (loader->refcount == 0)
{
+#ifdef HAVE_UNIX_FD_PASSING
+ close_unix_fds(loader->unix_fds, &loader->n_unix_fds);
+ dbus_free(loader->unix_fds);
+#endif
_dbus_list_foreach (&loader->messages,
(DBusForeachFunction) dbus_message_unref,
NULL);
@@ -3419,6 +3673,81 @@ _dbus_message_loader_return_buffer (DBusMessageLoader *loader,
loader->buffer_outstanding = FALSE;
}
+/**
+ * Gets the buffer to use for reading unix fds from the network.
+ *
+ * This works similar to _dbus_message_loader_get_buffer()
+ *
+ * @param loader the message loader.
+ * @param fds the array to read fds into
+ * @param max_n_fds how many fds to read at most
+ * @return TRUE on success, FALSE on OOM
+ */
+dbus_bool_t
+_dbus_message_loader_get_unix_fds(DBusMessageLoader *loader,
+ int **fds,
+ unsigned *max_n_fds)
+{
+#ifdef HAVE_UNIX_FD_PASSING
+ _dbus_assert (!loader->unix_fds_outstanding);
+
+ /* Allocate space where we can put the fds we read. We allocate
+ space for max_message_unix_fds since this is an
+ upper limit how many fds can be received within a single
+ message. Since SCM_RIGHTS doesn't allow a reallocate+retry logic
+ we are allocating the maximum possible array size right from the
+ beginning. This sucks a bit, however unless SCM_RIGHTS is fixed
+ there is no better way. */
+
+ if (loader->n_unix_fds_allocated < loader->max_message_unix_fds)
+ {
+ int *a = dbus_realloc(loader->unix_fds,
+ loader->max_message_unix_fds * sizeof(loader->unix_fds[0]));
+
+ if (!a)
+ return FALSE;
+
+ loader->unix_fds = a;
+ loader->n_unix_fds_allocated = loader->max_message_unix_fds;
+ }
+
+ *fds = loader->unix_fds + loader->n_unix_fds;
+ *max_n_fds = loader->n_unix_fds_allocated - loader->n_unix_fds;
+
+ loader->unix_fds_outstanding = TRUE;
+ return TRUE;
+#else
+ _dbus_assert_not_reached("Platform doesn't support unix fd passing");
+#endif
+}
+
+/**
+ * Returns a buffer obtained from _dbus_message_loader_get_unix_fds().
+ *
+ * This works similar to _dbus_message_loader_return_buffer()
+ *
+ * @param loader the message loader.
+ * @param fds the array fds were read into
+ * @param max_n_fds how many fds were read
+ */
+
+void
+_dbus_message_loader_return_unix_fds(DBusMessageLoader *loader,
+ int *fds,
+ unsigned n_fds)
+{
+#ifdef HAVE_UNIX_FD_PASSING
+ _dbus_assert(loader->unix_fds_outstanding);
+ _dbus_assert(loader->unix_fds + loader->n_unix_fds == fds);
+ _dbus_assert(loader->n_unix_fds + n_fds <= loader->n_unix_fds_allocated);
+
+ loader->n_unix_fds += n_fds;
+ loader->unix_fds_outstanding = FALSE;
+#else
+ _dbus_assert_not_reached("Platform doesn't support unix fd passing");
+#endif
+}
+
/*
* FIXME when we move the header out of the buffer, that memmoves all
* buffered messages. Kind of crappy.
@@ -3458,6 +3787,7 @@ load_message (DBusMessageLoader *loader,
const DBusString *type_str;
int type_pos;
DBusValidationMode mode;
+ dbus_uint32_t n_unix_fds = 0;
mode = DBUS_VALIDATION_MODE_DATA_IS_UNTRUSTED;
@@ -3527,6 +3857,59 @@ load_message (DBusMessageLoader *loader,
}
}
+ /* 3. COPY OVER UNIX FDS */
+ _dbus_header_get_field_basic(&message->header,
+ DBUS_HEADER_FIELD_UNIX_FDS,
+ DBUS_TYPE_UINT32,
+ &n_unix_fds);
+
+#ifdef HAVE_UNIX_FD_PASSING
+
+ if (n_unix_fds > loader->n_unix_fds)
+ {
+ _dbus_verbose("Message contains references to more unix fds than were sent %u != %u\n",
+ n_unix_fds, loader->n_unix_fds);
+
+ loader->corrupted = TRUE;
+ loader->corruption_reason = DBUS_INVALID_MISSING_UNIX_FDS;
+ goto failed;
+ }
+
+ /* If this was a recycled message there might still be
+ some memory allocated for the fds */
+ dbus_free(message->unix_fds);
+
+ if (n_unix_fds > 0)
+ {
+ message->unix_fds = _dbus_memdup(loader->unix_fds, n_unix_fds * sizeof(message->unix_fds[0]));
+ if (message->unix_fds == NULL)
+ {
+ _dbus_verbose ("Failed to allocate file descriptor array\n");
+ oom = TRUE;
+ goto failed;
+ }
+
+ message->n_unix_fds_allocated = message->n_unix_fds = n_unix_fds;
+ loader->n_unix_fds -= n_unix_fds;
+ memmove(loader->unix_fds + n_unix_fds, loader->unix_fds, loader->n_unix_fds);
+ }
+ else
+ message->unix_fds = NULL;
+
+#else
+
+ if (n_unix_fds > 0)
+ {
+ _dbus_verbose ("Hmm, message claims to come with file descriptors "
+ "but that's not supported on our platform, disconnecting.\n");
+
+ loader->corrupted = TRUE;
+ loader->corruption_reason = DBUS_INVALID_MISSING_UNIX_FDS;
+ goto failed;
+ }
+
+#endif
+
/* 3. COPY OVER BODY AND QUEUE MESSAGE */
if (!_dbus_list_append (&loader->messages, message))
@@ -3756,6 +4139,37 @@ _dbus_message_loader_get_max_message_size (DBusMessageLoader *loader)
return loader->max_message_size;
}
+/**
+ * Sets the maximum unix fds per message we allow.
+ *
+ * @param loader the loader
+ * @param size the max number of unix fds in a message
+ */
+void
+_dbus_message_loader_set_max_message_unix_fds (DBusMessageLoader *loader,
+ long n)
+{
+ if (n > DBUS_MAXIMUM_MESSAGE_UNIX_FDS)
+ {
+ _dbus_verbose ("clamping requested max message unix_fds %ld to %d\n",
+ n, DBUS_MAXIMUM_MESSAGE_UNIX_FDS);
+ n = DBUS_MAXIMUM_MESSAGE_UNIX_FDS;
+ }
+ loader->max_message_unix_fds = n;
+}
+
+/**
+ * Gets the maximum allowed number of unix fds per message
+ *
+ * @param loader the loader
+ * @returns max unix fds
+ */
+long
+_dbus_message_loader_get_max_message_unix_fds (DBusMessageLoader *loader)
+{
+ return loader->max_message_unix_fds;
+}
+
static DBusDataSlotAllocator slot_allocator;
_DBUS_DEFINE_GLOBAL_LOCK (message_slots);