queuearray: Fix potential heap overflow when expanding GstQueueArray
[platform/upstream/gstreamer.git] / subprojects / gstreamer / libs / gst / base / gstqueuearray.c
1 /* GStreamer
2  * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com>
3  * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
4  *
5  * gstqueuearray.c:
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 /**
24  * SECTION:gstqueuearray
25  * @title: GstQueueArray
26  * @short_description: Array based queue object
27  *
28  * #GstQueueArray is an object that provides standard queue functionality
29  * based on an array instead of linked lists. This reduces the overhead
30  * caused by memory management by a large factor.
31  */
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35
36 #include <string.h>
37 #include <gst/gst.h>
38 #include "gstqueuearray.h"
39
40 struct _GstQueueArray
41 {
42   /* < private > */
43   guint8 *array;
44   guint size;
45   guint head;
46   guint tail;
47   guint length;
48   guint elt_size;
49   gboolean struct_array;
50   GDestroyNotify clear_func;
51 };
52
53 /**
54  * gst_queue_array_new_for_struct: (skip)
55  * @struct_size: Size of each element (e.g. structure) in the array
56  * @initial_size: Initial size of the new queue
57  *
58  * Allocates a new #GstQueueArray object for elements (e.g. structures)
59  * of size @struct_size, with an initial queue size of @initial_size.
60  *
61  * Returns: a new #GstQueueArray object
62  *
63  * Since: 1.6
64  */
65 GstQueueArray *
66 gst_queue_array_new_for_struct (gsize struct_size, guint initial_size)
67 {
68   GstQueueArray *array;
69
70   g_return_val_if_fail (struct_size > 0, NULL);
71
72   array = g_slice_new (GstQueueArray);
73   array->elt_size = struct_size;
74   array->size = initial_size;
75   array->array = g_malloc0 (struct_size * initial_size);
76   array->head = 0;
77   array->tail = 0;
78   array->length = 0;
79   array->struct_array = TRUE;
80   array->clear_func = NULL;
81   return array;
82 }
83
84 /**
85  * gst_queue_array_new: (skip)
86  * @initial_size: Initial size of the new queue
87  *
88  * Allocates a new #GstQueueArray object with an initial
89  * queue size of @initial_size.
90  *
91  * Returns: a new #GstQueueArray object
92  *
93  * Since: 1.2
94  */
95 GstQueueArray *
96 gst_queue_array_new (guint initial_size)
97 {
98   GstQueueArray *array;
99
100   array = gst_queue_array_new_for_struct (sizeof (gpointer), initial_size);
101   array->struct_array = FALSE;
102   return array;
103 }
104
105 /**
106  * gst_queue_array_free: (skip)
107  * @array: a #GstQueueArray object
108  *
109  * Frees queue @array and all memory associated to it.
110  *
111  * Since: 1.2
112  */
113 void
114 gst_queue_array_free (GstQueueArray * array)
115 {
116   g_return_if_fail (array != NULL);
117   gst_queue_array_clear (array);
118   g_free (array->array);
119   g_slice_free (GstQueueArray, array);
120 }
121
122 /**
123  * gst_queue_array_set_clear_func: (skip)
124  * @array: a #GstQueueArray object
125  * @clear_func: a function to clear an element of @array
126  *
127  * Sets a function to clear an element of @array.
128  *
129  * The @clear_func will be called when an element in the array
130  * data segment is removed and when the array is freed and data
131  * segment is deallocated as well. @clear_func will be passed a
132  * pointer to the element to clear, rather than the element itself.
133  *
134  * Note that in contrast with other uses of #GDestroyNotify
135  * functions, @clear_func is expected to clear the contents of
136  * the array element it is given, but not free the element itself.
137  *
138  * Since: 1.16
139  */
140 void
141 gst_queue_array_set_clear_func (GstQueueArray * array,
142     GDestroyNotify clear_func)
143 {
144   g_return_if_fail (array != NULL);
145   array->clear_func = clear_func;
146 }
147
148 static void
149 gst_queue_array_clear_idx (GstQueueArray * array, guint idx)
150 {
151   guint pos;
152
153   if (!array->clear_func)
154     return;
155
156   pos = (idx + array->head) % array->size;
157   if (array->struct_array)
158     array->clear_func (array->array + pos * array->elt_size);
159   else
160     array->clear_func (*(gpointer *) (array->array + pos * array->elt_size));
161 }
162
163 /**
164  * gst_queue_array_clear: (skip)
165  * @array: a #GstQueueArray object
166  *
167  * Clears queue @array and frees all memory associated to it.
168  *
169  * Since: 1.16
170  */
171 void
172 gst_queue_array_clear (GstQueueArray * array)
173 {
174   g_return_if_fail (array != NULL);
175
176   if (array->clear_func != NULL) {
177     guint i;
178
179     for (i = 0; i < array->length; i++) {
180       gst_queue_array_clear_idx (array, i);
181     }
182   }
183
184   array->head = 0;
185   array->tail = 0;
186   array->length = 0;
187 }
188
189 /**
190  * gst_queue_array_pop_head_struct: (skip)
191  * @array: a #GstQueueArray object
192  *
193  * Returns the head of the queue @array and removes it from the queue.
194  *
195  * Returns: pointer to element or struct, or NULL if @array was empty. The
196  *    data pointed to by the returned pointer stays valid only as long as
197  *    the queue array is not modified further!
198  *
199  * Since: 1.6
200  */
201 gpointer
202 gst_queue_array_pop_head_struct (GstQueueArray * array)
203 {
204   gpointer p_struct;
205   g_return_val_if_fail (array != NULL, NULL);
206   /* empty array */
207   if (G_UNLIKELY (array->length == 0))
208     return NULL;
209
210   p_struct = array->array + (array->elt_size * array->head);
211
212   array->head++;
213   array->head %= array->size;
214   array->length--;
215
216   return p_struct;
217 }
218
219 /**
220  * gst_queue_array_pop_head: (skip)
221  * @array: a #GstQueueArray object
222  *
223  * Returns and head of the queue @array and removes
224  * it from the queue.
225  *
226  * Returns: The head of the queue
227  *
228  * Since: 1.2
229  */
230 gpointer
231 gst_queue_array_pop_head (GstQueueArray * array)
232 {
233   gpointer ret;
234   g_return_val_if_fail (array != NULL, NULL);
235
236   /* empty array */
237   if (G_UNLIKELY (array->length == 0))
238     return NULL;
239
240   ret = *(gpointer *) (array->array + (sizeof (gpointer) * array->head));
241   array->head++;
242   array->head %= array->size;
243   array->length--;
244   return ret;
245 }
246
247 /**
248  * gst_queue_array_peek_head_struct: (skip)
249  * @array: a #GstQueueArray object
250  *
251  * Returns the head of the queue @array without removing it from the queue.
252  *
253  * Returns: pointer to element or struct, or NULL if @array was empty. The
254  *    data pointed to by the returned pointer stays valid only as long as
255  *    the queue array is not modified further!
256  *
257  * Since: 1.6
258  */
259 gpointer
260 gst_queue_array_peek_head_struct (GstQueueArray * array)
261 {
262   g_return_val_if_fail (array != NULL, NULL);
263   /* empty array */
264   if (G_UNLIKELY (array->length == 0))
265     return NULL;
266
267   return array->array + (array->elt_size * array->head);
268 }
269
270 /**
271  * gst_queue_array_peek_head: (skip)
272  * @array: a #GstQueueArray object
273  *
274  * Returns the head of the queue @array and does not
275  * remove it from the queue.
276  *
277  * Returns: The head of the queue
278  *
279  * Since: 1.2
280  */
281 gpointer
282 gst_queue_array_peek_head (GstQueueArray * array)
283 {
284   g_return_val_if_fail (array != NULL, NULL);
285   /* empty array */
286   if (G_UNLIKELY (array->length == 0))
287     return NULL;
288
289   return *(gpointer *) (array->array + (sizeof (gpointer) * array->head));
290 }
291
292 /**
293  * gst_queue_array_peek_nth: (skip)
294  *
295  * Returns the item at @idx in @array, but does not remove it from the queue.
296  *
297  * Returns: The item, or %NULL if @idx was out of bounds
298  *
299  * Since: 1.16
300  */
301 gpointer
302 gst_queue_array_peek_nth (GstQueueArray * array, guint idx)
303 {
304   g_return_val_if_fail (array != NULL, NULL);
305   g_return_val_if_fail (idx < array->length, NULL);
306
307   idx = (array->head + idx) % array->size;
308
309   return *(gpointer *) (array->array + (sizeof (gpointer) * idx));
310 }
311
312 /**
313  * gst_queue_array_peek_nth_struct: (skip)
314  *
315  * Returns the item at @idx in @array, but does not remove it from the queue.
316  *
317  * Returns: The item, or %NULL if @idx was out of bounds
318  *
319  * Since: 1.16
320  */
321 gpointer
322 gst_queue_array_peek_nth_struct (GstQueueArray * array, guint idx)
323 {
324   g_return_val_if_fail (array != NULL, NULL);
325   g_return_val_if_fail (idx < array->length, NULL);
326
327   idx = (array->head + idx) % array->size;
328
329   return array->array + (array->elt_size * idx);
330 }
331
332 static void
333 gst_queue_array_do_expand (GstQueueArray * array)
334 {
335   gsize elt_size = array->elt_size;
336   /* newsize is 50% bigger */
337   gsize oldsize = array->size;
338   guint64 newsize;
339
340   newsize = MAX ((3 * (guint64) oldsize) / 2, (guint64) oldsize + 1);
341   if (newsize > G_MAXUINT)
342     g_error ("growing the queue array would overflow");
343
344   /* copy over data */
345   if (array->tail != 0) {
346     guint8 *array2 = NULL;
347     gsize t1 = 0;
348     gsize t2 = 0;
349
350     array2 = g_malloc0_n (newsize, elt_size);
351     t1 = array->head;
352     t2 = oldsize - array->head;
353
354     /* [0-----TAIL][HEAD------SIZE]
355      *
356      * We want to end up with
357      * [HEAD------------------TAIL][----FREEDATA------NEWSIZE]
358      *
359      * 1) move [HEAD-----SIZE] part to beginning of new array
360      * 2) move [0-------TAIL] part new array, after previous part
361      */
362
363     memcpy (array2, array->array + (elt_size * (gsize) array->head),
364         t2 * elt_size);
365     memcpy (array2 + t2 * elt_size, array->array, t1 * elt_size);
366
367     g_free (array->array);
368     array->array = array2;
369     array->head = 0;
370   } else {
371     /* Fast path, we just need to grow the array */
372     array->array = g_realloc_n (array->array, newsize, elt_size);
373     memset (array->array + elt_size * oldsize, 0,
374         elt_size * (newsize - oldsize));
375   }
376   array->tail = oldsize;
377   array->size = newsize;
378 }
379
380 /**
381  * gst_queue_array_push_element_tail: (skip)
382  * @array: a #GstQueueArray object
383  * @p_struct: address of element or structure to push to the tail of the queue
384  *
385  * Pushes the element at address @p_struct to the tail of the queue @array
386  * (Copies the contents of a structure of the struct_size specified when
387  * creating the queue into the array).
388  *
389  * Since: 1.6
390  */
391 void
392 gst_queue_array_push_tail_struct (GstQueueArray * array, gpointer p_struct)
393 {
394   guint elt_size;
395
396   g_return_if_fail (p_struct != NULL);
397   g_return_if_fail (array != NULL);
398   elt_size = array->elt_size;
399
400   /* Check if we need to make room */
401   if (G_UNLIKELY (array->length == array->size))
402     gst_queue_array_do_expand (array);
403
404   memcpy (array->array + elt_size * array->tail, p_struct, elt_size);
405   array->tail++;
406   array->tail %= array->size;
407   array->length++;
408 }
409
410 /**
411  * gst_queue_array_push_tail: (skip)
412  * @array: a #GstQueueArray object
413  * @data: object to push
414  *
415  * Pushes @data to the tail of the queue @array.
416  *
417  * Since: 1.2
418  */
419 void
420 gst_queue_array_push_tail (GstQueueArray * array, gpointer data)
421 {
422   g_return_if_fail (array != NULL);
423
424   /* Check if we need to make room */
425   if (G_UNLIKELY (array->length == array->size))
426     gst_queue_array_do_expand (array);
427
428   *(gpointer *) (array->array + sizeof (gpointer) * array->tail) = data;
429   array->tail++;
430   array->tail %= array->size;
431   array->length++;
432 }
433
434 /**
435  * gst_queue_array_peek_tail: (skip)
436  * @array: a #GstQueueArray object
437  *
438  * Returns the tail of the queue @array, but does not remove it from the queue.
439  *
440  * Returns: The tail of the queue
441  *
442  * Since: 1.14
443  */
444 gpointer
445 gst_queue_array_peek_tail (GstQueueArray * array)
446 {
447   guint len, idx;
448
449   g_return_val_if_fail (array != NULL, NULL);
450
451   len = array->length;
452
453   /* empty array */
454   if (len == 0)
455     return NULL;
456
457   idx = (array->head + (len - 1)) % array->size;
458
459   return *(gpointer *) (array->array + (sizeof (gpointer) * idx));
460 }
461
462 /**
463  * gst_queue_array_peek_tail_struct: (skip)
464  * @array: a #GstQueueArray object
465  *
466  * Returns the tail of the queue @array, but does not remove it from the queue.
467  *
468  * Returns: The tail of the queue
469  *
470  * Since: 1.14
471  */
472 gpointer
473 gst_queue_array_peek_tail_struct (GstQueueArray * array)
474 {
475   guint len, idx;
476
477   g_return_val_if_fail (array != NULL, NULL);
478
479   len = array->length;
480
481   /* empty array */
482   if (len == 0)
483     return NULL;
484
485   idx = (array->head + (len - 1)) % array->size;
486
487   return array->array + (array->elt_size * idx);
488 }
489
490 /**
491  * gst_queue_array_pop_tail: (skip)
492  * @array: a #GstQueueArray object
493  *
494  * Returns the tail of the queue @array and removes
495  * it from the queue.
496  *
497  * Returns: The tail of the queue
498  *
499  * Since: 1.14
500  */
501 gpointer
502 gst_queue_array_pop_tail (GstQueueArray * array)
503 {
504   gpointer ret;
505   guint len, idx;
506
507   g_return_val_if_fail (array != NULL, NULL);
508
509   len = array->length;
510
511   /* empty array */
512   if (len == 0)
513     return NULL;
514
515   idx = (array->head + (len - 1)) % array->size;
516
517   ret = *(gpointer *) (array->array + (sizeof (gpointer) * idx));
518
519   array->tail = idx;
520   array->length--;
521
522   return ret;
523 }
524
525 /**
526  * gst_queue_array_pop_tail_struct: (skip)
527  * @array: a #GstQueueArray object
528  *
529  * Returns the tail of the queue @array and removes
530  * it from the queue.
531  *
532  * Returns: The tail of the queue
533  *
534  * Since: 1.14
535  */
536 gpointer
537 gst_queue_array_pop_tail_struct (GstQueueArray * array)
538 {
539   gpointer ret;
540   guint len, idx;
541
542   g_return_val_if_fail (array != NULL, NULL);
543
544   len = array->length;
545
546   /* empty array */
547   if (len == 0)
548     return NULL;
549
550   idx = (array->head + (len - 1)) % array->size;
551
552   ret = array->array + (array->elt_size * idx);
553
554   array->tail = idx;
555   array->length--;
556
557   return ret;
558 }
559
560 /**
561  * gst_queue_array_is_empty: (skip)
562  * @array: a #GstQueueArray object
563  *
564  * Checks if the queue @array is empty.
565  *
566  * Returns: %TRUE if the queue @array is empty
567  *
568  * Since: 1.2
569  */
570 gboolean
571 gst_queue_array_is_empty (GstQueueArray * array)
572 {
573   g_return_val_if_fail (array != NULL, FALSE);
574   return (array->length == 0);
575 }
576
577
578 /**
579  * gst_queue_array_drop_struct: (skip)
580  * @array: a #GstQueueArray object
581  * @idx: index to drop
582  * @p_struct: address into which to store the data of the dropped structure, or NULL
583  *
584  * Drops the queue element at position @idx from queue @array and copies the
585  * data of the element or structure that was removed into @p_struct if
586  * @p_struct is set (not NULL).
587  *
588  * Returns: TRUE on success, or FALSE on error
589  *
590  * Since: 1.6
591  */
592 gboolean
593 gst_queue_array_drop_struct (GstQueueArray * array, guint idx,
594     gpointer p_struct)
595 {
596   int first_item_index, last_item_index;
597   guint actual_idx;
598   guint elt_size;
599
600   g_return_val_if_fail (array != NULL, FALSE);
601   actual_idx = (array->head + idx) % array->size;
602
603   g_return_val_if_fail (array->length > 0, FALSE);
604   g_return_val_if_fail (actual_idx < array->size, FALSE);
605
606   elt_size = array->elt_size;
607
608   first_item_index = array->head;
609
610   /* tail points to the first free spot */
611   last_item_index = (array->tail - 1 + array->size) % array->size;
612
613   if (p_struct != NULL)
614     memcpy (p_struct, array->array + elt_size * actual_idx, elt_size);
615
616   /* simple case actual_idx == first item */
617   if (actual_idx == first_item_index) {
618     /* clear current head position if needed */
619     if (p_struct == NULL)
620       gst_queue_array_clear_idx (array, idx);
621
622     /* move the head plus one */
623     array->head++;
624     array->head %= array->size;
625     array->length--;
626     return TRUE;
627   }
628
629   /* simple case idx == last item */
630   if (actual_idx == last_item_index) {
631     /* clear current tail position if needed */
632     if (p_struct == NULL)
633       gst_queue_array_clear_idx (array, idx);
634
635     /* move tail minus one, potentially wrapping */
636     array->tail = (array->tail - 1 + array->size) % array->size;
637     array->length--;
638     return TRUE;
639   }
640
641   /* non-wrapped case */
642   if (first_item_index < last_item_index) {
643     /* clear idx if needed */
644     if (p_struct == NULL)
645       gst_queue_array_clear_idx (array, idx);
646
647     g_assert (first_item_index < actual_idx && actual_idx < last_item_index);
648     /* move everything beyond actual_idx one step towards zero in array */
649     memmove (array->array + elt_size * actual_idx,
650         array->array + elt_size * (actual_idx + 1),
651         (last_item_index - actual_idx) * elt_size);
652     /* tail might wrap, ie if tail == 0 (and last_item_index == size) */
653     array->tail = (array->tail - 1 + array->size) % array->size;
654     array->length--;
655     return TRUE;
656   }
657
658   /* only wrapped cases left */
659   g_assert (first_item_index > last_item_index);
660
661   if (actual_idx < last_item_index) {
662     /* clear idx if needed */
663     if (p_struct == NULL)
664       gst_queue_array_clear_idx (array, idx);
665
666     /* actual_idx is before last_item_index, move data towards zero */
667     memmove (array->array + elt_size * actual_idx,
668         array->array + elt_size * (actual_idx + 1),
669         (last_item_index - actual_idx) * elt_size);
670     /* tail should not wrap in this case! */
671     g_assert (array->tail > 0);
672     array->tail--;
673     array->length--;
674     return TRUE;
675   }
676
677   if (actual_idx > first_item_index) {
678     /* clear idx if needed */
679     if (p_struct == NULL)
680       gst_queue_array_clear_idx (array, idx);
681
682     /* actual_idx is after first_item_index, move data to higher indices */
683     memmove (array->array + elt_size * (first_item_index + 1),
684         array->array + elt_size * first_item_index,
685         (actual_idx - first_item_index) * elt_size);
686     array->head++;
687     /* head should not wrap in this case! */
688     g_assert (array->head < array->size);
689     array->length--;
690     return TRUE;
691   }
692
693   g_return_val_if_reached (FALSE);
694 }
695
696 /**
697  * gst_queue_array_drop_element: (skip)
698  * @array: a #GstQueueArray object
699  * @idx: index to drop
700  *
701  * Drops the queue element at position @idx from queue @array.
702  *
703  * Returns: the dropped element
704  *
705  * Since: 1.2
706  */
707 gpointer
708 gst_queue_array_drop_element (GstQueueArray * array, guint idx)
709 {
710   gpointer ptr;
711
712   if (!gst_queue_array_drop_struct (array, idx, &ptr))
713     return NULL;
714
715   return ptr;
716 }
717
718 /**
719  * gst_queue_array_find: (skip)
720  * @array: a #GstQueueArray object
721  * @func: (allow-none): comparison function, or %NULL to find @data by value
722  * @data: data for comparison function
723  *
724  * Finds an element in the queue @array, either by comparing every element
725  * with @func or by looking up @data if no compare function @func is provided,
726  * and returning the index of the found element.
727  *
728  * Returns: Index of the found element or -1 if nothing was found.
729  *
730  * Since: 1.2
731  */
732 guint
733 gst_queue_array_find (GstQueueArray * array, GCompareFunc func, gpointer data)
734 {
735   gpointer p_element;
736   guint elt_size;
737   guint i;
738
739   /* For struct arrays we need to implement this differently so that
740    * the user gets a pointer to the element data not the dereferenced
741    * pointer itself */
742
743   g_return_val_if_fail (array != NULL, -1);
744   g_return_val_if_fail (array->struct_array == FALSE, -1);
745
746   elt_size = array->elt_size;
747
748   if (func != NULL) {
749     /* Scan from head to tail */
750     for (i = 0; i < array->length; i++) {
751       p_element = array->array + ((i + array->head) % array->size) * elt_size;
752       if (func (*(gpointer *) p_element, data) == 0)
753         return i;
754     }
755   } else {
756     for (i = 0; i < array->length; i++) {
757       p_element = array->array + ((i + array->head) % array->size) * elt_size;
758       if (*(gpointer *) p_element == data)
759         return i;
760     }
761   }
762
763   return -1;
764 }
765
766 /**
767  * gst_queue_array_get_length: (skip)
768  * @array: a #GstQueueArray object
769  *
770  * Returns the length of the queue @array
771  *
772  * Returns: the length of the queue @array.
773  *
774  * Since: 1.2
775  */
776 guint
777 gst_queue_array_get_length (GstQueueArray * array)
778 {
779   g_return_val_if_fail (array != NULL, 0);
780   return array->length;
781 }