gstreamer/gst/gststreamcollection.c
Vincent Penquerc'h efbacf1931 streamcollection: fix racy user-after-free
The issue happens when the structure is printed by the logging
subsystem: the object is included in the log, and this will cause the
full object printout to be done there. However, after dispose, the queue
was already cleared, so the access to it (to print the object) would
assert, as the queue was already freed. The patch changes it so that the
queue is merely empty, and only freed in _finalize.

https://bugzilla.gnome.org/show_bug.cgi?id=776293
2017-03-31 11:32:31 +01:00

349 lines
9.5 KiB
C

/* GStreamer
*
* Copyright (C) 2015 Centricular Ltd
* @author: Edward Hervey <edward@centricular.com>
* @author: Jan Schmidt <jan@centricular.com>
*
* gststreams.c: GstStreamCollection object and methods
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* MT safe.
*/
/**
* SECTION:gststreamcollection
* @title: GstStreamCollection
* @short_description: Base class for collection of streams
*
* Since: 1.10
*/
#include "gst_private.h"
#include "gstenumtypes.h"
#include "gstevent.h"
#include "gststreamcollection.h"
GST_DEBUG_CATEGORY_STATIC (stream_collection_debug);
#define GST_CAT_DEFAULT stream_collection_debug
#define GST_STREAM_COLLECTION_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_STREAM_COLLECTION, GstStreamCollectionPrivate))
struct _GstStreamCollectionPrivate
{
/* Maybe switch this to a GArray if performance is
* ever an issue? */
GQueue *streams;
};
/* stream signals and properties */
enum
{
SIG_STREAM_NOTIFY,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_UPSTREAM_ID,
PROP_LAST
};
static guint gst_stream_collection_signals[LAST_SIGNAL] = { 0 };
static void gst_stream_collection_dispose (GObject * object);
static void gst_stream_collection_finalize (GObject * object);
static void gst_stream_collection_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_stream_collection_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void
proxy_stream_notify_cb (GstStream * stream, GParamSpec * pspec,
GstStreamCollection * collection);
#define _do_init \
{ \
GST_DEBUG_CATEGORY_INIT (stream_collection_debug, "streamcollection", GST_DEBUG_BOLD, \
"debugging info for the stream collection objects"); \
\
}
#define gst_stream_collection_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstStreamCollection, gst_stream_collection,
GST_TYPE_OBJECT, _do_init);
static void
gst_stream_collection_class_init (GstStreamCollectionClass * klass)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) klass;
g_type_class_add_private (klass, sizeof (GstStreamCollectionPrivate));
gobject_class->set_property = gst_stream_collection_set_property;
gobject_class->get_property = gst_stream_collection_get_property;
/**
* GstStream:upstream-id:
*
* stream-id
*/
g_object_class_install_property (gobject_class, PROP_UPSTREAM_ID,
g_param_spec_string ("upstream-id", "Upstream ID",
"The stream ID of the parent stream",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
/**
* GstStream::stream-notify:
* @collection: a #GstStreamCollection
* @prop_stream: the #GstStream that originated the signal
* @prop: the property that changed
*
* The stream notify signal is used to be notified of property changes to
* streams within the collection.
*/
gst_stream_collection_signals[SIG_STREAM_NOTIFY] =
g_signal_new ("stream-notify", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED |
G_SIGNAL_NO_HOOKS, G_STRUCT_OFFSET (GstStreamCollectionClass,
stream_notify), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
2, GST_TYPE_STREAM, G_TYPE_PARAM);
gobject_class->dispose = gst_stream_collection_dispose;
gobject_class->finalize = gst_stream_collection_finalize;
}
static void
gst_stream_collection_init (GstStreamCollection * collection)
{
collection->priv = GST_STREAM_COLLECTION_GET_PRIVATE (collection);
collection->priv->streams = g_queue_new ();
}
static void
release_gst_stream (GstStream * stream, GstStreamCollection * collection)
{
g_signal_handlers_disconnect_by_func (stream,
proxy_stream_notify_cb, collection);
gst_object_unref (stream);
}
static void
gst_stream_collection_dispose (GObject * object)
{
GstStreamCollection *collection = GST_STREAM_COLLECTION_CAST (object);
if (collection->upstream_id) {
g_free (collection->upstream_id);
collection->upstream_id = NULL;
}
if (collection->priv->streams) {
g_queue_foreach (collection->priv->streams,
(GFunc) release_gst_stream, collection);
g_queue_clear (collection->priv->streams);
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_stream_collection_finalize (GObject * object)
{
GstStreamCollection *collection = GST_STREAM_COLLECTION_CAST (object);
if (collection->priv->streams)
g_queue_free (collection->priv->streams);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* gst_stream_collection_new:
* @upstream_id: (allow-none): The stream id of the parent stream
*
* Create a new #GstStreamCollection.
*
* Returns: The new #GstStreamCollection.
*
* Since: 1.10
*/
GstStreamCollection *
gst_stream_collection_new (const gchar * upstream_id)
{
return g_object_new (GST_TYPE_STREAM_COLLECTION, "upstream-id", upstream_id,
NULL);
}
static void
gst_stream_collection_set_upstream_id (GstStreamCollection * collection,
const gchar * upstream_id)
{
g_return_if_fail (collection->upstream_id == NULL);
/* Upstream ID should only be set once on construction, but let's
* not leak in case someone does something silly */
if (collection->upstream_id)
g_free (collection->upstream_id);
if (upstream_id)
collection->upstream_id = g_strdup (upstream_id);
}
/**
* gst_stream_collection_get_upstream_id:
* @collection: a #GstStreamCollection
*
* Returns the upstream id of the @collection.
*
* Returns: (transfer none): The upstream id
*
* Since: 1.10
*/
const gchar *
gst_stream_collection_get_upstream_id (GstStreamCollection * collection)
{
const gchar *res;
res = collection->upstream_id;
return res;
}
static void
gst_stream_collection_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstStreamCollection *collection;
collection = GST_STREAM_COLLECTION_CAST (object);
switch (prop_id) {
case PROP_UPSTREAM_ID:
gst_stream_collection_set_upstream_id (collection,
g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_stream_collection_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstStreamCollection *collection;
collection = GST_STREAM_COLLECTION_CAST (object);
switch (prop_id) {
case PROP_UPSTREAM_ID:
g_value_set_string (value,
gst_stream_collection_get_upstream_id (collection));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
proxy_stream_notify_cb (GstStream * stream, GParamSpec * pspec,
GstStreamCollection * collection)
{
GST_DEBUG_OBJECT (collection, "Stream %" GST_PTR_FORMAT " updated %s",
stream, pspec->name);
g_signal_emit (collection, gst_stream_collection_signals[SIG_STREAM_NOTIFY],
g_quark_from_string (pspec->name), stream, pspec);
}
/**
* gst_stream_collection_add_stream:
* @collection: a #GstStreamCollection
* @stream: (transfer full): the #GstStream to add
*
* Add the given @stream to the @collection.
*
* Returns: %TRUE if the @stream was properly added, else %FALSE
*
* Since: 1.10
*/
gboolean
gst_stream_collection_add_stream (GstStreamCollection * collection,
GstStream * stream)
{
g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), FALSE);
g_return_val_if_fail (GST_IS_STREAM (stream), FALSE);
g_return_val_if_fail (collection->priv->streams, FALSE);
GST_DEBUG_OBJECT (collection, "Adding stream %" GST_PTR_FORMAT, stream);
g_queue_push_tail (collection->priv->streams, stream);
g_signal_connect (stream, "notify", (GCallback) proxy_stream_notify_cb,
collection);
return TRUE;
}
/**
* gst_stream_collection_get_size:
* @collection: a #GstStreamCollection
*
* Get the number of streams this collection contains
*
* Returns: The number of streams that @collection contains
*
* Since: 1.10
*/
guint
gst_stream_collection_get_size (GstStreamCollection * collection)
{
g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), 0);
g_return_val_if_fail (collection->priv->streams, 0);
return g_queue_get_length (collection->priv->streams);
}
/**
* gst_stream_collection_get_stream:
* @collection: a #GstStreamCollection
* @index: Index of the stream to retrieve
*
* Retrieve the #GstStream with index @index from the collection.
*
* The caller should not modify the returned #GstStream
*
* Returns: (transfer none): A #GstStream
*
* Since: 1.10
*/
GstStream *
gst_stream_collection_get_stream (GstStreamCollection * collection, guint index)
{
g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), NULL);
g_return_val_if_fail (collection->priv->streams, NULL);
return g_queue_peek_nth (collection->priv->streams, index);
}